Remove AngularJS based examples (#13695)
This commit is contained in:
parent
5a2852530f
commit
ec4b1c0158
124 changed files with 0 additions and 325429 deletions
|
@ -1,182 +0,0 @@
|
||||||
# Keycloak Broker: Facebook Social Identity Provider Quickstarts
|
|
||||||
|
|
||||||
What is it?
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This example demonstrates how to use Social Identity Providers with Keycloak to authenticate users. In this case,
|
|
||||||
users are authenticated with Facebook using Keycloak Identity Broker capabilities using the oAuth 2 protocol.
|
|
||||||
|
|
||||||
From this example, you'll learn how to:
|
|
||||||
|
|
||||||
* Set up a social identity provider for a specific realm
|
|
||||||
* Store tokens from a social identity provider and use these tokens to invoke the social provider API
|
|
||||||
|
|
||||||
Basically, once you try to access the application for the first time, you'll be redirected to Keycloak's login page.
|
|
||||||
In this page you'll note that there is a "Facebook" button that allows you to authenticate with Facebook Identity Provider.
|
|
||||||
|
|
||||||
After clicking the "Facebook" button, you'll be redirected to Facebook's login page from where you must authenticate
|
|
||||||
and grant the necessary permissions to Keycloak in order to access your personal information from Facebook.
|
|
||||||
|
|
||||||
If everything is fine, Facebook will redirect you back to Keycloak and at this point you'll be asked to provide some
|
|
||||||
basic profile information in order to create a new user in Keycloak based on your social account. Once you update your profile,
|
|
||||||
you'll be authenticated and redirected to the application.
|
|
||||||
|
|
||||||
Basically, what the application does is obtain some basic information for the authenticated user and also allow users to
|
|
||||||
load their profile from Facebook. For that, this application demonstrates how to retrieve the token issued by a social provider
|
|
||||||
for the authenticated user and use this token to invoke Facebook's API.
|
|
||||||
|
|
||||||
Make sure you've set up an application in Facebook
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
This example application requires you to create a Facebook Application. How to create it is beyond the scope of this
|
|
||||||
documentation.
|
|
||||||
|
|
||||||
Please take a look on [Facebook Developer Console](https://developers.facebook.com/apps/) for more details. Make sure to use the correct
|
|
||||||
redirect URI to be used as URL on Facebook. The facebook will redirect to this URI after finish authentication. For this example, it's the URL
|
|
||||||
[http://localhost:8080/auth/realms/facebook-identity-provider-realm/broker/facebook/endpoint](http://localhost:8080/auth/realms/facebook-identity-provider-realm/broker/facebook/endpoint) .
|
|
||||||
You can also determine this redirect URI from Keycloak admin console (It's in Identity provider settings for Facebook provider).
|
|
||||||
|
|
||||||
Once you have a Facebook Application configured, you need to obtain both **App ID** and **App Secret** and update the
|
|
||||||
**facebook-identity-provider-realm.json** configuration file with these information. There you'll find a section as follows:
|
|
||||||
|
|
||||||
"identityProviders": [
|
|
||||||
{
|
|
||||||
"id" : "facebook",
|
|
||||||
"providerId" : "facebook",
|
|
||||||
"name" : "Facebook",
|
|
||||||
"enabled": true,
|
|
||||||
"updateProfileFirstLogin" : "true",
|
|
||||||
"storeToken" : "true",
|
|
||||||
"config": {
|
|
||||||
"clientId": "CHANGE_CLIENT_ID",
|
|
||||||
"clientSecret": "CHANGE_CLIENT_SECRET"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Please, update both *clientId* and *clientSecret* configuration options with the **App ID** and **App Secret**.
|
|
||||||
|
|
||||||
Make sure you've set up the Keycloak Server
|
|
||||||
--------------------------------------
|
|
||||||
The Keycloak Appliance Distribution comes with a preconfigured Keycloak server (based on Wildfly). You can use it out of
|
|
||||||
the box to run these demos. So, if you're using this, you can head to Step 2.
|
|
||||||
|
|
||||||
Alternatively, you can install the Keycloak Server onto any EAP 6.x, or Wildfly 8.x server, but there is
|
|
||||||
a few steps you must follow.
|
|
||||||
|
|
||||||
Obtain latest keycloak-war-dist-all.zip. This distro is used to install Keycloak onto an existing JBoss installation.
|
|
||||||
This installs the server.
|
|
||||||
|
|
||||||
$ cd ${wildfly.jboss.home}/standalone
|
|
||||||
$ cp -r ${keycloak-war-dist-all}/deployments .
|
|
||||||
|
|
||||||
To be able to run the demos you also need to install the Keycloak client adapter. For Wildfly:
|
|
||||||
|
|
||||||
$ cd ${wildfly.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-wildfly-adapter-dist.zip
|
|
||||||
|
|
||||||
For JBoss EAP 6.x
|
|
||||||
|
|
||||||
$ cd ${eap.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-eap6-adapter-dist.zip
|
|
||||||
|
|
||||||
For JBoss AS 7.1.1:
|
|
||||||
|
|
||||||
$ cd ${as7.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-as7-adapter-dist.zip
|
|
||||||
|
|
||||||
Unzipping the adapter ZIP only installs the JAR files. You must also add the Keycloak Subsystem to the server's
|
|
||||||
configuration (standalone/configuration/standalone.xml).
|
|
||||||
|
|
||||||
<server xmlns="urn:jboss:domain:1.4">
|
|
||||||
|
|
||||||
<extensions>
|
|
||||||
<extension module="org.keycloak.keycloak-subsystem"/>
|
|
||||||
...
|
|
||||||
</extensions>
|
|
||||||
|
|
||||||
<profile>
|
|
||||||
<subsystem xmlns="urn:jboss:domain:keycloak:1.0"/>
|
|
||||||
...
|
|
||||||
</profile>
|
|
||||||
|
|
||||||
Boot Keycloak Server
|
|
||||||
---------------------------------------
|
|
||||||
Where you go to start up the Keycloak Server depends on which distro you installed.
|
|
||||||
|
|
||||||
From appliance:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd keycloak/bin
|
|
||||||
$ ./standalone.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
From existing Wildfly/EAP6/AS7 distro
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd ${wildfly.jboss.home}/bin
|
|
||||||
$ ./standalone.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Import the Test Realm
|
|
||||||
---------------------------------------
|
|
||||||
Next thing you have to do is import the test realm for the demo. Clicking on the below link will bring you to the
|
|
||||||
create realm page in the Admin UI. The username/password is admin/admin to login in. Keycloak will ask you to
|
|
||||||
create a new admin password before you can go to the create realm page.
|
|
||||||
|
|
||||||
[http://localhost:8080/auth/admin/master/console/#/create/realm](http://localhost:8080/auth/admin/master/console/#/create/realm)
|
|
||||||
|
|
||||||
Import the **facebook-identity-provider-realm.json** file that is in the facebook-authentication/ example directory.
|
|
||||||
|
|
||||||
|
|
||||||
Start JBoss Enterprise Application Platform 6 or WildFly with the Web Profile
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
1. Open a command line and navigate to the root of the JBoss server directory.
|
|
||||||
2. The following shows the command line to start the server with the web profile:
|
|
||||||
|
|
||||||
For Linux: JBOSS_HOME/bin/standalone.sh
|
|
||||||
For Windows: JBOSS_HOME\bin\standalone.bat
|
|
||||||
|
|
||||||
|
|
||||||
Build and Deploy the Quickstart
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
_NOTE: The following build command assumes you have configured your Maven user settings. If you have not, you must include Maven setting arguments on the command line. See [Build and Deploy the Quickstarts](../README.md#build-and-deploy-the-quickstarts) for complete instructions and additional options._
|
|
||||||
|
|
||||||
1. Make sure you have started the JBoss Server as described above.
|
|
||||||
2. Open a command line and navigate to the root directory of this quickstart.
|
|
||||||
3. Type this command to build and deploy the archive:
|
|
||||||
|
|
||||||
For EAP 6: mvn clean package jboss-as:deploy
|
|
||||||
For WildFly: mvn -Pwildfly clean package wildfly:deploy
|
|
||||||
|
|
||||||
4. This will deploy `target/facebook-authentication.war` to the running instance of the server.
|
|
||||||
|
|
||||||
|
|
||||||
Access the application
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The application will be running at the following URL: <http://localhost:8080/facebook-authentication>.
|
|
||||||
|
|
||||||
|
|
||||||
Undeploy the Archive
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
1. Make sure you have started the JBoss Server as described above.
|
|
||||||
2. Open a command line and navigate to the root directory of this quickstart.
|
|
||||||
3. When you are finished testing, type this command to undeploy the archive:
|
|
||||||
|
|
||||||
For EAP 6: mvn jboss-as:undeploy
|
|
||||||
For WildFly: mvn -Pwildfly wildfly:undeploy
|
|
||||||
|
|
||||||
|
|
||||||
Debug the Application
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
If you want to debug the source code or look at the Javadocs of any library in the project, run either of the following commands to pull them into your local repository. The IDE should then detect them.
|
|
||||||
|
|
||||||
mvn dependency:sources
|
|
||||||
mvn dependency:resolve -Dclassifier=javadoc
|
|
|
@ -1,62 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "facebook-identity-provider-realm",
|
|
||||||
"enabled": true,
|
|
||||||
"sslRequired": "external",
|
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
|
||||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"defaultRoles": [ "user" ],
|
|
||||||
"users" : [
|
|
||||||
{
|
|
||||||
"username" : "admin",
|
|
||||||
"enabled": true,
|
|
||||||
"email" : "admin@admin.com",
|
|
||||||
"firstName": "Admin",
|
|
||||||
"lastName": "Burke",
|
|
||||||
"credentials" : [
|
|
||||||
{ "type" : "password",
|
|
||||||
"value" : "password" }
|
|
||||||
],
|
|
||||||
"realmRoles": [ "user","admin" ],
|
|
||||||
"clientRoles": {
|
|
||||||
"realm-management": [ "realm-admin" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles" : {
|
|
||||||
"realm" : [
|
|
||||||
{
|
|
||||||
"name": "user",
|
|
||||||
"description": "User privileges"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"clients": [
|
|
||||||
{
|
|
||||||
"clientId": "facebook-authentication",
|
|
||||||
"enabled": true,
|
|
||||||
"publicClient" : true,
|
|
||||||
"adminUrl": "/facebook-authentication",
|
|
||||||
"baseUrl": "/facebook-authentication",
|
|
||||||
"redirectUris": [
|
|
||||||
"/facebook-authentication/*"
|
|
||||||
],
|
|
||||||
"webOrigins": [
|
|
||||||
"http://localhost:8080"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"identityProviders": [
|
|
||||||
{
|
|
||||||
"alias" : "facebook",
|
|
||||||
"providerId" : "facebook",
|
|
||||||
"enabled": true,
|
|
||||||
"updateProfileFirstLogin" : "true",
|
|
||||||
"storeToken" : "true",
|
|
||||||
"addReadTokenRoleOnCreate" : true,
|
|
||||||
"config": {
|
|
||||||
"clientId": "CHANGE_CLIENT_ID",
|
|
||||||
"clientSecret": "CHANGE_CLIENT_SECRET"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-examples-broker-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Keycloak Broker Examples - Facebook Authentication</name>
|
|
||||||
<artifactId>facebook-authentication</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<description>
|
|
||||||
An example about how to authenticate with Facebook
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>facebook-authentication</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,69 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Facebook Authentication Example</title>
|
|
||||||
|
|
||||||
<script src="js/lib/angular/angular.js"></script>
|
|
||||||
<script src="js/lib/angular/angular-resource.js"></script>
|
|
||||||
<script src="js/lib/angular/angular-route.js"></script>
|
|
||||||
<script src="js/lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
|
|
||||||
|
|
||||||
<script src="/auth/js/keycloak.js"></script>
|
|
||||||
<script src="js/app.js" type="text/javascript"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body data-ng-controller="GlobalCtrl">
|
|
||||||
|
|
||||||
<div id="content-area" class="col-md-9" role="main">
|
|
||||||
<div id="content">
|
|
||||||
<h2>Hello, {{identity.name}} [<a href="" ng-click="logout()">Sign Out</a>]</h2>
|
|
||||||
<div>
|
|
||||||
<p><b>This is your Keycloak Profile</b>:</p>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li><b>Id</b>: {{identity.sub}}</li>
|
|
||||||
<li><b>Username</b>: {{identity.preferred_username}}</li>
|
|
||||||
<li><b>Email</b>: {{identity.email}}</li>
|
|
||||||
<li><b>Full Name</b>: {{identity.name}}</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p><button type="submit" data-ng-click="loadSocialProfile()">Load your social profile</button></p>
|
|
||||||
|
|
||||||
<div data-ng-show="socialProfile">
|
|
||||||
<p><b>This is your Facebook Profile</b>:</p>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li><b>Id</b>: {{socialProfile.id}}</li>
|
|
||||||
<li><b>First Name</b>: {{socialProfile.first_name}}</li>
|
|
||||||
<li><b>Last Name</b>: {{socialProfile.last_name}}</li>
|
|
||||||
<li><b>Gender</b>: {{socialProfile.gender}}</li>
|
|
||||||
<li><b>Profile Link</b>: <a href="{{socialProfile.link}}">{{socialProfile.link}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var module = angular.module('app', []);
|
|
||||||
|
|
||||||
angular.element(document).ready(function ($http) {
|
|
||||||
var keycloakAuth = new Keycloak('keycloak.json');
|
|
||||||
|
|
||||||
keycloakAuth.init({ onLoad: 'login-required' }).then(function () {
|
|
||||||
module.factory('Auth', function() {
|
|
||||||
var Auth = {};
|
|
||||||
|
|
||||||
Auth.logout = function() {
|
|
||||||
keycloakAuth.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.getIdentity = function() {
|
|
||||||
return keycloakAuth.idTokenParsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.getToken = function() {
|
|
||||||
return keycloakAuth.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.refreshToken = function() {
|
|
||||||
return window.location = keycloakAuth.createLoginUrl({
|
|
||||||
idpHint: 'facebook'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Auth;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('authInterceptor', function($q) {
|
|
||||||
return {
|
|
||||||
request: function (config) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
config.headers = config.headers || {};
|
|
||||||
|
|
||||||
if (!config.headers.Authorization) {
|
|
||||||
config.headers.Authorization = 'Bearer ' + keycloakAuth.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolve(config);
|
|
||||||
|
|
||||||
if (keycloakAuth.token) {
|
|
||||||
keycloakAuth.updateToken(5).then(function() {
|
|
||||||
}).catch(function() {
|
|
||||||
deferred.reject('Failed to refresh token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
module.config(function($httpProvider) {
|
|
||||||
$httpProvider.responseInterceptors.push('errorInterceptor');
|
|
||||||
$httpProvider.interceptors.push('authInterceptor');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('errorInterceptor', function($q) {
|
|
||||||
return function(promise) {
|
|
||||||
return promise.then(function(response) {
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
angular.bootstrap(document, ["app"]);
|
|
||||||
}).error(function () {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('GlobalCtrl', function($scope, $http, $location, Auth) {
|
|
||||||
$scope.logout = function() {
|
|
||||||
Auth.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.identity = Auth.getIdentity();
|
|
||||||
|
|
||||||
$scope.loadSocialProfile = function() {
|
|
||||||
$http.get('/auth/realms/facebook-identity-provider-realm/broker/facebook/token').success(function(data) {
|
|
||||||
var accessTokenParameter = 'access_token=';
|
|
||||||
var accessToken = data.substring(data.indexOf(accessTokenParameter) + accessTokenParameter.length, data.indexOf('&'));
|
|
||||||
|
|
||||||
$http.get('https://graph.facebook.com/me?access_token=' + accessToken)
|
|
||||||
.success(function(profile) {
|
|
||||||
$scope.socialProfile = profile;
|
|
||||||
})
|
|
||||||
.error(function(data, status, headers, config) {
|
|
||||||
$scope.socialProfile = 'Could not obtain social profile. Trying to refresh your token.';
|
|
||||||
Auth.refreshToken();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,192 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.7
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var directive = {};
|
|
||||||
|
|
||||||
directive.dropdownToggle =
|
|
||||||
['$document', '$location', '$window',
|
|
||||||
function ($document, $location, $window) {
|
|
||||||
var openElement = null, close;
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.parent().bind('click', function(event) {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
var iWasOpen = false;
|
|
||||||
|
|
||||||
if (openElement) {
|
|
||||||
iWasOpen = openElement === element;
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!iWasOpen){
|
|
||||||
element.parent().addClass('open');
|
|
||||||
openElement = element;
|
|
||||||
|
|
||||||
close = function (event) {
|
|
||||||
event && event.preventDefault();
|
|
||||||
event && event.stopPropagation();
|
|
||||||
$document.unbind('click', close);
|
|
||||||
element.parent().removeClass('open');
|
|
||||||
close = null;
|
|
||||||
openElement = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$document.bind('click', close);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
|
|
||||||
|
|
||||||
directive.tabbable = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
compile: function(element) {
|
|
||||||
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
|
|
||||||
tabContent = angular.element('<div class="tab-content"></div>');
|
|
||||||
|
|
||||||
tabContent.append(element.contents());
|
|
||||||
element.append(navTabs).append(tabContent);
|
|
||||||
},
|
|
||||||
controller: ['$scope', '$element', function($scope, $element) {
|
|
||||||
var navTabs = $element.contents().eq(0),
|
|
||||||
ngModel = $element.controller('ngModel') || {},
|
|
||||||
tabs = [],
|
|
||||||
selectedTab;
|
|
||||||
|
|
||||||
ngModel.$render = function() {
|
|
||||||
var $viewValue = this.$viewValue;
|
|
||||||
|
|
||||||
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
|
|
||||||
if(selectedTab) {
|
|
||||||
selectedTab.paneElement.removeClass('active');
|
|
||||||
selectedTab.tabElement.removeClass('active');
|
|
||||||
selectedTab = null;
|
|
||||||
}
|
|
||||||
if($viewValue) {
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++) {
|
|
||||||
if ($viewValue == tabs[i].value) {
|
|
||||||
selectedTab = tabs[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedTab) {
|
|
||||||
selectedTab.paneElement.addClass('active');
|
|
||||||
selectedTab.tabElement.addClass('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addPane = function(element, attr) {
|
|
||||||
var li = angular.element('<li><a href></a></li>'),
|
|
||||||
a = li.find('a'),
|
|
||||||
tab = {
|
|
||||||
paneElement: element,
|
|
||||||
paneAttrs: attr,
|
|
||||||
tabElement: li
|
|
||||||
};
|
|
||||||
|
|
||||||
tabs.push(tab);
|
|
||||||
|
|
||||||
attr.$observe('value', update)();
|
|
||||||
attr.$observe('title', function(){ update(); a.text(tab.title); })();
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
tab.title = attr.title;
|
|
||||||
tab.value = attr.value || attr.title;
|
|
||||||
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
}
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
|
|
||||||
navTabs.append(li);
|
|
||||||
li.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (ngModel.$setViewValue) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
ngModel.$setViewValue(tab.value);
|
|
||||||
ngModel.$render();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
tab.tabElement.remove();
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
|
|
||||||
if (tab == tabs[i]) {
|
|
||||||
tabs.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.table = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
element[0].className = 'table table-bordered table-striped code-table';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.tabPane = function() {
|
|
||||||
return {
|
|
||||||
require: '^tabbable',
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs, tabsCtrl) {
|
|
||||||
element.bind('$remove', tabsCtrl.addPane(element, attrs));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('bootstrap', []).directive(directive);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,219 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngCookies
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngCookies
|
|
||||||
*
|
|
||||||
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
|
|
||||||
*
|
|
||||||
* {@installModule cookies}
|
|
||||||
*
|
|
||||||
* <div doc-module-components="ngCookies"></div>
|
|
||||||
*
|
|
||||||
* See {@link ngCookies.$cookies `$cookies`} and
|
|
||||||
* {@link ngCookies.$cookieStore `$cookieStore`} for usage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('ngCookies', ['ng']).
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngCookies.$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.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example>
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function ExampleController($cookies) {
|
|
||||||
// Retrieving a cookie
|
|
||||||
var favoriteCookie = $cookies.myFavorite;
|
|
||||||
// Setting a cookie
|
|
||||||
$cookies.myFavorite = 'oatmeal';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</doc:source>
|
|
||||||
</doc: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 ngCookies.$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.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*/
|
|
||||||
factory('$cookieStore', ['$cookies', function($cookies) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngCookies.$cookieStore#get
|
|
||||||
* @methodOf ngCookies.$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) {
|
|
||||||
var value = $cookies[key];
|
|
||||||
return value ? angular.fromJson(value) : value;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngCookies.$cookieStore#put
|
|
||||||
* @methodOf ngCookies.$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 ngCookies.$cookieStore#remove
|
|
||||||
* @methodOf ngCookies.$cookieStore
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Remove given cookie
|
|
||||||
*
|
|
||||||
* @param {string} key Id of the key-value pair to delete.
|
|
||||||
*/
|
|
||||||
remove: function(key) {
|
|
||||||
delete $cookies[key];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}]);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,427 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function() {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* This object provides a utility for producing rich Error messages within
|
|
||||||
* Angular. It can be called as follows:
|
|
||||||
*
|
|
||||||
* var exampleMinErr = minErr('example');
|
|
||||||
* throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
|
|
||||||
*
|
|
||||||
* The above creates an instance of minErr in the example namespace. The
|
|
||||||
* resulting error will have a namespaced error code of example.one. The
|
|
||||||
* resulting error will replace {0} with the value of foo, and {1} with the
|
|
||||||
* value of bar. The object is not restricted in the number of arguments it can
|
|
||||||
* take.
|
|
||||||
*
|
|
||||||
* If fewer arguments are specified than necessary for interpolation, the extra
|
|
||||||
* interpolation markers will be preserved in the final string.
|
|
||||||
*
|
|
||||||
* Since data will be parsed statically during a build step, some restrictions
|
|
||||||
* are applied with respect to how minErr instances are created and called.
|
|
||||||
* Instances should have names of the form namespaceMinErr for a minErr created
|
|
||||||
* using minErr('namespace') . Error codes, namespaces and template strings
|
|
||||||
* should all be static strings, not variables or general expressions.
|
|
||||||
*
|
|
||||||
* @param {string} module The namespace to use for the new minErr instance.
|
|
||||||
* @returns {function(string, string, ...): Error} instance
|
|
||||||
*/
|
|
||||||
|
|
||||||
function minErr(module) {
|
|
||||||
return function () {
|
|
||||||
var code = arguments[0],
|
|
||||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
|
||||||
template = arguments[1],
|
|
||||||
templateArgs = arguments,
|
|
||||||
stringify = function (obj) {
|
|
||||||
if (typeof obj === 'function') {
|
|
||||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
|
||||||
} else if (typeof obj === 'undefined') {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof obj !== 'string') {
|
|
||||||
return JSON.stringify(obj);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
message, i;
|
|
||||||
|
|
||||||
message = prefix + template.replace(/\{\d+\}/g, function (match) {
|
|
||||||
var index = +match.slice(1, -1), arg;
|
|
||||||
|
|
||||||
if (index + 2 < templateArgs.length) {
|
|
||||||
arg = templateArgs[index + 2];
|
|
||||||
if (typeof arg === 'function') {
|
|
||||||
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
|
|
||||||
} else if (typeof arg === 'undefined') {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof arg !== 'string') {
|
|
||||||
return toJson(arg);
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
|
|
||||||
message = message + '\nhttp://errors.angularjs.org/1.2.13/' +
|
|
||||||
(module ? module + '/' : '') + code;
|
|
||||||
for (i = 2; i < arguments.length; i++) {
|
|
||||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
|
|
||||||
encodeURIComponent(stringify(arguments[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Error(message);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc interface
|
|
||||||
* @name angular.Module
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* Interface for configuring angular {@link angular.module modules}.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function setupModuleLoader(window) {
|
|
||||||
|
|
||||||
var $injectorMinErr = minErr('$injector');
|
|
||||||
var ngMinErr = minErr('ng');
|
|
||||||
|
|
||||||
function ensure(obj, name, factory) {
|
|
||||||
return obj[name] || (obj[name] = factory());
|
|
||||||
}
|
|
||||||
|
|
||||||
var angular = ensure(window, 'angular', Object);
|
|
||||||
|
|
||||||
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
|
|
||||||
angular.$$minErr = angular.$$minErr || minErr;
|
|
||||||
|
|
||||||
return ensure(angular, 'module', function() {
|
|
||||||
/** @type {Object.<string, angular.Module>} */
|
|
||||||
var modules = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc function
|
|
||||||
* @name angular.module
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* The `angular.module` is a global place for creating, registering and retrieving Angular
|
|
||||||
* modules.
|
|
||||||
* All modules (angular core or 3rd party) that should be available to an application must be
|
|
||||||
* registered using this mechanism.
|
|
||||||
*
|
|
||||||
* When passed two or more arguments, a new module is created. If passed only one argument, an
|
|
||||||
* existing module (the name passed as the first argument to `module`) is retrieved.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* # Module
|
|
||||||
*
|
|
||||||
* A module is a collection of services, directives, filters, and configuration information.
|
|
||||||
* `angular.module` is used to configure the {@link AUTO.$injector $injector}.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* // Create a new module
|
|
||||||
* var myModule = angular.module('myModule', []);
|
|
||||||
*
|
|
||||||
* // register a new service
|
|
||||||
* myModule.value('appName', 'MyCoolApp');
|
|
||||||
*
|
|
||||||
* // configure existing services inside initialization blocks.
|
|
||||||
* myModule.config(function($locationProvider) {
|
|
||||||
* // Configure existing providers
|
|
||||||
* $locationProvider.hashPrefix('!');
|
|
||||||
* });
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* Then you can create an injector and load your modules like this:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* var injector = angular.injector(['ng', 'MyModule'])
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* However it's more likely that you'll just use
|
|
||||||
* {@link ng.directive:ngApp ngApp} or
|
|
||||||
* {@link angular.bootstrap} to simplify this process for you.
|
|
||||||
*
|
|
||||||
* @param {!string} name The name of the module to create or retrieve.
|
|
||||||
* @param {Array.<string>=} requires If specified then new module is being created. If
|
|
||||||
* unspecified then the the module is being retrieved for further configuration.
|
|
||||||
* @param {Function} configFn Optional configuration function for the module. Same as
|
|
||||||
* {@link angular.Module#methods_config Module#config()}.
|
|
||||||
* @returns {module} new module with the {@link angular.Module} api.
|
|
||||||
*/
|
|
||||||
return function module(name, requires, configFn) {
|
|
||||||
var assertNotHasOwnProperty = function(name, context) {
|
|
||||||
if (name === 'hasOwnProperty') {
|
|
||||||
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
assertNotHasOwnProperty(name, 'module');
|
|
||||||
if (requires && modules.hasOwnProperty(name)) {
|
|
||||||
modules[name] = null;
|
|
||||||
}
|
|
||||||
return ensure(modules, name, function() {
|
|
||||||
if (!requires) {
|
|
||||||
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
|
|
||||||
"the module name or forgot to load it. If registering a module ensure that you " +
|
|
||||||
"specify the dependencies as the second argument.", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {!Array.<Array.<*>>} */
|
|
||||||
var invokeQueue = [];
|
|
||||||
|
|
||||||
/** @type {!Array.<Function>} */
|
|
||||||
var runBlocks = [];
|
|
||||||
|
|
||||||
var config = invokeLater('$injector', 'invoke');
|
|
||||||
|
|
||||||
/** @type {angular.Module} */
|
|
||||||
var moduleInstance = {
|
|
||||||
// Private state
|
|
||||||
_invokeQueue: invokeQueue,
|
|
||||||
_runBlocks: runBlocks,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc property
|
|
||||||
* @name angular.Module#requires
|
|
||||||
* @propertyOf angular.Module
|
|
||||||
* @returns {Array.<string>} List of module names which must be loaded before this module.
|
|
||||||
* @description
|
|
||||||
* Holds the list of modules which the injector will load before the current module is
|
|
||||||
* loaded.
|
|
||||||
*/
|
|
||||||
requires: requires,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc property
|
|
||||||
* @name angular.Module#name
|
|
||||||
* @propertyOf angular.Module
|
|
||||||
* @returns {string} Name of the module.
|
|
||||||
* @description
|
|
||||||
*/
|
|
||||||
name: name,
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#provider
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} providerType Construction function for creating new instance of the
|
|
||||||
* service.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#provider $provide.provider()}.
|
|
||||||
*/
|
|
||||||
provider: invokeLater('$provide', 'provider'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#factory
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} providerFunction Function for creating new instance of the service.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#factory $provide.factory()}.
|
|
||||||
*/
|
|
||||||
factory: invokeLater('$provide', 'factory'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#service
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} constructor A constructor function that will be instantiated.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#service $provide.service()}.
|
|
||||||
*/
|
|
||||||
service: invokeLater('$provide', 'service'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#value
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {*} object Service instance object.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#value $provide.value()}.
|
|
||||||
*/
|
|
||||||
value: invokeLater('$provide', 'value'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#constant
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name constant name
|
|
||||||
* @param {*} object Constant value.
|
|
||||||
* @description
|
|
||||||
* Because the constant are fixed, they get applied before other provide methods.
|
|
||||||
* See {@link AUTO.$provide#constant $provide.constant()}.
|
|
||||||
*/
|
|
||||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#animation
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name animation name
|
|
||||||
* @param {Function} animationFactory Factory function for creating new instance of an
|
|
||||||
* animation.
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Defines an animation hook that can be later used with
|
|
||||||
* {@link ngAnimate.$animate $animate} service and directives that use this service.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* module.animation('.animation-name', function($inject1, $inject2) {
|
|
||||||
* return {
|
|
||||||
* eventName : function(element, done) {
|
|
||||||
* //code to run the animation
|
|
||||||
* //once complete, then run done()
|
|
||||||
* return function cancellationFunction(element) {
|
|
||||||
* //code to cancel the animation
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* })
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
|
|
||||||
* {@link ngAnimate ngAnimate module} for more information.
|
|
||||||
*/
|
|
||||||
animation: invokeLater('$animateProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#filter
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name Filter name.
|
|
||||||
* @param {Function} filterFactory Factory function for creating new instance of filter.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$filterProvider#register $filterProvider.register()}.
|
|
||||||
*/
|
|
||||||
filter: invokeLater('$filterProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#controller
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string|Object} name Controller name, or an object map of controllers where the
|
|
||||||
* keys are the names and the values are the constructors.
|
|
||||||
* @param {Function} constructor Controller constructor function.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
|
|
||||||
*/
|
|
||||||
controller: invokeLater('$controllerProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#directive
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string|Object} name Directive name, or an object map of directives where the
|
|
||||||
* keys are the names and the values are the factories.
|
|
||||||
* @param {Function} directiveFactory Factory function for creating new instance of
|
|
||||||
* directives.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}.
|
|
||||||
*/
|
|
||||||
directive: invokeLater('$compileProvider', 'directive'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#config
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {Function} configFn Execute this function on module load. Useful for service
|
|
||||||
* configuration.
|
|
||||||
* @description
|
|
||||||
* Use this method to register work which needs to be performed on module loading.
|
|
||||||
*/
|
|
||||||
config: config,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#run
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {Function} initializationFn Execute this function after injector creation.
|
|
||||||
* Useful for application initialization.
|
|
||||||
* @description
|
|
||||||
* Use this method to register work which should be performed when the injector is done
|
|
||||||
* loading all modules.
|
|
||||||
*/
|
|
||||||
run: function(block) {
|
|
||||||
runBlocks.push(block);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (configFn) {
|
|
||||||
config(configFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return moduleInstance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} provider
|
|
||||||
* @param {string} method
|
|
||||||
* @param {String=} insertMethod
|
|
||||||
* @returns {angular.Module}
|
|
||||||
*/
|
|
||||||
function invokeLater(provider, method, insertMethod) {
|
|
||||||
return function() {
|
|
||||||
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
|
|
||||||
return moduleInstance;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setupModuleLoader(window);
|
|
||||||
})(window);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closure compiler type information
|
|
||||||
*
|
|
||||||
* @typedef { {
|
|
||||||
* requires: !Array.<string>,
|
|
||||||
* invokeQueue: !Array.<Array.<*>>,
|
|
||||||
*
|
|
||||||
* service: function(string, Function):angular.Module,
|
|
||||||
* factory: function(string, Function):angular.Module,
|
|
||||||
* value: function(string, *):angular.Module,
|
|
||||||
*
|
|
||||||
* filter: function(string, Function):angular.Module,
|
|
||||||
*
|
|
||||||
* init: function(Function):angular.Module
|
|
||||||
* } }
|
|
||||||
*/
|
|
||||||
angular.Module;
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,613 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 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
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngResource
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngResource.$resource
|
|
||||||
* @requires $http
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* A factory which creates a resource object that lets you interact with
|
|
||||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
|
||||||
*
|
|
||||||
* The returned resource object has action methods which provide high-level behaviors without
|
|
||||||
* the need to interact with the low level {@link ng.$http $http} service.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngResource `ngResource`} module to be installed.
|
|
||||||
*
|
|
||||||
* @param {string} url A parametrized URL template with parameters prefixed by `:` as in
|
|
||||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
|
||||||
* `http://example.com:8080/api`), it will be respected.
|
|
||||||
*
|
|
||||||
* If you are using a url with a suffix, just add the suffix, like this:
|
|
||||||
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
|
|
||||||
* or even `$resource('http://example.com/resource/:resource_id.:format')`
|
|
||||||
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
|
|
||||||
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
|
|
||||||
* can escape it with `/\.`.
|
|
||||||
*
|
|
||||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
|
||||||
* `actions` methods. 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).
|
|
||||||
*
|
|
||||||
* Each key value in the parameter object is first bound to url template if present and then any
|
|
||||||
* excess keys are appended to the url search query after the `?`.
|
|
||||||
*
|
|
||||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
|
||||||
* URL `/path/greet?salutation=Hello`.
|
|
||||||
*
|
|
||||||
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
|
|
||||||
* the data object (useful for non-GET operations).
|
|
||||||
*
|
|
||||||
* @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#usage_parameters $http.config}:
|
|
||||||
*
|
|
||||||
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
|
|
||||||
* action2: {method:?, params:?, isArray:?, headers:?, ...},
|
|
||||||
* ...}
|
|
||||||
*
|
|
||||||
* 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)>}` –
|
|
||||||
* 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)>}` –
|
|
||||||
* 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
|
|
||||||
* GET request, otherwise if a cache instance built with
|
|
||||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
|
||||||
* caching.
|
|
||||||
* - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
|
|
||||||
* should abort the request when resolved.
|
|
||||||
* - **`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
|
|
||||||
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
|
|
||||||
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
|
|
||||||
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
|
|
||||||
* with `http response` object. See {@link ng.$http $http interceptors}.
|
|
||||||
*
|
|
||||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
|
||||||
* optionally extended with custom `actions`. The default set contains these actions:
|
|
||||||
*
|
|
||||||
* { 'get': {method:'GET'},
|
|
||||||
* 'save': {method:'POST'},
|
|
||||||
* 'query': {method:'GET', isArray:true},
|
|
||||||
* 'remove': {method:'DELETE'},
|
|
||||||
* 'delete': {method:'DELETE'} };
|
|
||||||
*
|
|
||||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
|
||||||
* destination and parameters. When the data is returned from the server then the object is an
|
|
||||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
|
||||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
|
||||||
* read, update, delete) on server-side data like this:
|
|
||||||
* <pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
var user = User.get({userId:123}, function() {
|
|
||||||
user.abc = true;
|
|
||||||
user.$save();
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* It is important to realize that invoking a $resource object method immediately returns an
|
|
||||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
|
||||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
|
||||||
* 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 cases 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:
|
|
||||||
*
|
|
||||||
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
|
|
||||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
|
|
||||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
|
||||||
*
|
|
||||||
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
|
|
||||||
* with (httpResponse) argument.
|
|
||||||
*
|
|
||||||
* Class actions return empty instance (with additional properties below).
|
|
||||||
* Instance actions return promise of the action.
|
|
||||||
*
|
|
||||||
* The Resource instances and collection have these additional properties:
|
|
||||||
*
|
|
||||||
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
|
|
||||||
* instance or collection.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* # Credit card resource
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
// Define CreditCard class
|
|
||||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
|
||||||
{userId:123, cardId:'@id'}, {
|
|
||||||
charge: {method:'POST', params:{charge:true}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// We can retrieve a collection from the server
|
|
||||||
var cards = CreditCard.query(function() {
|
|
||||||
// GET: /user/123/card
|
|
||||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
|
||||||
|
|
||||||
var card = cards[0];
|
|
||||||
// each item is an instance of CreditCard
|
|
||||||
expect(card instanceof CreditCard).toEqual(true);
|
|
||||||
card.name = "J. Smith";
|
|
||||||
// non GET methods are mapped onto the instances
|
|
||||||
card.$save();
|
|
||||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
|
||||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
|
||||||
|
|
||||||
// our custom method is mapped as well.
|
|
||||||
card.$charge({amount:9.99});
|
|
||||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
|
||||||
});
|
|
||||||
|
|
||||||
// we can create an instance as well
|
|
||||||
var newCard = new CreditCard({number:'0123'});
|
|
||||||
newCard.name = "Mike Smith";
|
|
||||||
newCard.$save();
|
|
||||||
// POST: /user/123/card {number:'0123', 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`.
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
var user = User.get({userId:123}, function() {
|
|
||||||
user.abc = true;
|
|
||||||
user.$save();
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* It's worth noting that the success callback for `get`, `query` and other methods 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:
|
|
||||||
*
|
|
||||||
<pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
User.get({userId:123}, function(u, getResponseHeaders){
|
|
||||||
u.abc = true;
|
|
||||||
u.$save(function(u, putResponseHeaders) {
|
|
||||||
//u => saved user object
|
|
||||||
//putResponseHeaders => $http header getter
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
* # 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', '$q', function($http, $q) {
|
|
||||||
|
|
||||||
var DEFAULT_ACTIONS = {
|
|
||||||
'get': {method:'GET'},
|
|
||||||
'save': {method:'POST'},
|
|
||||||
'query': {method:'GET', isArray:true},
|
|
||||||
'remove': {method:'DELETE'},
|
|
||||||
'delete': {method:'DELETE'}
|
|
||||||
};
|
|
||||||
var noop = angular.noop,
|
|
||||||
forEach = angular.forEach,
|
|
||||||
extend = angular.extend,
|
|
||||||
copy = angular.copy,
|
|
||||||
isFunction = angular.isFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
|
|
||||||
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
|
|
||||||
* segments:
|
|
||||||
* segment = *pchar
|
|
||||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
|
||||||
* pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
|
||||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
||||||
* / "*" / "+" / "," / ";" / "="
|
|
||||||
*/
|
|
||||||
function encodeUriSegment(val) {
|
|
||||||
return encodeUriQuery(val, true).
|
|
||||||
replace(/%26/gi, '&').
|
|
||||||
replace(/%3D/gi, '=').
|
|
||||||
replace(/%2B/gi, '+');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 / "-" / "." / "_" / "~"
|
|
||||||
* pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
||||||
* / "*" / "+" / "," / ";" / "="
|
|
||||||
*/
|
|
||||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
|
||||||
return encodeURIComponent(val).
|
|
||||||
replace(/%40/gi, '@').
|
|
||||||
replace(/%3A/gi, ':').
|
|
||||||
replace(/%24/g, '$').
|
|
||||||
replace(/%2C/gi, ',').
|
|
||||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function Route(template, defaults) {
|
|
||||||
this.template = template;
|
|
||||||
this.defaults = defaults || {};
|
|
||||||
this.urlParams = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Route.prototype = {
|
|
||||||
setUrlParams: function(config, params, actionUrl) {
|
|
||||||
var self = this,
|
|
||||||
url = actionUrl || self.template,
|
|
||||||
val,
|
|
||||||
encodedVal;
|
|
||||||
|
|
||||||
var urlParams = self.urlParams = {};
|
|
||||||
forEach(url.split(/\W/), function(param){
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
url = url.replace(/\\:/g, ':');
|
|
||||||
|
|
||||||
params = params || {};
|
|
||||||
forEach(self.urlParams, function(_, urlParam){
|
|
||||||
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"), function(match, p1) {
|
|
||||||
return encodedVal + p1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
|
|
||||||
leadingSlashes, tail) {
|
|
||||||
if (tail.charAt(0) == '/') {
|
|
||||||
return tail;
|
|
||||||
} else {
|
|
||||||
return leadingSlashes + tail;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// strip trailing slashes and set the url
|
|
||||||
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+($|\?))/, '.');
|
|
||||||
// replace escaped `/\.` with `/.`
|
|
||||||
config.url = url.replace(/\/\\\./, '/.');
|
|
||||||
|
|
||||||
|
|
||||||
// set params - delegate param encoding to $http
|
|
||||||
forEach(params, function(value, key){
|
|
||||||
if (!self.urlParams[key]) {
|
|
||||||
config.params = config.params || {};
|
|
||||||
config.params[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function resourceFactory(url, paramDefaults, actions) {
|
|
||||||
var route = new Route(url);
|
|
||||||
|
|
||||||
actions = extend({}, DEFAULT_ACTIONS, actions);
|
|
||||||
|
|
||||||
function extractParams(data, actionParams){
|
|
||||||
var ids = {};
|
|
||||||
actionParams = extend({}, paramDefaults, actionParams);
|
|
||||||
forEach(actionParams, function(value, key){
|
|
||||||
if (isFunction(value)) { value = value(); }
|
|
||||||
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
|
|
||||||
lookupDottedPath(data, value.substr(1)) : value;
|
|
||||||
});
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultResponseInterceptor(response) {
|
|
||||||
return response.resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Resource(value){
|
|
||||||
shallowClearAndCopy(value || {}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(actions, function(action, name) {
|
|
||||||
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
|
|
||||||
|
|
||||||
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;
|
|
||||||
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 (hasBody) data = a1;
|
|
||||||
else params = a1;
|
|
||||||
break;
|
|
||||||
case 0: break;
|
|
||||||
default:
|
|
||||||
throw $resourceMinErr('badargs',
|
|
||||||
"Expected up to 4 arguments [params, data, success, error], got {0} arguments",
|
|
||||||
arguments.length);
|
|
||||||
}
|
|
||||||
/* jshint +W086 */ /* (purposefully fall through case statements) */
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
forEach(action, function(value, key) {
|
|
||||||
if (key != 'params' && key != 'isArray' && key != 'interceptor') {
|
|
||||||
httpConfig[key] = copy(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// 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 {
|
|
||||||
shallowClearAndCopy(data, value);
|
|
||||||
value.$promise = promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value.$resolved = true;
|
|
||||||
|
|
||||||
response.resource = value;
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
value.$resolved = true;
|
|
||||||
|
|
||||||
(error||noop)(response);
|
|
||||||
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then(
|
|
||||||
function(response) {
|
|
||||||
var value = responseInterceptor(response);
|
|
||||||
(success||noop)(value, response.headers);
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
responseErrorInterceptor);
|
|
||||||
|
|
||||||
if (!isInstanceCall) {
|
|
||||||
// we are creating instance / collection
|
|
||||||
// - set the initial promise
|
|
||||||
// - return the instance / collection
|
|
||||||
value.$promise = promise;
|
|
||||||
value.$resolved = false;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// instance call
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Resource.prototype['$' + name] = function(params, success, error) {
|
|
||||||
if (isFunction(params)) {
|
|
||||||
error = success; success = params; params = {};
|
|
||||||
}
|
|
||||||
var result = Resource[name].call(this, params, this, success, error);
|
|
||||||
return result.$promise || result;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
Resource.bind = function(additionalParamDefaults){
|
|
||||||
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
return Resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceFactory;
|
|
||||||
}]);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,938 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngRoute
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngRoute
|
|
||||||
*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$routeProvider
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* 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 = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$routeProvider#when
|
|
||||||
* @methodOf ngRoute.$routeProvider
|
|
||||||
*
|
|
||||||
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
|
|
||||||
* contains redundant 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 exactly match the
|
|
||||||
* route definition.
|
|
||||||
*
|
|
||||||
* * `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:
|
|
||||||
* 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: e.g.`:name?`.
|
|
||||||
*
|
|
||||||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
|
|
||||||
* `/color/brown/largecode/code/with/slashs/edit` and extract:
|
|
||||||
*
|
|
||||||
* * `color: brown`
|
|
||||||
* * `largecode: code/with/slashs`.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param {Object} route Mapping information to be assigned to `$route.current` on route
|
|
||||||
* match.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* - `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
|
|
||||||
* returns an html template as a string which should be used by {@link
|
|
||||||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
|
|
||||||
* This property takes precedence over `templateUrl`.
|
|
||||||
*
|
|
||||||
* If `template` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
|
||||||
* `$location.path()` by applying the current route
|
|
||||||
*
|
|
||||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
|
|
||||||
* template that should be used by {@link ngRoute.directive:ngView ngView}.
|
|
||||||
*
|
|
||||||
* If `templateUrl` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
|
||||||
* `$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, 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.
|
|
||||||
*
|
|
||||||
* - `redirectTo` – {(string|function())=} – value to update
|
|
||||||
* {@link ng.$location $location} path with and trigger route redirection.
|
|
||||||
*
|
|
||||||
* If `redirectTo` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Object.<string>}` - route parameters extracted from the current
|
|
||||||
* `$location.path()` by applying the current route templateUrl.
|
|
||||||
* - `{string}` - current `$location.path()`
|
|
||||||
* - `{Object}` - current `$location.search()`
|
|
||||||
*
|
|
||||||
* The custom `redirectTo` function is expected to return a string which will be used
|
|
||||||
* to update `$location.path()` and `$location.search()`.
|
|
||||||
*
|
|
||||||
* - `[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.
|
|
||||||
*
|
|
||||||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
|
|
||||||
*
|
|
||||||
* If the option is set to `true`, then the particular route can be matched without being
|
|
||||||
* case sensitive
|
|
||||||
*
|
|
||||||
* @returns {Object} self
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Adds a new route definition to the `$route` service.
|
|
||||||
*/
|
|
||||||
this.when = function(path, route) {
|
|
||||||
routes[path] = angular.extend(
|
|
||||||
{reloadOnSearch: true},
|
|
||||||
route,
|
|
||||||
path && pathRegExp(path, route)
|
|
||||||
);
|
|
||||||
|
|
||||||
// create redirection for trailing slashes
|
|
||||||
if (path) {
|
|
||||||
var redirectPath = (path[path.length-1] == '/')
|
|
||||||
? path.substr(0, path.length-1)
|
|
||||||
: path +'/';
|
|
||||||
|
|
||||||
routes[redirectPath] = angular.extend(
|
|
||||||
{redirectTo: path},
|
|
||||||
pathRegExp(redirectPath, route)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param path {string} path
|
|
||||||
* @param opts {Object} options
|
|
||||||
* @return {?Object}
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Normalizes the given path, returning a regular expression
|
|
||||||
* and the original path.
|
|
||||||
*
|
|
||||||
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
|
|
||||||
*/
|
|
||||||
function pathRegExp(path, opts) {
|
|
||||||
var insensitive = opts.caseInsensitiveMatch,
|
|
||||||
ret = {
|
|
||||||
originalPath: path,
|
|
||||||
regexp: path
|
|
||||||
},
|
|
||||||
keys = ret.keys = [];
|
|
||||||
|
|
||||||
path = path
|
|
||||||
.replace(/([().])/g, '\\$1')
|
|
||||||
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){
|
|
||||||
var optional = option === '?' ? option : null;
|
|
||||||
var star = option === '*' ? option : null;
|
|
||||||
keys.push({ name: key, optional: !!optional });
|
|
||||||
slash = slash || '';
|
|
||||||
return ''
|
|
||||||
+ (optional ? '' : slash)
|
|
||||||
+ '(?:'
|
|
||||||
+ (optional ? slash : '')
|
|
||||||
+ (star && '(.+?)' || '([^/]+)')
|
|
||||||
+ (optional || '')
|
|
||||||
+ ')'
|
|
||||||
+ (optional || '');
|
|
||||||
})
|
|
||||||
.replace(/([\/$\*])/g, '\\$1');
|
|
||||||
|
|
||||||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$routeProvider#otherwise
|
|
||||||
* @methodOf ngRoute.$routeProvider
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Sets route definition that will be used on route change when no other route definition
|
|
||||||
* is matched.
|
|
||||||
*
|
|
||||||
* @param {Object} params Mapping information to be assigned to `$route.current`.
|
|
||||||
* @returns {Object} self
|
|
||||||
*/
|
|
||||||
this.otherwise = function(params) {
|
|
||||||
this.when(null, params);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
this.$get = ['$rootScope',
|
|
||||||
'$location',
|
|
||||||
'$routeParams',
|
|
||||||
'$q',
|
|
||||||
'$injector',
|
|
||||||
'$http',
|
|
||||||
'$templateCache',
|
|
||||||
'$sce',
|
|
||||||
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$route
|
|
||||||
* @requires $location
|
|
||||||
* @requires $routeParams
|
|
||||||
*
|
|
||||||
* @property {Object} current Reference to the current route definition.
|
|
||||||
* The route definition contains:
|
|
||||||
*
|
|
||||||
* - `controller`: The controller constructor as define in route definition.
|
|
||||||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
|
|
||||||
* controller instantiation. The `locals` contain
|
|
||||||
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
|
|
||||||
*
|
|
||||||
* - `$scope` - The current route scope.
|
|
||||||
* - `$template` - The current route template HTML.
|
|
||||||
*
|
|
||||||
* @property {Array.<Object>} routes Array of all configured routes.
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials).
|
|
||||||
* It watches `$location.url()` and tries to map the path to an existing route definition.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
This example shows how changing the URL hash causes the `$route` to match a route against the
|
|
||||||
URL, and the `ngView` pulls in the partial.
|
|
||||||
|
|
||||||
Note that this example is using {@link ng.directive:script inlined templates}
|
|
||||||
to get it working on jsfiddle as well.
|
|
||||||
|
|
||||||
<example module="ngViewExample" deps="angular-route.js">
|
|
||||||
<file name="index.html">
|
|
||||||
<div ng-controller="MainCntl">
|
|
||||||
Choose:
|
|
||||||
<a href="Book/Moby">Moby</a> |
|
|
||||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
|
||||||
<a href="Book/Gatsby">Gatsby</a> |
|
|
||||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
|
||||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
|
||||||
|
|
||||||
<div ng-view></div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre>$location.path() = {{$location.path()}}</pre>
|
|
||||||
<pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
|
|
||||||
<pre>$route.current.params = {{$route.current.params}}</pre>
|
|
||||||
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
|
|
||||||
<pre>$routeParams = {{$routeParams}}</pre>
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="book.html">
|
|
||||||
controller: {{name}}<br />
|
|
||||||
Book Id: {{params.bookId}}<br />
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="chapter.html">
|
|
||||||
controller: {{name}}<br />
|
|
||||||
Book Id: {{params.bookId}}<br />
|
|
||||||
Chapter Id: {{params.chapterId}}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="script.js">
|
|
||||||
angular.module('ngViewExample', ['ngRoute'])
|
|
||||||
|
|
||||||
.config(function($routeProvider, $locationProvider) {
|
|
||||||
$routeProvider.when('/Book/:bookId', {
|
|
||||||
templateUrl: 'book.html',
|
|
||||||
controller: BookCntl,
|
|
||||||
resolve: {
|
|
||||||
// I will cause a 1 second delay
|
|
||||||
delay: function($q, $timeout) {
|
|
||||||
var delay = $q.defer();
|
|
||||||
$timeout(delay.resolve, 1000);
|
|
||||||
return delay.promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
|
|
||||||
templateUrl: 'chapter.html',
|
|
||||||
controller: ChapterCntl
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure html5 to get links working on jsfiddle
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
function MainCntl($scope, $route, $routeParams, $location) {
|
|
||||||
$scope.$route = $route;
|
|
||||||
$scope.$location = $location;
|
|
||||||
$scope.$routeParams = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookCntl($scope, $routeParams) {
|
|
||||||
$scope.name = "BookCntl";
|
|
||||||
$scope.params = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChapterCntl($scope, $routeParams) {
|
|
||||||
$scope.name = "ChapterCntl";
|
|
||||||
$scope.params = $routeParams;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="protractorTest.js">
|
|
||||||
it('should load and compile correct template', function() {
|
|
||||||
element(by.linkText('Moby: Ch1')).click();
|
|
||||||
var content = element(by.css('.doc-example-live [ng-view]')).getText();
|
|
||||||
expect(content).toMatch(/controller\: ChapterCntl/);
|
|
||||||
expect(content).toMatch(/Book Id\: Moby/);
|
|
||||||
expect(content).toMatch(/Chapter Id\: 1/);
|
|
||||||
|
|
||||||
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/);
|
|
||||||
});
|
|
||||||
</file>
|
|
||||||
</example>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeStart
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @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.
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeSuccess
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @description
|
|
||||||
* Broadcasted after a route dependencies are resolved.
|
|
||||||
* {@link ngRoute.directive:ngView ngView} listens for the directive
|
|
||||||
* to instantiate the controller and render the view.
|
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeError
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeUpdate
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* The `reloadOnSearch` property has been set to false, and we are reusing the same
|
|
||||||
* instance of the Controller.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var forceReload = false,
|
|
||||||
$route = {
|
|
||||||
routes: routes,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$route#reload
|
|
||||||
* @methodOf ngRoute.$route
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Causes `$route` service to reload the current route even if
|
|
||||||
* {@link ng.$location $location} hasn't changed.
|
|
||||||
*
|
|
||||||
* As a result of that, {@link ngRoute.directive:ngView ngView}
|
|
||||||
* creates new scope, reinstantiates the controller.
|
|
||||||
*/
|
|
||||||
reload: function() {
|
|
||||||
forceReload = true;
|
|
||||||
$rootScope.$evalAsync(updateRoute);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.$on('$locationChangeSuccess', updateRoute);
|
|
||||||
|
|
||||||
return $route;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param on {string} current url
|
|
||||||
* @param route {Object} route regexp to match the url against
|
|
||||||
* @return {?Object}
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Check if the route matches the current url.
|
|
||||||
*
|
|
||||||
* Inspired by match in
|
|
||||||
* visionmedia/express/lib/router/router.js.
|
|
||||||
*/
|
|
||||||
function switchRouteMatcher(on, route) {
|
|
||||||
var keys = route.keys,
|
|
||||||
params = {};
|
|
||||||
|
|
||||||
if (!route.regexp) return null;
|
|
||||||
|
|
||||||
var m = route.regexp.exec(on);
|
|
||||||
if (!m) return null;
|
|
||||||
|
|
||||||
for (var i = 1, len = m.length; i < len; ++i) {
|
|
||||||
var key = keys[i - 1];
|
|
||||||
|
|
||||||
var val = 'string' == typeof m[i]
|
|
||||||
? decodeURIComponent(m[i])
|
|
||||||
: m[i];
|
|
||||||
|
|
||||||
if (key && val) {
|
|
||||||
params[key.name] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRoute() {
|
|
||||||
var next = parseRoute(),
|
|
||||||
last = $route.current;
|
|
||||||
|
|
||||||
if (next && last && next.$$route === last.$$route
|
|
||||||
&& angular.equals(next.pathParams, last.pathParams)
|
|
||||||
&& !next.reloadOnSearch && !forceReload) {
|
|
||||||
last.params = next.params;
|
|
||||||
angular.copy(last.params, $routeParams);
|
|
||||||
$rootScope.$broadcast('$routeUpdate', last);
|
|
||||||
} else if (next || last) {
|
|
||||||
forceReload = false;
|
|
||||||
$rootScope.$broadcast('$routeChangeStart', next, last);
|
|
||||||
$route.current = next;
|
|
||||||
if (next) {
|
|
||||||
if (next.redirectTo) {
|
|
||||||
if (angular.isString(next.redirectTo)) {
|
|
||||||
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
|
|
||||||
.replace();
|
|
||||||
} else {
|
|
||||||
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
|
|
||||||
.replace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$q.when(next).
|
|
||||||
then(function() {
|
|
||||||
if (next) {
|
|
||||||
var locals = angular.extend({}, next.resolve),
|
|
||||||
template, templateUrl;
|
|
||||||
|
|
||||||
angular.forEach(locals, function(value, key) {
|
|
||||||
locals[key] = angular.isString(value) ?
|
|
||||||
$injector.get(value) : $injector.invoke(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (angular.isDefined(template = next.template)) {
|
|
||||||
if (angular.isFunction(template)) {
|
|
||||||
template = template(next.params);
|
|
||||||
}
|
|
||||||
} else if (angular.isDefined(templateUrl = next.templateUrl)) {
|
|
||||||
if (angular.isFunction(templateUrl)) {
|
|
||||||
templateUrl = templateUrl(next.params);
|
|
||||||
}
|
|
||||||
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
|
|
||||||
if (angular.isDefined(templateUrl)) {
|
|
||||||
next.loadedTemplateUrl = templateUrl;
|
|
||||||
template = $http.get(templateUrl, {cache: $templateCache}).
|
|
||||||
then(function(response) { return response.data; });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (angular.isDefined(template)) {
|
|
||||||
locals['$template'] = template;
|
|
||||||
}
|
|
||||||
return $q.all(locals);
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
// after route change
|
|
||||||
then(function(locals) {
|
|
||||||
if (next == $route.current) {
|
|
||||||
if (next) {
|
|
||||||
next.locals = locals;
|
|
||||||
angular.copy(next.params, $routeParams);
|
|
||||||
}
|
|
||||||
$rootScope.$broadcast('$routeChangeSuccess', next, last);
|
|
||||||
}
|
|
||||||
}, function(error) {
|
|
||||||
if (next == $route.current) {
|
|
||||||
$rootScope.$broadcast('$routeChangeError', next, last, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns the current active route, by matching it against the URL
|
|
||||||
*/
|
|
||||||
function parseRoute() {
|
|
||||||
// Match a route
|
|
||||||
var params, match;
|
|
||||||
angular.forEach(routes, function(route, path) {
|
|
||||||
if (!match && (params = switchRouteMatcher($location.path(), route))) {
|
|
||||||
match = inherit(route, {
|
|
||||||
params: angular.extend({}, $location.search(), params),
|
|
||||||
pathParams: params});
|
|
||||||
match.$$route = route;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// No route matched; fallback to "otherwise" route
|
|
||||||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns interpolation of the redirect path with the parameters
|
|
||||||
*/
|
|
||||||
function interpolate(string, params) {
|
|
||||||
var result = [];
|
|
||||||
angular.forEach((string||'').split(':'), function(segment, i) {
|
|
||||||
if (i === 0) {
|
|
||||||
result.push(segment);
|
|
||||||
} else {
|
|
||||||
var segmentMatch = segment.match(/(\w+)(.*)/);
|
|
||||||
var key = segmentMatch[1];
|
|
||||||
result.push(params[key]);
|
|
||||||
result.push(segmentMatch[2] || '');
|
|
||||||
delete params[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result.join('');
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
ngRouteModule.provider('$routeParams', $RouteParamsProvider);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$routeParams
|
|
||||||
* @requires $route
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* The `$routeParams` service allows you to retrieve the current set of route parameters.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* The route parameters are a combination of {@link ng.$location `$location`}'s
|
|
||||||
* {@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.
|
|
||||||
*
|
|
||||||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged
|
|
||||||
* (but its properties will likely change) even when a route change occurs.
|
|
||||||
*
|
|
||||||
* Note that the `$routeParams` are only updated *after* a route change completes successfully.
|
|
||||||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions.
|
|
||||||
* Instead you can use `$route.current.params` to access the new route's parameters.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* <pre>
|
|
||||||
* // Given:
|
|
||||||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
|
|
||||||
* // Route: /Chapter/:chapterId/Section/:sectionId
|
|
||||||
* //
|
|
||||||
* // Then
|
|
||||||
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
function $RouteParamsProvider() {
|
|
||||||
this.$get = function() { return {}; };
|
|
||||||
}
|
|
||||||
|
|
||||||
ngRouteModule.directive('ngView', ngViewFactory);
|
|
||||||
ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc directive
|
|
||||||
* @name ngRoute.directive:ngView
|
|
||||||
* @restrict ECA
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* # Overview
|
|
||||||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
|
|
||||||
* including the rendered template of the current route into the main layout (`index.html`) file.
|
|
||||||
* Every time the current route changes, the included view changes with it according to the
|
|
||||||
* configuration of the `$route` service.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* @animations
|
|
||||||
* enter - animation is used to bring new content into the browser.
|
|
||||||
* leave - animation is used to animate existing content away.
|
|
||||||
*
|
|
||||||
* 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">
|
|
||||||
<div ng-controller="MainCntl as main">
|
|
||||||
Choose:
|
|
||||||
<a href="Book/Moby">Moby</a> |
|
|
||||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
|
||||||
<a href="Book/Gatsby">Gatsby</a> |
|
|
||||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
|
||||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
|
||||||
|
|
||||||
<div class="view-animate-container">
|
|
||||||
<div ng-view class="view-animate"></div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre>$location.path() = {{main.$location.path()}}</pre>
|
|
||||||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
|
|
||||||
<pre>$route.current.params = {{main.$route.current.params}}</pre>
|
|
||||||
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
|
|
||||||
<pre>$routeParams = {{main.$routeParams}}</pre>
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="book.html">
|
|
||||||
<div>
|
|
||||||
controller: {{book.name}}<br />
|
|
||||||
Book Id: {{book.params.bookId}}<br />
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="chapter.html">
|
|
||||||
<div>
|
|
||||||
controller: {{chapter.name}}<br />
|
|
||||||
Book Id: {{chapter.params.bookId}}<br />
|
|
||||||
Chapter Id: {{chapter.params.chapterId}}
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="animations.css">
|
|
||||||
.view-animate-container {
|
|
||||||
position:relative;
|
|
||||||
height:100px!important;
|
|
||||||
position:relative;
|
|
||||||
background:white;
|
|
||||||
border:1px solid black;
|
|
||||||
height:40px;
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate {
|
|
||||||
padding:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate.ng-enter, .view-animate.ng-leave {
|
|
||||||
-webkit-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;
|
|
||||||
width:100%;
|
|
||||||
border-left:1px solid black;
|
|
||||||
|
|
||||||
position:absolute;
|
|
||||||
top:0;
|
|
||||||
left:0;
|
|
||||||
right:0;
|
|
||||||
bottom:0;
|
|
||||||
padding:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate.ng-enter {
|
|
||||||
left:100%;
|
|
||||||
}
|
|
||||||
.view-animate.ng-enter.ng-enter-active {
|
|
||||||
left:0;
|
|
||||||
}
|
|
||||||
.view-animate.ng-leave.ng-leave-active {
|
|
||||||
left:-100%;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="script.js">
|
|
||||||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'],
|
|
||||||
function($routeProvider, $locationProvider) {
|
|
||||||
$routeProvider.when('/Book/:bookId', {
|
|
||||||
templateUrl: 'book.html',
|
|
||||||
controller: BookCntl,
|
|
||||||
controllerAs: 'book'
|
|
||||||
});
|
|
||||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
|
|
||||||
templateUrl: 'chapter.html',
|
|
||||||
controller: ChapterCntl,
|
|
||||||
controllerAs: 'chapter'
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure html5 to get links working on jsfiddle
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
function MainCntl($route, $routeParams, $location) {
|
|
||||||
this.$route = $route;
|
|
||||||
this.$location = $location;
|
|
||||||
this.$routeParams = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookCntl($routeParams) {
|
|
||||||
this.name = "BookCntl";
|
|
||||||
this.params = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChapterCntl($routeParams) {
|
|
||||||
this.name = "ChapterCntl";
|
|
||||||
this.params = $routeParams;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="protractorTest.js">
|
|
||||||
it('should load and compile correct template', function() {
|
|
||||||
element(by.linkText('Moby: Ch1')).click();
|
|
||||||
var content = element(by.css('.doc-example-live [ng-view]')).getText();
|
|
||||||
expect(content).toMatch(/controller\: ChapterCntl/);
|
|
||||||
expect(content).toMatch(/Book Id\: Moby/);
|
|
||||||
expect(content).toMatch(/Chapter Id\: 1/);
|
|
||||||
|
|
||||||
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/);
|
|
||||||
});
|
|
||||||
</file>
|
|
||||||
</example>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.directive:ngView#$viewContentLoaded
|
|
||||||
* @eventOf ngRoute.directive:ngView
|
|
||||||
* @eventType emit on the current ngView scope
|
|
||||||
* @description
|
|
||||||
* Emitted every time the ngView content is reloaded.
|
|
||||||
*/
|
|
||||||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
|
|
||||||
function ngViewFactory( $route, $anchorScroll, $animate) {
|
|
||||||
return {
|
|
||||||
restrict: 'ECA',
|
|
||||||
terminal: true,
|
|
||||||
priority: 400,
|
|
||||||
transclude: 'element',
|
|
||||||
link: function(scope, $element, attr, ctrl, $transclude) {
|
|
||||||
var currentScope,
|
|
||||||
currentElement,
|
|
||||||
autoScrollExp = attr.autoscroll,
|
|
||||||
onloadExp = attr.onload || '';
|
|
||||||
|
|
||||||
scope.$on('$routeChangeSuccess', update);
|
|
||||||
update();
|
|
||||||
|
|
||||||
function cleanupLastView() {
|
|
||||||
if (currentScope) {
|
|
||||||
currentScope.$destroy();
|
|
||||||
currentScope = null;
|
|
||||||
}
|
|
||||||
if(currentElement) {
|
|
||||||
$animate.leave(currentElement);
|
|
||||||
currentElement = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var locals = $route.current && $route.current.locals,
|
|
||||||
template = locals && locals.$template;
|
|
||||||
|
|
||||||
if (angular.isDefined(template)) {
|
|
||||||
var newScope = scope.$new();
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
currentElement = clone;
|
|
||||||
currentScope = current.scope = newScope;
|
|
||||||
currentScope.$emit('$viewContentLoaded');
|
|
||||||
currentScope.$eval(onloadExp);
|
|
||||||
} else {
|
|
||||||
cleanupLastView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
$element.html(locals.$template);
|
|
||||||
|
|
||||||
var link = $compile($element.contents());
|
|
||||||
|
|
||||||
if (current.controller) {
|
|
||||||
locals.$scope = scope;
|
|
||||||
var controller = $controller(current.controller, locals);
|
|
||||||
if (current.controllerAs) {
|
|
||||||
scope[current.controllerAs] = controller;
|
|
||||||
}
|
|
||||||
$element.data('$ngControllerController', controller);
|
|
||||||
$element.children().data('$ngControllerController', controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
link(scope);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,642 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngSanitize
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngSanitize
|
|
||||||
*
|
|
||||||
* The `ngSanitize` module provides functionality to sanitize HTML.
|
|
||||||
*
|
|
||||||
* {@installModule sanitize}
|
|
||||||
*
|
|
||||||
* <div doc-module-components="ngSanitize"></div>
|
|
||||||
*
|
|
||||||
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* HTML Parser By Misko Hevery (misko@hevery.com)
|
|
||||||
* based on: HTML Parser By John Resig (ejohn.org)
|
|
||||||
* Original code by Erik Arvidsson, Mozilla Public License
|
|
||||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
|
||||||
*
|
|
||||||
* // Use like so:
|
|
||||||
* htmlParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc service
|
|
||||||
* @name ngSanitize.$sanitize
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
|
||||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example module="ngSanitize">
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function Ctrl($scope, $sce) {
|
|
||||||
$scope.snippet =
|
|
||||||
'<p style="color:blue">an html\n' +
|
|
||||||
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
|
|
||||||
'snippet</p>';
|
|
||||||
$scope.deliberatelyTrustDangerousSnippet = function() {
|
|
||||||
return $sce.trustAsHtml($scope.snippet);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div ng-controller="Ctrl">
|
|
||||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Directive</td>
|
|
||||||
<td>How</td>
|
|
||||||
<td>Source</td>
|
|
||||||
<td>Rendered</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="bind-html-with-sanitize">
|
|
||||||
<td>ng-bind-html</td>
|
|
||||||
<td>Automatically uses $sanitize</td>
|
|
||||||
<td><pre><div ng-bind-html="snippet"><br/></div></pre></td>
|
|
||||||
<td><div ng-bind-html="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
<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()">
|
|
||||||
</div></pre>
|
|
||||||
</td>
|
|
||||||
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="bind-default">
|
|
||||||
<td>ng-bind</td>
|
|
||||||
<td>Automatically escapes</td>
|
|
||||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
|
||||||
<td><div ng-bind="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</doc:source>
|
|
||||||
<doc:protractor>
|
|
||||||
it('should sanitize the html snippet by default', function() {
|
|
||||||
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
|
|
||||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should inline raw snippet if bound to a trusted value', function() {
|
|
||||||
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
|
|
||||||
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(element(by.css('#bind-default div')).getInnerHtml()).
|
|
||||||
toBe("<p style=\"color:blue\">an html\n" +
|
|
||||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
|
||||||
"snippet</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update', function() {
|
|
||||||
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:protractor>
|
|
||||||
</doc:example>
|
|
||||||
*/
|
|
||||||
function $SanitizeProvider() {
|
|
||||||
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
|
|
||||||
return function(html) {
|
|
||||||
var 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*>/,
|
|
||||||
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,
|
|
||||||
// Match everything outside of normal chars and " (quote character)
|
|
||||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
|
|
||||||
|
|
||||||
|
|
||||||
// Good source of info about elements and attributes
|
|
||||||
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
|
||||||
// http://simon.html5.org/html-elements
|
|
||||||
|
|
||||||
// Safe Void Elements - HTML5
|
|
||||||
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
|
||||||
var voidElements = makeMap("area,br,col,hr,img,wbr");
|
|
||||||
|
|
||||||
// Elements that you can, intentionally, leave open (and which close themselves)
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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"));
|
|
||||||
|
|
||||||
// Inline Elements - HTML5
|
|
||||||
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
|
|
||||||
"bdi,bdo,big,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"));
|
|
||||||
|
|
||||||
|
|
||||||
// Special Elements (can contain anything)
|
|
||||||
var specialElements = makeMap("script,style");
|
|
||||||
|
|
||||||
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");
|
|
||||||
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,size,span,start,summary,target,title,type,'+
|
|
||||||
'valign,value,vspace,width'));
|
|
||||||
|
|
||||||
function makeMap(str) {
|
|
||||||
var obj = {}, items = str.split(','), i;
|
|
||||||
for (i = 0; i < items.length; i++) obj[items[i]] = true;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example
|
|
||||||
* htmlParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {string} html string
|
|
||||||
* @param {object} handler
|
|
||||||
*/
|
|
||||||
function htmlParser( html, handler ) {
|
|
||||||
var index, chars, match, stack = [], last = html;
|
|
||||||
stack.last = function() { return stack[ stack.length - 1 ]; };
|
|
||||||
|
|
||||||
while ( html ) {
|
|
||||||
chars = true;
|
|
||||||
|
|
||||||
// Make sure we're not in a script or style element
|
|
||||||
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
|
||||||
|
|
||||||
// Comment
|
|
||||||
if ( html.indexOf("<!--") === 0 ) {
|
|
||||||
// comments containing -- are not allowed unless they terminate the comment
|
|
||||||
index = html.indexOf("--", 4);
|
|
||||||
|
|
||||||
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 );
|
|
||||||
|
|
||||||
if ( match ) {
|
|
||||||
html = html.substring( match[0].length );
|
|
||||||
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start tag
|
|
||||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
|
||||||
match = html.match( START_TAG_REGEXP );
|
|
||||||
|
|
||||||
if ( match ) {
|
|
||||||
html = html.substring( match[0].length );
|
|
||||||
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( chars ) {
|
|
||||||
index = html.indexOf("<");
|
|
||||||
|
|
||||||
var text = index < 0 ? html : html.substring( 0, index );
|
|
||||||
html = index < 0 ? "" : html.substring( index );
|
|
||||||
|
|
||||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
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) );
|
|
||||||
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
|
|
||||||
parseEndTag( "", stack.last() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( html == last ) {
|
|
||||||
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
|
|
||||||
"of html: {0}", html);
|
|
||||||
}
|
|
||||||
last = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any remaining tags
|
|
||||||
parseEndTag();
|
|
||||||
|
|
||||||
function parseStartTag( tag, tagName, rest, unary ) {
|
|
||||||
tagName = angular.lowercase(tagName);
|
|
||||||
if ( blockElements[ tagName ] ) {
|
|
||||||
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
|
||||||
parseEndTag( "", stack.last() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
|
|
||||||
parseEndTag( "", tagName );
|
|
||||||
}
|
|
||||||
|
|
||||||
unary = voidElements[ tagName ] || !!unary;
|
|
||||||
|
|
||||||
if ( !unary )
|
|
||||||
stack.push( tagName );
|
|
||||||
|
|
||||||
var attrs = {};
|
|
||||||
|
|
||||||
rest.replace(ATTR_REGEXP,
|
|
||||||
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
|
|
||||||
var value = doubleQuotedValue
|
|
||||||
|| singleQuotedValue
|
|
||||||
|| unquotedValue
|
|
||||||
|| '';
|
|
||||||
|
|
||||||
attrs[name] = decodeEntities(value);
|
|
||||||
});
|
|
||||||
if (handler.start) handler.start( tagName, attrs, unary );
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEndTag( tag, tagName ) {
|
|
||||||
var pos = 0, i;
|
|
||||||
tagName = angular.lowercase(tagName);
|
|
||||||
if ( tagName )
|
|
||||||
// Find the closest opened tag of the same type
|
|
||||||
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
|
||||||
if ( stack[ pos ] == tagName )
|
|
||||||
break;
|
|
||||||
|
|
||||||
if ( pos >= 0 ) {
|
|
||||||
// Close all the open elements, up the stack
|
|
||||||
for ( i = stack.length - 1; i >= pos; i-- )
|
|
||||||
if (handler.end) handler.end( stack[ i ] );
|
|
||||||
|
|
||||||
// Remove the open elements from the stack
|
|
||||||
stack.length = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
function decodeEntities(value) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes all potentially dangerous characters, so that the
|
|
||||||
* resulting string can be safely inserted into attribute or
|
|
||||||
* element text.
|
|
||||||
* @param value
|
|
||||||
* @returns escaped text
|
|
||||||
*/
|
|
||||||
function encodeEntities(value) {
|
|
||||||
return value.
|
|
||||||
replace(/&/g, '&').
|
|
||||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
|
||||||
return '&#' + value.charCodeAt(0) + ';';
|
|
||||||
}).
|
|
||||||
replace(/</g, '<').
|
|
||||||
replace(/>/g, '>');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create an HTML/XML writer which writes to buffer
|
|
||||||
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
|
||||||
* @returns {object} in the form of {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
function htmlSanitizeWriter(buf, uriValidator){
|
|
||||||
var ignore = false;
|
|
||||||
var out = angular.bind(buf, buf.push);
|
|
||||||
return {
|
|
||||||
start: function(tag, attrs, unary){
|
|
||||||
tag = angular.lowercase(tag);
|
|
||||||
if (!ignore && specialElements[tag]) {
|
|
||||||
ignore = tag;
|
|
||||||
}
|
|
||||||
if (!ignore && validElements[tag] === true) {
|
|
||||||
out('<');
|
|
||||||
out(tag);
|
|
||||||
angular.forEach(attrs, function(value, key){
|
|
||||||
var lkey=angular.lowercase(key);
|
|
||||||
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
|
|
||||||
if (validAttrs[lkey] === true &&
|
|
||||||
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
|
|
||||||
out(' ');
|
|
||||||
out(key);
|
|
||||||
out('="');
|
|
||||||
out(encodeEntities(value));
|
|
||||||
out('"');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
out(unary ? '/>' : '>');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end: function(tag){
|
|
||||||
tag = angular.lowercase(tag);
|
|
||||||
if (!ignore && validElements[tag] === true) {
|
|
||||||
out('</');
|
|
||||||
out(tag);
|
|
||||||
out('>');
|
|
||||||
}
|
|
||||||
if (tag == ignore) {
|
|
||||||
ignore = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chars: function(chars){
|
|
||||||
if (!ignore) {
|
|
||||||
out(encodeEntities(chars));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// define ngSanitize module and register $sanitize service
|
|
||||||
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
||||||
|
|
||||||
/* global sanitizeText: false */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc filter
|
|
||||||
* @name ngSanitize.filter:linky
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
|
||||||
* plain email address links.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
|
|
||||||
*
|
|
||||||
* @param {string} text Input text.
|
|
||||||
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
|
|
||||||
* @returns {string} Html-linkified text.
|
|
||||||
*
|
|
||||||
* @usage
|
|
||||||
<span ng-bind-html="linky_expression | linky"></span>
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example module="ngSanitize">
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function Ctrl($scope) {
|
|
||||||
$scope.snippet =
|
|
||||||
'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/.';
|
|
||||||
$scope.snippetWithTarget = 'http://angularjs.org/';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div ng-controller="Ctrl">
|
|
||||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Filter</td>
|
|
||||||
<td>Source</td>
|
|
||||||
<td>Rendered</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="linky-filter">
|
|
||||||
<td>linky filter</td>
|
|
||||||
<td>
|
|
||||||
<pre><div ng-bind-html="snippet | linky"><br></div></pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div ng-bind-html="snippet | linky"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="linky-target">
|
|
||||||
<td>linky target</td>
|
|
||||||
<td>
|
|
||||||
<pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="escaped-html">
|
|
||||||
<td>no filter</td>
|
|
||||||
<td><pre><div ng-bind="snippet"><br></div></pre></td>
|
|
||||||
<td><div ng-bind="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</doc:source>
|
|
||||||
<doc:protractor>
|
|
||||||
it('should linkify the snippet with urls', function() {
|
|
||||||
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(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() {
|
|
||||||
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(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:protractor>
|
|
||||||
</doc:example>
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
if (!text) return text;
|
|
||||||
var match;
|
|
||||||
var raw = text;
|
|
||||||
var html = [];
|
|
||||||
var url;
|
|
||||||
var i;
|
|
||||||
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;
|
|
||||||
addText(raw.substr(0, i));
|
|
||||||
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
|
|
||||||
raw = raw.substring(i + match[0].length);
|
|
||||||
}
|
|
||||||
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);
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for jstd scenario adapter
|
|
||||||
*/
|
|
||||||
var jstdScenarioAdapter = {
|
|
||||||
relativeUrlPrefix: '/build/docs/'
|
|
||||||
};
|
|
|
@ -1,202 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.5
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSTestDriver adapter for angular scenario tests
|
|
||||||
*
|
|
||||||
* Example of jsTestDriver.conf for running scenario tests with JSTD:
|
|
||||||
<pre>
|
|
||||||
server: http://localhost:9877
|
|
||||||
|
|
||||||
load:
|
|
||||||
- lib/angular-scenario.js
|
|
||||||
- lib/jstd-scenario-adapter-config.js
|
|
||||||
- lib/jstd-scenario-adapter.js
|
|
||||||
# your test files go here #
|
|
||||||
|
|
||||||
proxy:
|
|
||||||
- {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
|
|
||||||
* Note the order of files - it's important !
|
|
||||||
*
|
|
||||||
* Example of jstd-scenario-adapter-config.js
|
|
||||||
<pre>
|
|
||||||
var jstdScenarioAdapter = {
|
|
||||||
relativeUrlPrefix: '/your-prefix/'
|
|
||||||
};
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
|
|
||||||
* You have to configure this to work together with JSTD proxy.
|
|
||||||
*
|
|
||||||
* Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
|
|
||||||
* Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
|
|
||||||
* That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom type of test case
|
|
||||||
*
|
|
||||||
* @const
|
|
||||||
* @see jstestdriver.TestCaseInfo
|
|
||||||
*/
|
|
||||||
var SCENARIO_TYPE = 'scenario';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin for JSTestDriver
|
|
||||||
* Connection point between scenario's jstd output and jstestdriver.
|
|
||||||
*
|
|
||||||
* @see jstestdriver.PluginRegistrar
|
|
||||||
*/
|
|
||||||
function JstdPlugin() {
|
|
||||||
var nop = function() {};
|
|
||||||
|
|
||||||
this.reportResult = nop;
|
|
||||||
this.reportEnd = nop;
|
|
||||||
this.runScenario = nop;
|
|
||||||
|
|
||||||
this.name = 'Angular Scenario Adapter';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called for each JSTD TestCase
|
|
||||||
*
|
|
||||||
* Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
|
|
||||||
* Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
|
|
||||||
*
|
|
||||||
* @param {jstestdriver.TestRunConfiguration} configuration
|
|
||||||
* @param {Function} onTestDone
|
|
||||||
* @param {Function} onAllTestsComplete
|
|
||||||
* @returns {boolean} True if this type of test is handled by this plugin, false otherwise
|
|
||||||
*/
|
|
||||||
this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
|
|
||||||
if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
|
|
||||||
|
|
||||||
this.reportResult = onTestDone;
|
|
||||||
this.reportEnd = onAllTestsComplete;
|
|
||||||
this.runScenario();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
|
|
||||||
testRunsConfiguration.push(
|
|
||||||
new jstestdriver.TestRunConfiguration(
|
|
||||||
new jstestdriver.TestCaseInfo(
|
|
||||||
'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton instance of the plugin
|
|
||||||
* Accessed using closure by:
|
|
||||||
* - jstd output (reports to this plugin)
|
|
||||||
* - initScenarioAdapter (register the plugin to jstd)
|
|
||||||
*/
|
|
||||||
var plugin = new JstdPlugin();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise scenario jstd-adapter
|
|
||||||
* (only if jstestdriver is defined)
|
|
||||||
*
|
|
||||||
* @param {Object} jstestdriver Undefined when run from browser (without jstd)
|
|
||||||
* @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
|
|
||||||
* @param {Object=} config Configuration object, supported properties:
|
|
||||||
* - relativeUrlPrefix: prefix for all relative links when navigateTo()
|
|
||||||
*/
|
|
||||||
function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
|
|
||||||
if (jstestdriver) {
|
|
||||||
// create and register ScenarioPlugin
|
|
||||||
jstestdriver.pluginRegistrar.register(plugin);
|
|
||||||
plugin.runScenario = initScenarioAndRun;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HACK (angular.scenario.Application.navigateTo)
|
|
||||||
*
|
|
||||||
* We need to navigate to relative urls when running from browser (without JSTD),
|
|
||||||
* because we want to allow running scenario tests without creating its own virtual host.
|
|
||||||
* For example: http://angular.local/build/docs/docs-scenario.html
|
|
||||||
*
|
|
||||||
* On the other hand, when running with JSTD, we need to navigate to absolute urls,
|
|
||||||
* because of JSTD proxy. (proxy, because of same domain policy)
|
|
||||||
*
|
|
||||||
* So this hack is applied only if running with JSTD and change all relative urls to absolute.
|
|
||||||
*/
|
|
||||||
var appProto = angular.scenario.Application.prototype,
|
|
||||||
navigateTo = appProto.navigateTo,
|
|
||||||
relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
|
|
||||||
|
|
||||||
appProto.navigateTo = function(url, loadFn, errorFn) {
|
|
||||||
if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
|
|
||||||
url != 'about:blank' && !url.match(/^https?/)) {
|
|
||||||
url = relativeUrlPrefix + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return navigateTo.call(this, url, loadFn, errorFn);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds proper TestResult object from given model spec
|
|
||||||
*
|
|
||||||
* TODO(vojta) report error details
|
|
||||||
*
|
|
||||||
* @param {angular.scenario.ObjectModel.Spec} spec
|
|
||||||
* @returns {jstestdriver.TestResult}
|
|
||||||
*/
|
|
||||||
function createTestResultFromSpec(spec) {
|
|
||||||
var map = {
|
|
||||||
success: 'PASSED',
|
|
||||||
error: 'ERROR',
|
|
||||||
failure: 'FAILED'
|
|
||||||
};
|
|
||||||
|
|
||||||
return new jstestdriver.TestResult(
|
|
||||||
spec.fullDefinitionName,
|
|
||||||
spec.name,
|
|
||||||
jstestdriver.TestResult.RESULT[map[spec.status]],
|
|
||||||
spec.error || '',
|
|
||||||
spec.line || '',
|
|
||||||
spec.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates JSTD output (jstestdriver.TestResult)
|
|
||||||
*/
|
|
||||||
angular.scenario.output('jstd', function(context, runner, model) {
|
|
||||||
model.on('SpecEnd', function(spec) {
|
|
||||||
plugin.reportResult(createTestResultFromSpec(spec));
|
|
||||||
});
|
|
||||||
|
|
||||||
model.on('RunnerEnd', function() {
|
|
||||||
plugin.reportEnd();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
|
|
||||||
})(window);
|
|
|
@ -1,239 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhanced Select2 Dropmenus
|
|
||||||
*
|
|
||||||
* @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
|
|
||||||
* This change is so that you do not have to do an additional query yourself on top of Select2's own query
|
|
||||||
* @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
|
|
||||||
*/
|
|
||||||
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
|
|
||||||
var options = {};
|
|
||||||
if (uiSelect2Config) {
|
|
||||||
angular.extend(options, uiSelect2Config);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
require: 'ngModel',
|
|
||||||
priority: 1,
|
|
||||||
compile: function (tElm, tAttrs) {
|
|
||||||
var watch,
|
|
||||||
repeatOption,
|
|
||||||
repeatAttr,
|
|
||||||
isSelect = tElm.is('select'),
|
|
||||||
isMultiple = angular.isDefined(tAttrs.multiple);
|
|
||||||
|
|
||||||
// Enable watching of the options dataset if in use
|
|
||||||
if (tElm.is('select')) {
|
|
||||||
repeatOption = tElm.find( 'optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]');
|
|
||||||
|
|
||||||
if (repeatOption.length) {
|
|
||||||
repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
|
|
||||||
watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function (scope, elm, attrs, controller) {
|
|
||||||
// instance-specific options
|
|
||||||
var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert from Select2 view-model to Angular view-model.
|
|
||||||
*/
|
|
||||||
var convertToAngularModel = function(select2_data) {
|
|
||||||
var model;
|
|
||||||
if (opts.simple_tags) {
|
|
||||||
model = [];
|
|
||||||
angular.forEach(select2_data, function(value, index) {
|
|
||||||
model.push(value.id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
model = select2_data;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert from Angular view-model to Select2 view-model.
|
|
||||||
*/
|
|
||||||
var convertToSelect2Model = function(angular_data) {
|
|
||||||
var model = [];
|
|
||||||
if (!angular_data) {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.simple_tags) {
|
|
||||||
model = [];
|
|
||||||
angular.forEach(
|
|
||||||
angular_data,
|
|
||||||
function(value, index) {
|
|
||||||
model.push({'id': value, 'text': value});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
model = angular_data;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isSelect) {
|
|
||||||
// Use <select multiple> instead
|
|
||||||
delete opts.multiple;
|
|
||||||
delete opts.initSelection;
|
|
||||||
} else if (isMultiple) {
|
|
||||||
opts.multiple = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller) {
|
|
||||||
// Watch the model for programmatic changes
|
|
||||||
scope.$watch(tAttrs.ngModel, function(current, old) {
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (current === old) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
controller.$render();
|
|
||||||
}, true);
|
|
||||||
controller.$render = function () {
|
|
||||||
if (isSelect) {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
} else {
|
|
||||||
if (opts.multiple) {
|
|
||||||
var viewValue = controller.$viewValue;
|
|
||||||
if (angular.isString(viewValue)) {
|
|
||||||
viewValue = viewValue.split(',');
|
|
||||||
}
|
|
||||||
elm.select2(
|
|
||||||
'data', convertToSelect2Model(viewValue));
|
|
||||||
} else {
|
|
||||||
if (angular.isObject(controller.$viewValue)) {
|
|
||||||
elm.select2('data', controller.$viewValue);
|
|
||||||
} else if (!controller.$viewValue) {
|
|
||||||
elm.select2('data', null);
|
|
||||||
} else {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Watch the options dataset for changes
|
|
||||||
if (watch) {
|
|
||||||
scope.$watch(watch, function (newVal, oldVal, scope) {
|
|
||||||
if (angular.equals(newVal, oldVal)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Delayed so that the options have time to be rendered
|
|
||||||
$timeout(function () {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
// Refresh angular to remove the superfluous option
|
|
||||||
elm.trigger('change');
|
|
||||||
if(newVal && !oldVal && controller.$setPristine) {
|
|
||||||
controller.$setPristine(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update valid and dirty statuses
|
|
||||||
controller.$parsers.push(function (value) {
|
|
||||||
var div = elm.prev();
|
|
||||||
div
|
|
||||||
.toggleClass('ng-invalid', !controller.$valid)
|
|
||||||
.toggleClass('ng-valid', controller.$valid)
|
|
||||||
.toggleClass('ng-invalid-required', !controller.$valid)
|
|
||||||
.toggleClass('ng-valid-required', controller.$valid)
|
|
||||||
.toggleClass('ng-dirty', controller.$dirty)
|
|
||||||
.toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isSelect) {
|
|
||||||
// Set the view and model value and update the angular template manually for the ajax/multiple select2.
|
|
||||||
elm.bind("change", function (e) {
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
|
|
||||||
if (scope.$$phase || scope.$root.$$phase) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scope.$apply(function () {
|
|
||||||
controller.$setViewValue(
|
|
||||||
convertToAngularModel(elm.select2('data')));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.initSelection) {
|
|
||||||
var initSelection = opts.initSelection;
|
|
||||||
opts.initSelection = function (element, callback) {
|
|
||||||
initSelection(element, function (value) {
|
|
||||||
var isPristine = controller.$pristine;
|
|
||||||
controller.$setViewValue(convertToAngularModel(value));
|
|
||||||
callback(value);
|
|
||||||
if (isPristine) {
|
|
||||||
controller.$setPristine();
|
|
||||||
}
|
|
||||||
elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elm.bind("$destroy", function() {
|
|
||||||
elm.select2("destroy");
|
|
||||||
});
|
|
||||||
|
|
||||||
attrs.$observe('disabled', function (value) {
|
|
||||||
elm.select2('enable', !value);
|
|
||||||
});
|
|
||||||
|
|
||||||
attrs.$observe('readonly', function (value) {
|
|
||||||
elm.select2('readonly', !!value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (attrs.ngMultiple) {
|
|
||||||
scope.$watch(attrs.ngMultiple, function(newVal) {
|
|
||||||
attrs.$set('multiple', !!newVal);
|
|
||||||
elm.select2(opts);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the plugin late so that the injected DOM does not disrupt the template compiler
|
|
||||||
$timeout(function () {
|
|
||||||
elm.select2(opts);
|
|
||||||
|
|
||||||
// Set initial value - I'm not sure about this but it seems to need to be there
|
|
||||||
elm.select2('data', controller.$modelValue);
|
|
||||||
// important!
|
|
||||||
controller.$render();
|
|
||||||
|
|
||||||
// Not sure if I should just check for !isSelect OR if I should check for 'tags' key
|
|
||||||
if (!opts.initSelection && !isSelect) {
|
|
||||||
var isPristine = controller.$pristine;
|
|
||||||
controller.$setViewValue(
|
|
||||||
convertToAngularModel(elm.select2('data'))
|
|
||||||
);
|
|
||||||
if (isPristine) {
|
|
||||||
controller.$setPristine();
|
|
||||||
}
|
|
||||||
elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
||||||
{"full":"1.0.7","major":"1","minor":"0","dot":"7","codename":"monochromatic-rainbow","cdn":"1.0.6"}
|
|
|
@ -1 +0,0 @@
|
||||||
1.0.7
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"realm" : "facebook-identity-provider-realm",
|
|
||||||
"resource" : "facebook-authentication",
|
|
||||||
"auth-server-url": "/auth",
|
|
||||||
"ssl-required" : "external",
|
|
||||||
"public-client" : true
|
|
||||||
}
|
|
|
@ -1,183 +0,0 @@
|
||||||
# Keycloak Broker: Google Social Identity Provider Quickstarts
|
|
||||||
|
|
||||||
What is it?
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This example demonstrates how to use Social Identity Providers with Keycloak to authenticate users. In this case,
|
|
||||||
users are authenticated with Google using Keycloak Identity Broker capabilities using the oAuth 2 protocol.
|
|
||||||
|
|
||||||
From this example, you'll learn how to:
|
|
||||||
|
|
||||||
* Set up a social identity provider for a specific realm
|
|
||||||
* Store tokens from a social identity provider and use these tokens to invoke the social provider API
|
|
||||||
|
|
||||||
Basically, once you try to access the application for the first time, you'll be redirected to Keycloak's login page.
|
|
||||||
In this page you'll note that there is a "Google" button that allows you to authenticate with Google Identity Provider.
|
|
||||||
|
|
||||||
After clicking the "Google" button, you'll be redirected to Google's login page from where you must authenticate
|
|
||||||
and grant the necessary permissions to Keycloak in order to access your personal information from Google.
|
|
||||||
|
|
||||||
If everything is fine, Google will redirect you back to Keycloak and at this point you'll be asked to provide some
|
|
||||||
basic profile information in order to create a new user in Keycloak based on your social account. Once you update your profile,
|
|
||||||
you'll be authenticated and redirected to the application.
|
|
||||||
|
|
||||||
Basically, what the application does is obtain some basic information for the authenticated user and also allow users to
|
|
||||||
load their profile from Google. For that, this application demonstrates how to retrieve the token issued by a social provider
|
|
||||||
for the authenticated user and use this token to invoke Google's API.
|
|
||||||
|
|
||||||
Make sure you've set up an application in Google
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
This example application requires you to create a Google Application. How to create it is beyond the scope of this
|
|
||||||
documentation.
|
|
||||||
|
|
||||||
Please take a look on [Google Developer Console](https://cloud.google.com/console/project) for more details.
|
|
||||||
|
|
||||||
Once you have a Google Application configured, you need to obtain both **Client ID** and **Client Secret** and update the
|
|
||||||
**google-identity-provider-realm.json** configuration file with these information. There you'll find a section as follows:
|
|
||||||
|
|
||||||
"identityProviders": [
|
|
||||||
{
|
|
||||||
"id" : "google",
|
|
||||||
"providerId" : "google",
|
|
||||||
"name" : "Google",
|
|
||||||
"enabled": true,
|
|
||||||
"updateProfileFirstLogin" : "true",
|
|
||||||
"storeToken" : "true",
|
|
||||||
"config": {
|
|
||||||
"clientId": "CHANGE_CLIENT_ID",
|
|
||||||
"clientSecret": "CHANGE_CLIENT_SECRET"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Please, update both *clientId* and *clientSecret* configuration options with the **Client ID** and **Client Secret**.
|
|
||||||
|
|
||||||
Make sure to use the correct redirect URI to be used as URL on Google. The Google will redirect to this URI after finish authentication. For this example, it's the URL
|
|
||||||
[http://localhost:8080/auth/realms/google-identity-provider-realm/broker/google/endpoint](http://localhost:8080/auth/realms/google-identity-provider-realm/broker/google/endpoint) .
|
|
||||||
You can also determine the redirect URI from Keycloak admin console (It's in Identity provider settings for Google provider).
|
|
||||||
|
|
||||||
Make sure you've set up the Keycloak Server
|
|
||||||
--------------------------------------
|
|
||||||
The Keycloak Appliance Distribution comes with a preconfigured Keycloak server (based on Wildfly). You can use it out of
|
|
||||||
the box to run these demos. So, if you're using this, you can head to Step 2.
|
|
||||||
|
|
||||||
Alternatively, you can install the Keycloak Server onto any EAP 6.x, or Wildfly 8.x server, but there is
|
|
||||||
a few steps you must follow.
|
|
||||||
|
|
||||||
Obtain latest keycloak-war-dist-all.zip. This distro is used to install Keycloak onto an existing JBoss installation.
|
|
||||||
This installs the server.
|
|
||||||
|
|
||||||
$ cd ${wildfly.jboss.home}/standalone
|
|
||||||
$ cp -r ${keycloak-war-dist-all}/deployments .
|
|
||||||
|
|
||||||
To be able to run the demos you also need to install the Keycloak client adapter. For Wildfly:
|
|
||||||
|
|
||||||
$ cd ${wildfly.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-wildfly-adapter-dist.zip
|
|
||||||
|
|
||||||
For JBoss EAP 6.x
|
|
||||||
|
|
||||||
$ cd ${eap.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-eap6-adapter-dist.zip
|
|
||||||
|
|
||||||
For JBoss AS 7.1.1:
|
|
||||||
|
|
||||||
$ cd ${as7.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-as7-adapter-dist.zip
|
|
||||||
|
|
||||||
Unzipping the adapter ZIP only installs the JAR files. You must also add the Keycloak Subsystem to the server's
|
|
||||||
configuration (standalone/configuration/standalone.xml).
|
|
||||||
|
|
||||||
<server xmlns="urn:jboss:domain:1.4">
|
|
||||||
|
|
||||||
<extensions>
|
|
||||||
<extension module="org.keycloak.keycloak-subsystem"/>
|
|
||||||
...
|
|
||||||
</extensions>
|
|
||||||
|
|
||||||
<profile>
|
|
||||||
<subsystem xmlns="urn:jboss:domain:keycloak:1.0"/>
|
|
||||||
...
|
|
||||||
</profile>
|
|
||||||
|
|
||||||
Boot Keycloak Server
|
|
||||||
---------------------------------------
|
|
||||||
Where you go to start up the Keycloak Server depends on which distro you installed.
|
|
||||||
|
|
||||||
From appliance:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd keycloak/bin
|
|
||||||
$ ./standalone.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
From existing Wildfly/EAP6/AS7 distro
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd ${wildfly.jboss.home}/bin
|
|
||||||
$ ./standalone.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Import the Test Realm
|
|
||||||
---------------------------------------
|
|
||||||
Next thing you have to do is import the test realm for the demo. Clicking on the below link will bring you to the
|
|
||||||
create realm page in the Admin UI. The username/password is admin/admin to login in. Keycloak will ask you to
|
|
||||||
create a new admin password before you can go to the create realm page.
|
|
||||||
|
|
||||||
[http://localhost:8080/auth/admin/master/console/#/create/realm](http://localhost:8080/auth/admin/master/console/#/create/realm)
|
|
||||||
|
|
||||||
Import the **google-identity-provider-realm.json** file that is in the google-authentication/ example directory.
|
|
||||||
|
|
||||||
|
|
||||||
Start JBoss Enterprise Application Platform 6 or WildFly with the Web Profile
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
1. Open a command line and navigate to the root of the JBoss server directory.
|
|
||||||
2. The following shows the command line to start the server with the web profile:
|
|
||||||
|
|
||||||
For Linux: JBOSS_HOME/bin/standalone.sh
|
|
||||||
For Windows: JBOSS_HOME\bin\standalone.bat
|
|
||||||
|
|
||||||
|
|
||||||
Build and Deploy the Quickstart
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
_NOTE: The following build command assumes you have configured your Maven user settings. If you have not, you must include Maven setting arguments on the command line. See [Build and Deploy the Quickstarts](../README.md#build-and-deploy-the-quickstarts) for complete instructions and additional options._
|
|
||||||
|
|
||||||
1. Make sure you have started the JBoss Server as described above.
|
|
||||||
2. Open a command line and navigate to the root directory of this quickstart.
|
|
||||||
3. Type this command to build and deploy the archive:
|
|
||||||
|
|
||||||
For EAP 6: mvn clean package jboss-as:deploy
|
|
||||||
For WildFly: mvn -Pwildfly clean package wildfly:deploy
|
|
||||||
|
|
||||||
4. This will deploy `target/google-authentication.war` to the running instance of the server.
|
|
||||||
|
|
||||||
|
|
||||||
Access the application
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The application will be running at the following URL: <http://localhost:8080/google-authentication>.
|
|
||||||
|
|
||||||
|
|
||||||
Undeploy the Archive
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
1. Make sure you have started the JBoss Server as described above.
|
|
||||||
2. Open a command line and navigate to the root directory of this quickstart.
|
|
||||||
3. When you are finished testing, type this command to undeploy the archive:
|
|
||||||
|
|
||||||
For EAP 6: mvn jboss-as:undeploy
|
|
||||||
For WildFly: mvn -Pwildfly wildfly:undeploy
|
|
||||||
|
|
||||||
|
|
||||||
Debug the Application
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
If you want to debug the source code or look at the Javadocs of any library in the project, run either of the following commands to pull them into your local repository. The IDE should then detect them.
|
|
||||||
|
|
||||||
mvn dependency:sources
|
|
||||||
mvn dependency:resolve -Dclassifier=javadoc
|
|
|
@ -1,62 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "google-identity-provider-realm",
|
|
||||||
"enabled": true,
|
|
||||||
"sslRequired": "external",
|
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
|
||||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"defaultRoles": [ "user" ],
|
|
||||||
"users" : [
|
|
||||||
{
|
|
||||||
"username" : "admin",
|
|
||||||
"enabled": true,
|
|
||||||
"email" : "admin@admin.com",
|
|
||||||
"firstName": "Admin",
|
|
||||||
"lastName": "Burke",
|
|
||||||
"credentials" : [
|
|
||||||
{ "type" : "password",
|
|
||||||
"value" : "password" }
|
|
||||||
],
|
|
||||||
"realmRoles": [ "user","admin" ],
|
|
||||||
"clientRoles": {
|
|
||||||
"realm-management": [ "realm-admin" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles" : {
|
|
||||||
"realm" : [
|
|
||||||
{
|
|
||||||
"name": "user",
|
|
||||||
"description": "User privileges"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"clients": [
|
|
||||||
{
|
|
||||||
"clientId": "google-authentication",
|
|
||||||
"enabled": true,
|
|
||||||
"publicClient" : true,
|
|
||||||
"adminUrl": "/google-authentication",
|
|
||||||
"baseUrl": "/google-authentication",
|
|
||||||
"redirectUris": [
|
|
||||||
"/google-authentication/*"
|
|
||||||
],
|
|
||||||
"webOrigins": [
|
|
||||||
"http://localhost:8080"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"identityProviders": [
|
|
||||||
{
|
|
||||||
"alias" : "google",
|
|
||||||
"providerId" : "google",
|
|
||||||
"enabled": true,
|
|
||||||
"updateProfileFirstLogin" : "true",
|
|
||||||
"storeToken" : "true",
|
|
||||||
"addReadTokenRoleOnCreate" : true,
|
|
||||||
"config": {
|
|
||||||
"clientId": "CHANGE_CLIENT_ID",
|
|
||||||
"clientSecret": "CHANGE_CLIENT_SECRET"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-examples-broker-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Keycloak Broker Examples - Google Authentication</name>
|
|
||||||
<artifactId>google-authentication</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<description>
|
|
||||||
An example about how to authenticate with Google
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>google-authentication</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,69 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Google Authentication Example</title>
|
|
||||||
|
|
||||||
<script src="js/lib/angular/angular.js"></script>
|
|
||||||
<script src="js/lib/angular/angular-resource.js"></script>
|
|
||||||
<script src="js/lib/angular/angular-route.js"></script>
|
|
||||||
<script src="js/lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
|
|
||||||
|
|
||||||
<script src="/auth/js/keycloak.js"></script>
|
|
||||||
<script src="js/app.js" type="text/javascript"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body data-ng-controller="GlobalCtrl">
|
|
||||||
|
|
||||||
<div id="content-area" class="col-md-9" role="main">
|
|
||||||
<div id="content">
|
|
||||||
<h2>Hello, {{identity.name}} [<a href="" ng-click="logout()">Sign Out</a>]</h2>
|
|
||||||
<div>
|
|
||||||
<p><b>This is your Keycloak Profile</b>:</p>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li><b>Id</b>: {{identity.sub}}</li>
|
|
||||||
<li><b>Username</b>: {{identity.preferred_username}}</li>
|
|
||||||
<li><b>Email</b>: {{identity.email}}</li>
|
|
||||||
<li><b>Full Name</b>: {{identity.name}}</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p><button type="submit" data-ng-click="loadSocialProfile()">Load your social profile</button></p>
|
|
||||||
|
|
||||||
<div data-ng-show="socialProfile">
|
|
||||||
<p><b>This is your Google Profile</b>:</p>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li><b>Id</b>: {{socialProfile.id}}</li>
|
|
||||||
<li><b>First Name</b>: {{socialProfile.name['givenName']}}</li>
|
|
||||||
<li><b>Last Name</b>: {{socialProfile.name['familyName']}}</li>
|
|
||||||
<li><b>Gender</b>: {{socialProfile.gender}}</li>
|
|
||||||
<li><b>Profile Link</b>: <a href="{{socialProfile.url}}">{{socialProfile.url}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,124 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var module = angular.module('app', []);
|
|
||||||
|
|
||||||
angular.element(document).ready(function ($http) {
|
|
||||||
var keycloakAuth = new Keycloak('keycloak.json');
|
|
||||||
|
|
||||||
keycloakAuth.init({ onLoad: 'login-required' }).then(function () {
|
|
||||||
module.factory('Auth', function() {
|
|
||||||
var Auth = {};
|
|
||||||
|
|
||||||
Auth.logout = function() {
|
|
||||||
keycloakAuth.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.getIdentity = function() {
|
|
||||||
return keycloakAuth.idTokenParsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.getToken = function() {
|
|
||||||
return keycloakAuth.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.refreshToken = function() {
|
|
||||||
return window.location = keycloakAuth.createLoginUrl({
|
|
||||||
idpHint: 'google'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Auth;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('authInterceptor', function($q) {
|
|
||||||
return {
|
|
||||||
request: function (config) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
config.headers = config.headers || {};
|
|
||||||
|
|
||||||
if (!config.headers.Authorization) {
|
|
||||||
config.headers.Authorization = 'Bearer ' + keycloakAuth.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolve(config);
|
|
||||||
|
|
||||||
if (keycloakAuth.token) {
|
|
||||||
keycloakAuth.updateToken(5).then(function() {
|
|
||||||
}).catch(function() {
|
|
||||||
deferred.reject('Failed to refresh token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
module.config(function($httpProvider) {
|
|
||||||
$httpProvider.responseInterceptors.push('errorInterceptor');
|
|
||||||
$httpProvider.interceptors.push('authInterceptor');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('errorInterceptor', function($q) {
|
|
||||||
return function(promise) {
|
|
||||||
return promise.then(function(response) {
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
angular.bootstrap(document, ["app"]);
|
|
||||||
}).catch(function () {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('GlobalCtrl', function($scope, $http, $location, Auth) {
|
|
||||||
$scope.logout = function() {
|
|
||||||
Auth.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.identity = Auth.getIdentity();
|
|
||||||
|
|
||||||
$scope.loadSocialProfile = function() {
|
|
||||||
$http.get('/auth/realms/google-identity-provider-realm/broker/google/token').success(function(data) {
|
|
||||||
var accessToken = data.access_token;
|
|
||||||
|
|
||||||
var req = {
|
|
||||||
method: 'GET',
|
|
||||||
url: 'https://www.googleapis.com/plus/v1/people/me',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + accessToken
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$http(req)
|
|
||||||
.success(function(profile) {
|
|
||||||
$scope.socialProfile = profile;
|
|
||||||
})
|
|
||||||
.error(function(data, status, headers, config) {
|
|
||||||
$scope.socialProfile = 'Could not obtain social profile. Trying to refresh your token.';
|
|
||||||
Auth.refreshToken();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,192 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.7
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var directive = {};
|
|
||||||
|
|
||||||
directive.dropdownToggle =
|
|
||||||
['$document', '$location', '$window',
|
|
||||||
function ($document, $location, $window) {
|
|
||||||
var openElement = null, close;
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.parent().bind('click', function(event) {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
var iWasOpen = false;
|
|
||||||
|
|
||||||
if (openElement) {
|
|
||||||
iWasOpen = openElement === element;
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!iWasOpen){
|
|
||||||
element.parent().addClass('open');
|
|
||||||
openElement = element;
|
|
||||||
|
|
||||||
close = function (event) {
|
|
||||||
event && event.preventDefault();
|
|
||||||
event && event.stopPropagation();
|
|
||||||
$document.unbind('click', close);
|
|
||||||
element.parent().removeClass('open');
|
|
||||||
close = null;
|
|
||||||
openElement = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$document.bind('click', close);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
|
|
||||||
|
|
||||||
directive.tabbable = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
compile: function(element) {
|
|
||||||
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
|
|
||||||
tabContent = angular.element('<div class="tab-content"></div>');
|
|
||||||
|
|
||||||
tabContent.append(element.contents());
|
|
||||||
element.append(navTabs).append(tabContent);
|
|
||||||
},
|
|
||||||
controller: ['$scope', '$element', function($scope, $element) {
|
|
||||||
var navTabs = $element.contents().eq(0),
|
|
||||||
ngModel = $element.controller('ngModel') || {},
|
|
||||||
tabs = [],
|
|
||||||
selectedTab;
|
|
||||||
|
|
||||||
ngModel.$render = function() {
|
|
||||||
var $viewValue = this.$viewValue;
|
|
||||||
|
|
||||||
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
|
|
||||||
if(selectedTab) {
|
|
||||||
selectedTab.paneElement.removeClass('active');
|
|
||||||
selectedTab.tabElement.removeClass('active');
|
|
||||||
selectedTab = null;
|
|
||||||
}
|
|
||||||
if($viewValue) {
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++) {
|
|
||||||
if ($viewValue == tabs[i].value) {
|
|
||||||
selectedTab = tabs[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedTab) {
|
|
||||||
selectedTab.paneElement.addClass('active');
|
|
||||||
selectedTab.tabElement.addClass('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addPane = function(element, attr) {
|
|
||||||
var li = angular.element('<li><a href></a></li>'),
|
|
||||||
a = li.find('a'),
|
|
||||||
tab = {
|
|
||||||
paneElement: element,
|
|
||||||
paneAttrs: attr,
|
|
||||||
tabElement: li
|
|
||||||
};
|
|
||||||
|
|
||||||
tabs.push(tab);
|
|
||||||
|
|
||||||
attr.$observe('value', update)();
|
|
||||||
attr.$observe('title', function(){ update(); a.text(tab.title); })();
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
tab.title = attr.title;
|
|
||||||
tab.value = attr.value || attr.title;
|
|
||||||
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
}
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
|
|
||||||
navTabs.append(li);
|
|
||||||
li.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (ngModel.$setViewValue) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
ngModel.$setViewValue(tab.value);
|
|
||||||
ngModel.$render();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
tab.tabElement.remove();
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
|
|
||||||
if (tab == tabs[i]) {
|
|
||||||
tabs.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.table = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
element[0].className = 'table table-bordered table-striped code-table';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.tabPane = function() {
|
|
||||||
return {
|
|
||||||
require: '^tabbable',
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs, tabsCtrl) {
|
|
||||||
element.bind('$remove', tabsCtrl.addPane(element, attrs));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('bootstrap', []).directive(directive);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,219 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngCookies
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngCookies
|
|
||||||
*
|
|
||||||
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
|
|
||||||
*
|
|
||||||
* {@installModule cookies}
|
|
||||||
*
|
|
||||||
* <div doc-module-components="ngCookies"></div>
|
|
||||||
*
|
|
||||||
* See {@link ngCookies.$cookies `$cookies`} and
|
|
||||||
* {@link ngCookies.$cookieStore `$cookieStore`} for usage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('ngCookies', ['ng']).
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngCookies.$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.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example>
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function ExampleController($cookies) {
|
|
||||||
// Retrieving a cookie
|
|
||||||
var favoriteCookie = $cookies.myFavorite;
|
|
||||||
// Setting a cookie
|
|
||||||
$cookies.myFavorite = 'oatmeal';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</doc:source>
|
|
||||||
</doc: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 ngCookies.$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.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*/
|
|
||||||
factory('$cookieStore', ['$cookies', function($cookies) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngCookies.$cookieStore#get
|
|
||||||
* @methodOf ngCookies.$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) {
|
|
||||||
var value = $cookies[key];
|
|
||||||
return value ? angular.fromJson(value) : value;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngCookies.$cookieStore#put
|
|
||||||
* @methodOf ngCookies.$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 ngCookies.$cookieStore#remove
|
|
||||||
* @methodOf ngCookies.$cookieStore
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Remove given cookie
|
|
||||||
*
|
|
||||||
* @param {string} key Id of the key-value pair to delete.
|
|
||||||
*/
|
|
||||||
remove: function(key) {
|
|
||||||
delete $cookies[key];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}]);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,427 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function() {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* This object provides a utility for producing rich Error messages within
|
|
||||||
* Angular. It can be called as follows:
|
|
||||||
*
|
|
||||||
* var exampleMinErr = minErr('example');
|
|
||||||
* throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
|
|
||||||
*
|
|
||||||
* The above creates an instance of minErr in the example namespace. The
|
|
||||||
* resulting error will have a namespaced error code of example.one. The
|
|
||||||
* resulting error will replace {0} with the value of foo, and {1} with the
|
|
||||||
* value of bar. The object is not restricted in the number of arguments it can
|
|
||||||
* take.
|
|
||||||
*
|
|
||||||
* If fewer arguments are specified than necessary for interpolation, the extra
|
|
||||||
* interpolation markers will be preserved in the final string.
|
|
||||||
*
|
|
||||||
* Since data will be parsed statically during a build step, some restrictions
|
|
||||||
* are applied with respect to how minErr instances are created and called.
|
|
||||||
* Instances should have names of the form namespaceMinErr for a minErr created
|
|
||||||
* using minErr('namespace') . Error codes, namespaces and template strings
|
|
||||||
* should all be static strings, not variables or general expressions.
|
|
||||||
*
|
|
||||||
* @param {string} module The namespace to use for the new minErr instance.
|
|
||||||
* @returns {function(string, string, ...): Error} instance
|
|
||||||
*/
|
|
||||||
|
|
||||||
function minErr(module) {
|
|
||||||
return function () {
|
|
||||||
var code = arguments[0],
|
|
||||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
|
||||||
template = arguments[1],
|
|
||||||
templateArgs = arguments,
|
|
||||||
stringify = function (obj) {
|
|
||||||
if (typeof obj === 'function') {
|
|
||||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
|
||||||
} else if (typeof obj === 'undefined') {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof obj !== 'string') {
|
|
||||||
return JSON.stringify(obj);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
message, i;
|
|
||||||
|
|
||||||
message = prefix + template.replace(/\{\d+\}/g, function (match) {
|
|
||||||
var index = +match.slice(1, -1), arg;
|
|
||||||
|
|
||||||
if (index + 2 < templateArgs.length) {
|
|
||||||
arg = templateArgs[index + 2];
|
|
||||||
if (typeof arg === 'function') {
|
|
||||||
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
|
|
||||||
} else if (typeof arg === 'undefined') {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof arg !== 'string') {
|
|
||||||
return toJson(arg);
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
|
|
||||||
message = message + '\nhttp://errors.angularjs.org/1.2.13/' +
|
|
||||||
(module ? module + '/' : '') + code;
|
|
||||||
for (i = 2; i < arguments.length; i++) {
|
|
||||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
|
|
||||||
encodeURIComponent(stringify(arguments[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Error(message);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc interface
|
|
||||||
* @name angular.Module
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* Interface for configuring angular {@link angular.module modules}.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function setupModuleLoader(window) {
|
|
||||||
|
|
||||||
var $injectorMinErr = minErr('$injector');
|
|
||||||
var ngMinErr = minErr('ng');
|
|
||||||
|
|
||||||
function ensure(obj, name, factory) {
|
|
||||||
return obj[name] || (obj[name] = factory());
|
|
||||||
}
|
|
||||||
|
|
||||||
var angular = ensure(window, 'angular', Object);
|
|
||||||
|
|
||||||
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
|
|
||||||
angular.$$minErr = angular.$$minErr || minErr;
|
|
||||||
|
|
||||||
return ensure(angular, 'module', function() {
|
|
||||||
/** @type {Object.<string, angular.Module>} */
|
|
||||||
var modules = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc function
|
|
||||||
* @name angular.module
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* The `angular.module` is a global place for creating, registering and retrieving Angular
|
|
||||||
* modules.
|
|
||||||
* All modules (angular core or 3rd party) that should be available to an application must be
|
|
||||||
* registered using this mechanism.
|
|
||||||
*
|
|
||||||
* When passed two or more arguments, a new module is created. If passed only one argument, an
|
|
||||||
* existing module (the name passed as the first argument to `module`) is retrieved.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* # Module
|
|
||||||
*
|
|
||||||
* A module is a collection of services, directives, filters, and configuration information.
|
|
||||||
* `angular.module` is used to configure the {@link AUTO.$injector $injector}.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* // Create a new module
|
|
||||||
* var myModule = angular.module('myModule', []);
|
|
||||||
*
|
|
||||||
* // register a new service
|
|
||||||
* myModule.value('appName', 'MyCoolApp');
|
|
||||||
*
|
|
||||||
* // configure existing services inside initialization blocks.
|
|
||||||
* myModule.config(function($locationProvider) {
|
|
||||||
* // Configure existing providers
|
|
||||||
* $locationProvider.hashPrefix('!');
|
|
||||||
* });
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* Then you can create an injector and load your modules like this:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* var injector = angular.injector(['ng', 'MyModule'])
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* However it's more likely that you'll just use
|
|
||||||
* {@link ng.directive:ngApp ngApp} or
|
|
||||||
* {@link angular.bootstrap} to simplify this process for you.
|
|
||||||
*
|
|
||||||
* @param {!string} name The name of the module to create or retrieve.
|
|
||||||
* @param {Array.<string>=} requires If specified then new module is being created. If
|
|
||||||
* unspecified then the the module is being retrieved for further configuration.
|
|
||||||
* @param {Function} configFn Optional configuration function for the module. Same as
|
|
||||||
* {@link angular.Module#methods_config Module#config()}.
|
|
||||||
* @returns {module} new module with the {@link angular.Module} api.
|
|
||||||
*/
|
|
||||||
return function module(name, requires, configFn) {
|
|
||||||
var assertNotHasOwnProperty = function(name, context) {
|
|
||||||
if (name === 'hasOwnProperty') {
|
|
||||||
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
assertNotHasOwnProperty(name, 'module');
|
|
||||||
if (requires && modules.hasOwnProperty(name)) {
|
|
||||||
modules[name] = null;
|
|
||||||
}
|
|
||||||
return ensure(modules, name, function() {
|
|
||||||
if (!requires) {
|
|
||||||
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
|
|
||||||
"the module name or forgot to load it. If registering a module ensure that you " +
|
|
||||||
"specify the dependencies as the second argument.", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {!Array.<Array.<*>>} */
|
|
||||||
var invokeQueue = [];
|
|
||||||
|
|
||||||
/** @type {!Array.<Function>} */
|
|
||||||
var runBlocks = [];
|
|
||||||
|
|
||||||
var config = invokeLater('$injector', 'invoke');
|
|
||||||
|
|
||||||
/** @type {angular.Module} */
|
|
||||||
var moduleInstance = {
|
|
||||||
// Private state
|
|
||||||
_invokeQueue: invokeQueue,
|
|
||||||
_runBlocks: runBlocks,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc property
|
|
||||||
* @name angular.Module#requires
|
|
||||||
* @propertyOf angular.Module
|
|
||||||
* @returns {Array.<string>} List of module names which must be loaded before this module.
|
|
||||||
* @description
|
|
||||||
* Holds the list of modules which the injector will load before the current module is
|
|
||||||
* loaded.
|
|
||||||
*/
|
|
||||||
requires: requires,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc property
|
|
||||||
* @name angular.Module#name
|
|
||||||
* @propertyOf angular.Module
|
|
||||||
* @returns {string} Name of the module.
|
|
||||||
* @description
|
|
||||||
*/
|
|
||||||
name: name,
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#provider
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} providerType Construction function for creating new instance of the
|
|
||||||
* service.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#provider $provide.provider()}.
|
|
||||||
*/
|
|
||||||
provider: invokeLater('$provide', 'provider'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#factory
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} providerFunction Function for creating new instance of the service.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#factory $provide.factory()}.
|
|
||||||
*/
|
|
||||||
factory: invokeLater('$provide', 'factory'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#service
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} constructor A constructor function that will be instantiated.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#service $provide.service()}.
|
|
||||||
*/
|
|
||||||
service: invokeLater('$provide', 'service'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#value
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {*} object Service instance object.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#value $provide.value()}.
|
|
||||||
*/
|
|
||||||
value: invokeLater('$provide', 'value'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#constant
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name constant name
|
|
||||||
* @param {*} object Constant value.
|
|
||||||
* @description
|
|
||||||
* Because the constant are fixed, they get applied before other provide methods.
|
|
||||||
* See {@link AUTO.$provide#constant $provide.constant()}.
|
|
||||||
*/
|
|
||||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#animation
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name animation name
|
|
||||||
* @param {Function} animationFactory Factory function for creating new instance of an
|
|
||||||
* animation.
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Defines an animation hook that can be later used with
|
|
||||||
* {@link ngAnimate.$animate $animate} service and directives that use this service.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* module.animation('.animation-name', function($inject1, $inject2) {
|
|
||||||
* return {
|
|
||||||
* eventName : function(element, done) {
|
|
||||||
* //code to run the animation
|
|
||||||
* //once complete, then run done()
|
|
||||||
* return function cancellationFunction(element) {
|
|
||||||
* //code to cancel the animation
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* })
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
|
|
||||||
* {@link ngAnimate ngAnimate module} for more information.
|
|
||||||
*/
|
|
||||||
animation: invokeLater('$animateProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#filter
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name Filter name.
|
|
||||||
* @param {Function} filterFactory Factory function for creating new instance of filter.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$filterProvider#register $filterProvider.register()}.
|
|
||||||
*/
|
|
||||||
filter: invokeLater('$filterProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#controller
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string|Object} name Controller name, or an object map of controllers where the
|
|
||||||
* keys are the names and the values are the constructors.
|
|
||||||
* @param {Function} constructor Controller constructor function.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
|
|
||||||
*/
|
|
||||||
controller: invokeLater('$controllerProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#directive
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string|Object} name Directive name, or an object map of directives where the
|
|
||||||
* keys are the names and the values are the factories.
|
|
||||||
* @param {Function} directiveFactory Factory function for creating new instance of
|
|
||||||
* directives.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}.
|
|
||||||
*/
|
|
||||||
directive: invokeLater('$compileProvider', 'directive'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#config
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {Function} configFn Execute this function on module load. Useful for service
|
|
||||||
* configuration.
|
|
||||||
* @description
|
|
||||||
* Use this method to register work which needs to be performed on module loading.
|
|
||||||
*/
|
|
||||||
config: config,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#run
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {Function} initializationFn Execute this function after injector creation.
|
|
||||||
* Useful for application initialization.
|
|
||||||
* @description
|
|
||||||
* Use this method to register work which should be performed when the injector is done
|
|
||||||
* loading all modules.
|
|
||||||
*/
|
|
||||||
run: function(block) {
|
|
||||||
runBlocks.push(block);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (configFn) {
|
|
||||||
config(configFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return moduleInstance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} provider
|
|
||||||
* @param {string} method
|
|
||||||
* @param {String=} insertMethod
|
|
||||||
* @returns {angular.Module}
|
|
||||||
*/
|
|
||||||
function invokeLater(provider, method, insertMethod) {
|
|
||||||
return function() {
|
|
||||||
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
|
|
||||||
return moduleInstance;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setupModuleLoader(window);
|
|
||||||
})(window);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closure compiler type information
|
|
||||||
*
|
|
||||||
* @typedef { {
|
|
||||||
* requires: !Array.<string>,
|
|
||||||
* invokeQueue: !Array.<Array.<*>>,
|
|
||||||
*
|
|
||||||
* service: function(string, Function):angular.Module,
|
|
||||||
* factory: function(string, Function):angular.Module,
|
|
||||||
* value: function(string, *):angular.Module,
|
|
||||||
*
|
|
||||||
* filter: function(string, Function):angular.Module,
|
|
||||||
*
|
|
||||||
* init: function(Function):angular.Module
|
|
||||||
* } }
|
|
||||||
*/
|
|
||||||
angular.Module;
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,613 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 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
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngResource
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngResource.$resource
|
|
||||||
* @requires $http
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* A factory which creates a resource object that lets you interact with
|
|
||||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
|
||||||
*
|
|
||||||
* The returned resource object has action methods which provide high-level behaviors without
|
|
||||||
* the need to interact with the low level {@link ng.$http $http} service.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngResource `ngResource`} module to be installed.
|
|
||||||
*
|
|
||||||
* @param {string} url A parametrized URL template with parameters prefixed by `:` as in
|
|
||||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
|
||||||
* `http://example.com:8080/api`), it will be respected.
|
|
||||||
*
|
|
||||||
* If you are using a url with a suffix, just add the suffix, like this:
|
|
||||||
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
|
|
||||||
* or even `$resource('http://example.com/resource/:resource_id.:format')`
|
|
||||||
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
|
|
||||||
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
|
|
||||||
* can escape it with `/\.`.
|
|
||||||
*
|
|
||||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
|
||||||
* `actions` methods. 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).
|
|
||||||
*
|
|
||||||
* Each key value in the parameter object is first bound to url template if present and then any
|
|
||||||
* excess keys are appended to the url search query after the `?`.
|
|
||||||
*
|
|
||||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
|
||||||
* URL `/path/greet?salutation=Hello`.
|
|
||||||
*
|
|
||||||
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
|
|
||||||
* the data object (useful for non-GET operations).
|
|
||||||
*
|
|
||||||
* @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#usage_parameters $http.config}:
|
|
||||||
*
|
|
||||||
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
|
|
||||||
* action2: {method:?, params:?, isArray:?, headers:?, ...},
|
|
||||||
* ...}
|
|
||||||
*
|
|
||||||
* 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)>}` –
|
|
||||||
* 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)>}` –
|
|
||||||
* 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
|
|
||||||
* GET request, otherwise if a cache instance built with
|
|
||||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
|
||||||
* caching.
|
|
||||||
* - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
|
|
||||||
* should abort the request when resolved.
|
|
||||||
* - **`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
|
|
||||||
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
|
|
||||||
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
|
|
||||||
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
|
|
||||||
* with `http response` object. See {@link ng.$http $http interceptors}.
|
|
||||||
*
|
|
||||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
|
||||||
* optionally extended with custom `actions`. The default set contains these actions:
|
|
||||||
*
|
|
||||||
* { 'get': {method:'GET'},
|
|
||||||
* 'save': {method:'POST'},
|
|
||||||
* 'query': {method:'GET', isArray:true},
|
|
||||||
* 'remove': {method:'DELETE'},
|
|
||||||
* 'delete': {method:'DELETE'} };
|
|
||||||
*
|
|
||||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
|
||||||
* destination and parameters. When the data is returned from the server then the object is an
|
|
||||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
|
||||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
|
||||||
* read, update, delete) on server-side data like this:
|
|
||||||
* <pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
var user = User.get({userId:123}, function() {
|
|
||||||
user.abc = true;
|
|
||||||
user.$save();
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* It is important to realize that invoking a $resource object method immediately returns an
|
|
||||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
|
||||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
|
||||||
* 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 cases 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:
|
|
||||||
*
|
|
||||||
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
|
|
||||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
|
|
||||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
|
||||||
*
|
|
||||||
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
|
|
||||||
* with (httpResponse) argument.
|
|
||||||
*
|
|
||||||
* Class actions return empty instance (with additional properties below).
|
|
||||||
* Instance actions return promise of the action.
|
|
||||||
*
|
|
||||||
* The Resource instances and collection have these additional properties:
|
|
||||||
*
|
|
||||||
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
|
|
||||||
* instance or collection.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* # Credit card resource
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
// Define CreditCard class
|
|
||||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
|
||||||
{userId:123, cardId:'@id'}, {
|
|
||||||
charge: {method:'POST', params:{charge:true}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// We can retrieve a collection from the server
|
|
||||||
var cards = CreditCard.query(function() {
|
|
||||||
// GET: /user/123/card
|
|
||||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
|
||||||
|
|
||||||
var card = cards[0];
|
|
||||||
// each item is an instance of CreditCard
|
|
||||||
expect(card instanceof CreditCard).toEqual(true);
|
|
||||||
card.name = "J. Smith";
|
|
||||||
// non GET methods are mapped onto the instances
|
|
||||||
card.$save();
|
|
||||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
|
||||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
|
||||||
|
|
||||||
// our custom method is mapped as well.
|
|
||||||
card.$charge({amount:9.99});
|
|
||||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
|
||||||
});
|
|
||||||
|
|
||||||
// we can create an instance as well
|
|
||||||
var newCard = new CreditCard({number:'0123'});
|
|
||||||
newCard.name = "Mike Smith";
|
|
||||||
newCard.$save();
|
|
||||||
// POST: /user/123/card {number:'0123', 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`.
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
var user = User.get({userId:123}, function() {
|
|
||||||
user.abc = true;
|
|
||||||
user.$save();
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* It's worth noting that the success callback for `get`, `query` and other methods 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:
|
|
||||||
*
|
|
||||||
<pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
User.get({userId:123}, function(u, getResponseHeaders){
|
|
||||||
u.abc = true;
|
|
||||||
u.$save(function(u, putResponseHeaders) {
|
|
||||||
//u => saved user object
|
|
||||||
//putResponseHeaders => $http header getter
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
* # 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', '$q', function($http, $q) {
|
|
||||||
|
|
||||||
var DEFAULT_ACTIONS = {
|
|
||||||
'get': {method:'GET'},
|
|
||||||
'save': {method:'POST'},
|
|
||||||
'query': {method:'GET', isArray:true},
|
|
||||||
'remove': {method:'DELETE'},
|
|
||||||
'delete': {method:'DELETE'}
|
|
||||||
};
|
|
||||||
var noop = angular.noop,
|
|
||||||
forEach = angular.forEach,
|
|
||||||
extend = angular.extend,
|
|
||||||
copy = angular.copy,
|
|
||||||
isFunction = angular.isFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
|
|
||||||
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
|
|
||||||
* segments:
|
|
||||||
* segment = *pchar
|
|
||||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
|
||||||
* pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
|
||||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
||||||
* / "*" / "+" / "," / ";" / "="
|
|
||||||
*/
|
|
||||||
function encodeUriSegment(val) {
|
|
||||||
return encodeUriQuery(val, true).
|
|
||||||
replace(/%26/gi, '&').
|
|
||||||
replace(/%3D/gi, '=').
|
|
||||||
replace(/%2B/gi, '+');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 / "-" / "." / "_" / "~"
|
|
||||||
* pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
||||||
* / "*" / "+" / "," / ";" / "="
|
|
||||||
*/
|
|
||||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
|
||||||
return encodeURIComponent(val).
|
|
||||||
replace(/%40/gi, '@').
|
|
||||||
replace(/%3A/gi, ':').
|
|
||||||
replace(/%24/g, '$').
|
|
||||||
replace(/%2C/gi, ',').
|
|
||||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function Route(template, defaults) {
|
|
||||||
this.template = template;
|
|
||||||
this.defaults = defaults || {};
|
|
||||||
this.urlParams = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Route.prototype = {
|
|
||||||
setUrlParams: function(config, params, actionUrl) {
|
|
||||||
var self = this,
|
|
||||||
url = actionUrl || self.template,
|
|
||||||
val,
|
|
||||||
encodedVal;
|
|
||||||
|
|
||||||
var urlParams = self.urlParams = {};
|
|
||||||
forEach(url.split(/\W/), function(param){
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
url = url.replace(/\\:/g, ':');
|
|
||||||
|
|
||||||
params = params || {};
|
|
||||||
forEach(self.urlParams, function(_, urlParam){
|
|
||||||
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"), function(match, p1) {
|
|
||||||
return encodedVal + p1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
|
|
||||||
leadingSlashes, tail) {
|
|
||||||
if (tail.charAt(0) == '/') {
|
|
||||||
return tail;
|
|
||||||
} else {
|
|
||||||
return leadingSlashes + tail;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// strip trailing slashes and set the url
|
|
||||||
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+($|\?))/, '.');
|
|
||||||
// replace escaped `/\.` with `/.`
|
|
||||||
config.url = url.replace(/\/\\\./, '/.');
|
|
||||||
|
|
||||||
|
|
||||||
// set params - delegate param encoding to $http
|
|
||||||
forEach(params, function(value, key){
|
|
||||||
if (!self.urlParams[key]) {
|
|
||||||
config.params = config.params || {};
|
|
||||||
config.params[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function resourceFactory(url, paramDefaults, actions) {
|
|
||||||
var route = new Route(url);
|
|
||||||
|
|
||||||
actions = extend({}, DEFAULT_ACTIONS, actions);
|
|
||||||
|
|
||||||
function extractParams(data, actionParams){
|
|
||||||
var ids = {};
|
|
||||||
actionParams = extend({}, paramDefaults, actionParams);
|
|
||||||
forEach(actionParams, function(value, key){
|
|
||||||
if (isFunction(value)) { value = value(); }
|
|
||||||
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
|
|
||||||
lookupDottedPath(data, value.substr(1)) : value;
|
|
||||||
});
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultResponseInterceptor(response) {
|
|
||||||
return response.resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Resource(value){
|
|
||||||
shallowClearAndCopy(value || {}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(actions, function(action, name) {
|
|
||||||
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
|
|
||||||
|
|
||||||
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;
|
|
||||||
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 (hasBody) data = a1;
|
|
||||||
else params = a1;
|
|
||||||
break;
|
|
||||||
case 0: break;
|
|
||||||
default:
|
|
||||||
throw $resourceMinErr('badargs',
|
|
||||||
"Expected up to 4 arguments [params, data, success, error], got {0} arguments",
|
|
||||||
arguments.length);
|
|
||||||
}
|
|
||||||
/* jshint +W086 */ /* (purposefully fall through case statements) */
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
forEach(action, function(value, key) {
|
|
||||||
if (key != 'params' && key != 'isArray' && key != 'interceptor') {
|
|
||||||
httpConfig[key] = copy(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// 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 {
|
|
||||||
shallowClearAndCopy(data, value);
|
|
||||||
value.$promise = promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value.$resolved = true;
|
|
||||||
|
|
||||||
response.resource = value;
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
value.$resolved = true;
|
|
||||||
|
|
||||||
(error||noop)(response);
|
|
||||||
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then(
|
|
||||||
function(response) {
|
|
||||||
var value = responseInterceptor(response);
|
|
||||||
(success||noop)(value, response.headers);
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
responseErrorInterceptor);
|
|
||||||
|
|
||||||
if (!isInstanceCall) {
|
|
||||||
// we are creating instance / collection
|
|
||||||
// - set the initial promise
|
|
||||||
// - return the instance / collection
|
|
||||||
value.$promise = promise;
|
|
||||||
value.$resolved = false;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// instance call
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Resource.prototype['$' + name] = function(params, success, error) {
|
|
||||||
if (isFunction(params)) {
|
|
||||||
error = success; success = params; params = {};
|
|
||||||
}
|
|
||||||
var result = Resource[name].call(this, params, this, success, error);
|
|
||||||
return result.$promise || result;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
Resource.bind = function(additionalParamDefaults){
|
|
||||||
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
return Resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceFactory;
|
|
||||||
}]);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,938 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngRoute
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngRoute
|
|
||||||
*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$routeProvider
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* 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 = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$routeProvider#when
|
|
||||||
* @methodOf ngRoute.$routeProvider
|
|
||||||
*
|
|
||||||
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
|
|
||||||
* contains redundant 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 exactly match the
|
|
||||||
* route definition.
|
|
||||||
*
|
|
||||||
* * `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:
|
|
||||||
* 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: e.g.`:name?`.
|
|
||||||
*
|
|
||||||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
|
|
||||||
* `/color/brown/largecode/code/with/slashs/edit` and extract:
|
|
||||||
*
|
|
||||||
* * `color: brown`
|
|
||||||
* * `largecode: code/with/slashs`.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param {Object} route Mapping information to be assigned to `$route.current` on route
|
|
||||||
* match.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* - `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
|
|
||||||
* returns an html template as a string which should be used by {@link
|
|
||||||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
|
|
||||||
* This property takes precedence over `templateUrl`.
|
|
||||||
*
|
|
||||||
* If `template` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
|
||||||
* `$location.path()` by applying the current route
|
|
||||||
*
|
|
||||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
|
|
||||||
* template that should be used by {@link ngRoute.directive:ngView ngView}.
|
|
||||||
*
|
|
||||||
* If `templateUrl` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
|
||||||
* `$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, 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.
|
|
||||||
*
|
|
||||||
* - `redirectTo` – {(string|function())=} – value to update
|
|
||||||
* {@link ng.$location $location} path with and trigger route redirection.
|
|
||||||
*
|
|
||||||
* If `redirectTo` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Object.<string>}` - route parameters extracted from the current
|
|
||||||
* `$location.path()` by applying the current route templateUrl.
|
|
||||||
* - `{string}` - current `$location.path()`
|
|
||||||
* - `{Object}` - current `$location.search()`
|
|
||||||
*
|
|
||||||
* The custom `redirectTo` function is expected to return a string which will be used
|
|
||||||
* to update `$location.path()` and `$location.search()`.
|
|
||||||
*
|
|
||||||
* - `[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.
|
|
||||||
*
|
|
||||||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
|
|
||||||
*
|
|
||||||
* If the option is set to `true`, then the particular route can be matched without being
|
|
||||||
* case sensitive
|
|
||||||
*
|
|
||||||
* @returns {Object} self
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Adds a new route definition to the `$route` service.
|
|
||||||
*/
|
|
||||||
this.when = function(path, route) {
|
|
||||||
routes[path] = angular.extend(
|
|
||||||
{reloadOnSearch: true},
|
|
||||||
route,
|
|
||||||
path && pathRegExp(path, route)
|
|
||||||
);
|
|
||||||
|
|
||||||
// create redirection for trailing slashes
|
|
||||||
if (path) {
|
|
||||||
var redirectPath = (path[path.length-1] == '/')
|
|
||||||
? path.substr(0, path.length-1)
|
|
||||||
: path +'/';
|
|
||||||
|
|
||||||
routes[redirectPath] = angular.extend(
|
|
||||||
{redirectTo: path},
|
|
||||||
pathRegExp(redirectPath, route)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param path {string} path
|
|
||||||
* @param opts {Object} options
|
|
||||||
* @return {?Object}
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Normalizes the given path, returning a regular expression
|
|
||||||
* and the original path.
|
|
||||||
*
|
|
||||||
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
|
|
||||||
*/
|
|
||||||
function pathRegExp(path, opts) {
|
|
||||||
var insensitive = opts.caseInsensitiveMatch,
|
|
||||||
ret = {
|
|
||||||
originalPath: path,
|
|
||||||
regexp: path
|
|
||||||
},
|
|
||||||
keys = ret.keys = [];
|
|
||||||
|
|
||||||
path = path
|
|
||||||
.replace(/([().])/g, '\\$1')
|
|
||||||
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){
|
|
||||||
var optional = option === '?' ? option : null;
|
|
||||||
var star = option === '*' ? option : null;
|
|
||||||
keys.push({ name: key, optional: !!optional });
|
|
||||||
slash = slash || '';
|
|
||||||
return ''
|
|
||||||
+ (optional ? '' : slash)
|
|
||||||
+ '(?:'
|
|
||||||
+ (optional ? slash : '')
|
|
||||||
+ (star && '(.+?)' || '([^/]+)')
|
|
||||||
+ (optional || '')
|
|
||||||
+ ')'
|
|
||||||
+ (optional || '');
|
|
||||||
})
|
|
||||||
.replace(/([\/$\*])/g, '\\$1');
|
|
||||||
|
|
||||||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$routeProvider#otherwise
|
|
||||||
* @methodOf ngRoute.$routeProvider
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Sets route definition that will be used on route change when no other route definition
|
|
||||||
* is matched.
|
|
||||||
*
|
|
||||||
* @param {Object} params Mapping information to be assigned to `$route.current`.
|
|
||||||
* @returns {Object} self
|
|
||||||
*/
|
|
||||||
this.otherwise = function(params) {
|
|
||||||
this.when(null, params);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
this.$get = ['$rootScope',
|
|
||||||
'$location',
|
|
||||||
'$routeParams',
|
|
||||||
'$q',
|
|
||||||
'$injector',
|
|
||||||
'$http',
|
|
||||||
'$templateCache',
|
|
||||||
'$sce',
|
|
||||||
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$route
|
|
||||||
* @requires $location
|
|
||||||
* @requires $routeParams
|
|
||||||
*
|
|
||||||
* @property {Object} current Reference to the current route definition.
|
|
||||||
* The route definition contains:
|
|
||||||
*
|
|
||||||
* - `controller`: The controller constructor as define in route definition.
|
|
||||||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
|
|
||||||
* controller instantiation. The `locals` contain
|
|
||||||
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
|
|
||||||
*
|
|
||||||
* - `$scope` - The current route scope.
|
|
||||||
* - `$template` - The current route template HTML.
|
|
||||||
*
|
|
||||||
* @property {Array.<Object>} routes Array of all configured routes.
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials).
|
|
||||||
* It watches `$location.url()` and tries to map the path to an existing route definition.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
This example shows how changing the URL hash causes the `$route` to match a route against the
|
|
||||||
URL, and the `ngView` pulls in the partial.
|
|
||||||
|
|
||||||
Note that this example is using {@link ng.directive:script inlined templates}
|
|
||||||
to get it working on jsfiddle as well.
|
|
||||||
|
|
||||||
<example module="ngViewExample" deps="angular-route.js">
|
|
||||||
<file name="index.html">
|
|
||||||
<div ng-controller="MainCntl">
|
|
||||||
Choose:
|
|
||||||
<a href="Book/Moby">Moby</a> |
|
|
||||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
|
||||||
<a href="Book/Gatsby">Gatsby</a> |
|
|
||||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
|
||||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
|
||||||
|
|
||||||
<div ng-view></div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre>$location.path() = {{$location.path()}}</pre>
|
|
||||||
<pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
|
|
||||||
<pre>$route.current.params = {{$route.current.params}}</pre>
|
|
||||||
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
|
|
||||||
<pre>$routeParams = {{$routeParams}}</pre>
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="book.html">
|
|
||||||
controller: {{name}}<br />
|
|
||||||
Book Id: {{params.bookId}}<br />
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="chapter.html">
|
|
||||||
controller: {{name}}<br />
|
|
||||||
Book Id: {{params.bookId}}<br />
|
|
||||||
Chapter Id: {{params.chapterId}}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="script.js">
|
|
||||||
angular.module('ngViewExample', ['ngRoute'])
|
|
||||||
|
|
||||||
.config(function($routeProvider, $locationProvider) {
|
|
||||||
$routeProvider.when('/Book/:bookId', {
|
|
||||||
templateUrl: 'book.html',
|
|
||||||
controller: BookCntl,
|
|
||||||
resolve: {
|
|
||||||
// I will cause a 1 second delay
|
|
||||||
delay: function($q, $timeout) {
|
|
||||||
var delay = $q.defer();
|
|
||||||
$timeout(delay.resolve, 1000);
|
|
||||||
return delay.promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
|
|
||||||
templateUrl: 'chapter.html',
|
|
||||||
controller: ChapterCntl
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure html5 to get links working on jsfiddle
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
function MainCntl($scope, $route, $routeParams, $location) {
|
|
||||||
$scope.$route = $route;
|
|
||||||
$scope.$location = $location;
|
|
||||||
$scope.$routeParams = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookCntl($scope, $routeParams) {
|
|
||||||
$scope.name = "BookCntl";
|
|
||||||
$scope.params = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChapterCntl($scope, $routeParams) {
|
|
||||||
$scope.name = "ChapterCntl";
|
|
||||||
$scope.params = $routeParams;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="protractorTest.js">
|
|
||||||
it('should load and compile correct template', function() {
|
|
||||||
element(by.linkText('Moby: Ch1')).click();
|
|
||||||
var content = element(by.css('.doc-example-live [ng-view]')).getText();
|
|
||||||
expect(content).toMatch(/controller\: ChapterCntl/);
|
|
||||||
expect(content).toMatch(/Book Id\: Moby/);
|
|
||||||
expect(content).toMatch(/Chapter Id\: 1/);
|
|
||||||
|
|
||||||
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/);
|
|
||||||
});
|
|
||||||
</file>
|
|
||||||
</example>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeStart
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @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.
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeSuccess
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @description
|
|
||||||
* Broadcasted after a route dependencies are resolved.
|
|
||||||
* {@link ngRoute.directive:ngView ngView} listens for the directive
|
|
||||||
* to instantiate the controller and render the view.
|
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeError
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeUpdate
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* The `reloadOnSearch` property has been set to false, and we are reusing the same
|
|
||||||
* instance of the Controller.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var forceReload = false,
|
|
||||||
$route = {
|
|
||||||
routes: routes,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$route#reload
|
|
||||||
* @methodOf ngRoute.$route
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Causes `$route` service to reload the current route even if
|
|
||||||
* {@link ng.$location $location} hasn't changed.
|
|
||||||
*
|
|
||||||
* As a result of that, {@link ngRoute.directive:ngView ngView}
|
|
||||||
* creates new scope, reinstantiates the controller.
|
|
||||||
*/
|
|
||||||
reload: function() {
|
|
||||||
forceReload = true;
|
|
||||||
$rootScope.$evalAsync(updateRoute);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.$on('$locationChangeSuccess', updateRoute);
|
|
||||||
|
|
||||||
return $route;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param on {string} current url
|
|
||||||
* @param route {Object} route regexp to match the url against
|
|
||||||
* @return {?Object}
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Check if the route matches the current url.
|
|
||||||
*
|
|
||||||
* Inspired by match in
|
|
||||||
* visionmedia/express/lib/router/router.js.
|
|
||||||
*/
|
|
||||||
function switchRouteMatcher(on, route) {
|
|
||||||
var keys = route.keys,
|
|
||||||
params = {};
|
|
||||||
|
|
||||||
if (!route.regexp) return null;
|
|
||||||
|
|
||||||
var m = route.regexp.exec(on);
|
|
||||||
if (!m) return null;
|
|
||||||
|
|
||||||
for (var i = 1, len = m.length; i < len; ++i) {
|
|
||||||
var key = keys[i - 1];
|
|
||||||
|
|
||||||
var val = 'string' == typeof m[i]
|
|
||||||
? decodeURIComponent(m[i])
|
|
||||||
: m[i];
|
|
||||||
|
|
||||||
if (key && val) {
|
|
||||||
params[key.name] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRoute() {
|
|
||||||
var next = parseRoute(),
|
|
||||||
last = $route.current;
|
|
||||||
|
|
||||||
if (next && last && next.$$route === last.$$route
|
|
||||||
&& angular.equals(next.pathParams, last.pathParams)
|
|
||||||
&& !next.reloadOnSearch && !forceReload) {
|
|
||||||
last.params = next.params;
|
|
||||||
angular.copy(last.params, $routeParams);
|
|
||||||
$rootScope.$broadcast('$routeUpdate', last);
|
|
||||||
} else if (next || last) {
|
|
||||||
forceReload = false;
|
|
||||||
$rootScope.$broadcast('$routeChangeStart', next, last);
|
|
||||||
$route.current = next;
|
|
||||||
if (next) {
|
|
||||||
if (next.redirectTo) {
|
|
||||||
if (angular.isString(next.redirectTo)) {
|
|
||||||
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
|
|
||||||
.replace();
|
|
||||||
} else {
|
|
||||||
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
|
|
||||||
.replace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$q.when(next).
|
|
||||||
then(function() {
|
|
||||||
if (next) {
|
|
||||||
var locals = angular.extend({}, next.resolve),
|
|
||||||
template, templateUrl;
|
|
||||||
|
|
||||||
angular.forEach(locals, function(value, key) {
|
|
||||||
locals[key] = angular.isString(value) ?
|
|
||||||
$injector.get(value) : $injector.invoke(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (angular.isDefined(template = next.template)) {
|
|
||||||
if (angular.isFunction(template)) {
|
|
||||||
template = template(next.params);
|
|
||||||
}
|
|
||||||
} else if (angular.isDefined(templateUrl = next.templateUrl)) {
|
|
||||||
if (angular.isFunction(templateUrl)) {
|
|
||||||
templateUrl = templateUrl(next.params);
|
|
||||||
}
|
|
||||||
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
|
|
||||||
if (angular.isDefined(templateUrl)) {
|
|
||||||
next.loadedTemplateUrl = templateUrl;
|
|
||||||
template = $http.get(templateUrl, {cache: $templateCache}).
|
|
||||||
then(function(response) { return response.data; });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (angular.isDefined(template)) {
|
|
||||||
locals['$template'] = template;
|
|
||||||
}
|
|
||||||
return $q.all(locals);
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
// after route change
|
|
||||||
then(function(locals) {
|
|
||||||
if (next == $route.current) {
|
|
||||||
if (next) {
|
|
||||||
next.locals = locals;
|
|
||||||
angular.copy(next.params, $routeParams);
|
|
||||||
}
|
|
||||||
$rootScope.$broadcast('$routeChangeSuccess', next, last);
|
|
||||||
}
|
|
||||||
}, function(error) {
|
|
||||||
if (next == $route.current) {
|
|
||||||
$rootScope.$broadcast('$routeChangeError', next, last, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns the current active route, by matching it against the URL
|
|
||||||
*/
|
|
||||||
function parseRoute() {
|
|
||||||
// Match a route
|
|
||||||
var params, match;
|
|
||||||
angular.forEach(routes, function(route, path) {
|
|
||||||
if (!match && (params = switchRouteMatcher($location.path(), route))) {
|
|
||||||
match = inherit(route, {
|
|
||||||
params: angular.extend({}, $location.search(), params),
|
|
||||||
pathParams: params});
|
|
||||||
match.$$route = route;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// No route matched; fallback to "otherwise" route
|
|
||||||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns interpolation of the redirect path with the parameters
|
|
||||||
*/
|
|
||||||
function interpolate(string, params) {
|
|
||||||
var result = [];
|
|
||||||
angular.forEach((string||'').split(':'), function(segment, i) {
|
|
||||||
if (i === 0) {
|
|
||||||
result.push(segment);
|
|
||||||
} else {
|
|
||||||
var segmentMatch = segment.match(/(\w+)(.*)/);
|
|
||||||
var key = segmentMatch[1];
|
|
||||||
result.push(params[key]);
|
|
||||||
result.push(segmentMatch[2] || '');
|
|
||||||
delete params[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result.join('');
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
ngRouteModule.provider('$routeParams', $RouteParamsProvider);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$routeParams
|
|
||||||
* @requires $route
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* The `$routeParams` service allows you to retrieve the current set of route parameters.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* The route parameters are a combination of {@link ng.$location `$location`}'s
|
|
||||||
* {@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.
|
|
||||||
*
|
|
||||||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged
|
|
||||||
* (but its properties will likely change) even when a route change occurs.
|
|
||||||
*
|
|
||||||
* Note that the `$routeParams` are only updated *after* a route change completes successfully.
|
|
||||||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions.
|
|
||||||
* Instead you can use `$route.current.params` to access the new route's parameters.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* <pre>
|
|
||||||
* // Given:
|
|
||||||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
|
|
||||||
* // Route: /Chapter/:chapterId/Section/:sectionId
|
|
||||||
* //
|
|
||||||
* // Then
|
|
||||||
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
function $RouteParamsProvider() {
|
|
||||||
this.$get = function() { return {}; };
|
|
||||||
}
|
|
||||||
|
|
||||||
ngRouteModule.directive('ngView', ngViewFactory);
|
|
||||||
ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc directive
|
|
||||||
* @name ngRoute.directive:ngView
|
|
||||||
* @restrict ECA
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* # Overview
|
|
||||||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
|
|
||||||
* including the rendered template of the current route into the main layout (`index.html`) file.
|
|
||||||
* Every time the current route changes, the included view changes with it according to the
|
|
||||||
* configuration of the `$route` service.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* @animations
|
|
||||||
* enter - animation is used to bring new content into the browser.
|
|
||||||
* leave - animation is used to animate existing content away.
|
|
||||||
*
|
|
||||||
* 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">
|
|
||||||
<div ng-controller="MainCntl as main">
|
|
||||||
Choose:
|
|
||||||
<a href="Book/Moby">Moby</a> |
|
|
||||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
|
||||||
<a href="Book/Gatsby">Gatsby</a> |
|
|
||||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
|
||||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
|
||||||
|
|
||||||
<div class="view-animate-container">
|
|
||||||
<div ng-view class="view-animate"></div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre>$location.path() = {{main.$location.path()}}</pre>
|
|
||||||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
|
|
||||||
<pre>$route.current.params = {{main.$route.current.params}}</pre>
|
|
||||||
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
|
|
||||||
<pre>$routeParams = {{main.$routeParams}}</pre>
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="book.html">
|
|
||||||
<div>
|
|
||||||
controller: {{book.name}}<br />
|
|
||||||
Book Id: {{book.params.bookId}}<br />
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="chapter.html">
|
|
||||||
<div>
|
|
||||||
controller: {{chapter.name}}<br />
|
|
||||||
Book Id: {{chapter.params.bookId}}<br />
|
|
||||||
Chapter Id: {{chapter.params.chapterId}}
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="animations.css">
|
|
||||||
.view-animate-container {
|
|
||||||
position:relative;
|
|
||||||
height:100px!important;
|
|
||||||
position:relative;
|
|
||||||
background:white;
|
|
||||||
border:1px solid black;
|
|
||||||
height:40px;
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate {
|
|
||||||
padding:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate.ng-enter, .view-animate.ng-leave {
|
|
||||||
-webkit-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;
|
|
||||||
width:100%;
|
|
||||||
border-left:1px solid black;
|
|
||||||
|
|
||||||
position:absolute;
|
|
||||||
top:0;
|
|
||||||
left:0;
|
|
||||||
right:0;
|
|
||||||
bottom:0;
|
|
||||||
padding:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate.ng-enter {
|
|
||||||
left:100%;
|
|
||||||
}
|
|
||||||
.view-animate.ng-enter.ng-enter-active {
|
|
||||||
left:0;
|
|
||||||
}
|
|
||||||
.view-animate.ng-leave.ng-leave-active {
|
|
||||||
left:-100%;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="script.js">
|
|
||||||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'],
|
|
||||||
function($routeProvider, $locationProvider) {
|
|
||||||
$routeProvider.when('/Book/:bookId', {
|
|
||||||
templateUrl: 'book.html',
|
|
||||||
controller: BookCntl,
|
|
||||||
controllerAs: 'book'
|
|
||||||
});
|
|
||||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
|
|
||||||
templateUrl: 'chapter.html',
|
|
||||||
controller: ChapterCntl,
|
|
||||||
controllerAs: 'chapter'
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure html5 to get links working on jsfiddle
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
function MainCntl($route, $routeParams, $location) {
|
|
||||||
this.$route = $route;
|
|
||||||
this.$location = $location;
|
|
||||||
this.$routeParams = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookCntl($routeParams) {
|
|
||||||
this.name = "BookCntl";
|
|
||||||
this.params = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChapterCntl($routeParams) {
|
|
||||||
this.name = "ChapterCntl";
|
|
||||||
this.params = $routeParams;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="protractorTest.js">
|
|
||||||
it('should load and compile correct template', function() {
|
|
||||||
element(by.linkText('Moby: Ch1')).click();
|
|
||||||
var content = element(by.css('.doc-example-live [ng-view]')).getText();
|
|
||||||
expect(content).toMatch(/controller\: ChapterCntl/);
|
|
||||||
expect(content).toMatch(/Book Id\: Moby/);
|
|
||||||
expect(content).toMatch(/Chapter Id\: 1/);
|
|
||||||
|
|
||||||
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/);
|
|
||||||
});
|
|
||||||
</file>
|
|
||||||
</example>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.directive:ngView#$viewContentLoaded
|
|
||||||
* @eventOf ngRoute.directive:ngView
|
|
||||||
* @eventType emit on the current ngView scope
|
|
||||||
* @description
|
|
||||||
* Emitted every time the ngView content is reloaded.
|
|
||||||
*/
|
|
||||||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
|
|
||||||
function ngViewFactory( $route, $anchorScroll, $animate) {
|
|
||||||
return {
|
|
||||||
restrict: 'ECA',
|
|
||||||
terminal: true,
|
|
||||||
priority: 400,
|
|
||||||
transclude: 'element',
|
|
||||||
link: function(scope, $element, attr, ctrl, $transclude) {
|
|
||||||
var currentScope,
|
|
||||||
currentElement,
|
|
||||||
autoScrollExp = attr.autoscroll,
|
|
||||||
onloadExp = attr.onload || '';
|
|
||||||
|
|
||||||
scope.$on('$routeChangeSuccess', update);
|
|
||||||
update();
|
|
||||||
|
|
||||||
function cleanupLastView() {
|
|
||||||
if (currentScope) {
|
|
||||||
currentScope.$destroy();
|
|
||||||
currentScope = null;
|
|
||||||
}
|
|
||||||
if(currentElement) {
|
|
||||||
$animate.leave(currentElement);
|
|
||||||
currentElement = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var locals = $route.current && $route.current.locals,
|
|
||||||
template = locals && locals.$template;
|
|
||||||
|
|
||||||
if (angular.isDefined(template)) {
|
|
||||||
var newScope = scope.$new();
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
currentElement = clone;
|
|
||||||
currentScope = current.scope = newScope;
|
|
||||||
currentScope.$emit('$viewContentLoaded');
|
|
||||||
currentScope.$eval(onloadExp);
|
|
||||||
} else {
|
|
||||||
cleanupLastView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
$element.html(locals.$template);
|
|
||||||
|
|
||||||
var link = $compile($element.contents());
|
|
||||||
|
|
||||||
if (current.controller) {
|
|
||||||
locals.$scope = scope;
|
|
||||||
var controller = $controller(current.controller, locals);
|
|
||||||
if (current.controllerAs) {
|
|
||||||
scope[current.controllerAs] = controller;
|
|
||||||
}
|
|
||||||
$element.data('$ngControllerController', controller);
|
|
||||||
$element.children().data('$ngControllerController', controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
link(scope);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,642 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngSanitize
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngSanitize
|
|
||||||
*
|
|
||||||
* The `ngSanitize` module provides functionality to sanitize HTML.
|
|
||||||
*
|
|
||||||
* {@installModule sanitize}
|
|
||||||
*
|
|
||||||
* <div doc-module-components="ngSanitize"></div>
|
|
||||||
*
|
|
||||||
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* HTML Parser By Misko Hevery (misko@hevery.com)
|
|
||||||
* based on: HTML Parser By John Resig (ejohn.org)
|
|
||||||
* Original code by Erik Arvidsson, Mozilla Public License
|
|
||||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
|
||||||
*
|
|
||||||
* // Use like so:
|
|
||||||
* htmlParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc service
|
|
||||||
* @name ngSanitize.$sanitize
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
|
||||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example module="ngSanitize">
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function Ctrl($scope, $sce) {
|
|
||||||
$scope.snippet =
|
|
||||||
'<p style="color:blue">an html\n' +
|
|
||||||
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
|
|
||||||
'snippet</p>';
|
|
||||||
$scope.deliberatelyTrustDangerousSnippet = function() {
|
|
||||||
return $sce.trustAsHtml($scope.snippet);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div ng-controller="Ctrl">
|
|
||||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Directive</td>
|
|
||||||
<td>How</td>
|
|
||||||
<td>Source</td>
|
|
||||||
<td>Rendered</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="bind-html-with-sanitize">
|
|
||||||
<td>ng-bind-html</td>
|
|
||||||
<td>Automatically uses $sanitize</td>
|
|
||||||
<td><pre><div ng-bind-html="snippet"><br/></div></pre></td>
|
|
||||||
<td><div ng-bind-html="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
<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()">
|
|
||||||
</div></pre>
|
|
||||||
</td>
|
|
||||||
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="bind-default">
|
|
||||||
<td>ng-bind</td>
|
|
||||||
<td>Automatically escapes</td>
|
|
||||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
|
||||||
<td><div ng-bind="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</doc:source>
|
|
||||||
<doc:protractor>
|
|
||||||
it('should sanitize the html snippet by default', function() {
|
|
||||||
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
|
|
||||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should inline raw snippet if bound to a trusted value', function() {
|
|
||||||
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
|
|
||||||
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(element(by.css('#bind-default div')).getInnerHtml()).
|
|
||||||
toBe("<p style=\"color:blue\">an html\n" +
|
|
||||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
|
||||||
"snippet</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update', function() {
|
|
||||||
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:protractor>
|
|
||||||
</doc:example>
|
|
||||||
*/
|
|
||||||
function $SanitizeProvider() {
|
|
||||||
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
|
|
||||||
return function(html) {
|
|
||||||
var 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*>/,
|
|
||||||
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,
|
|
||||||
// Match everything outside of normal chars and " (quote character)
|
|
||||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
|
|
||||||
|
|
||||||
|
|
||||||
// Good source of info about elements and attributes
|
|
||||||
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
|
||||||
// http://simon.html5.org/html-elements
|
|
||||||
|
|
||||||
// Safe Void Elements - HTML5
|
|
||||||
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
|
||||||
var voidElements = makeMap("area,br,col,hr,img,wbr");
|
|
||||||
|
|
||||||
// Elements that you can, intentionally, leave open (and which close themselves)
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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"));
|
|
||||||
|
|
||||||
// Inline Elements - HTML5
|
|
||||||
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
|
|
||||||
"bdi,bdo,big,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"));
|
|
||||||
|
|
||||||
|
|
||||||
// Special Elements (can contain anything)
|
|
||||||
var specialElements = makeMap("script,style");
|
|
||||||
|
|
||||||
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");
|
|
||||||
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,size,span,start,summary,target,title,type,'+
|
|
||||||
'valign,value,vspace,width'));
|
|
||||||
|
|
||||||
function makeMap(str) {
|
|
||||||
var obj = {}, items = str.split(','), i;
|
|
||||||
for (i = 0; i < items.length; i++) obj[items[i]] = true;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example
|
|
||||||
* htmlParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {string} html string
|
|
||||||
* @param {object} handler
|
|
||||||
*/
|
|
||||||
function htmlParser( html, handler ) {
|
|
||||||
var index, chars, match, stack = [], last = html;
|
|
||||||
stack.last = function() { return stack[ stack.length - 1 ]; };
|
|
||||||
|
|
||||||
while ( html ) {
|
|
||||||
chars = true;
|
|
||||||
|
|
||||||
// Make sure we're not in a script or style element
|
|
||||||
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
|
||||||
|
|
||||||
// Comment
|
|
||||||
if ( html.indexOf("<!--") === 0 ) {
|
|
||||||
// comments containing -- are not allowed unless they terminate the comment
|
|
||||||
index = html.indexOf("--", 4);
|
|
||||||
|
|
||||||
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 );
|
|
||||||
|
|
||||||
if ( match ) {
|
|
||||||
html = html.substring( match[0].length );
|
|
||||||
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start tag
|
|
||||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
|
||||||
match = html.match( START_TAG_REGEXP );
|
|
||||||
|
|
||||||
if ( match ) {
|
|
||||||
html = html.substring( match[0].length );
|
|
||||||
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( chars ) {
|
|
||||||
index = html.indexOf("<");
|
|
||||||
|
|
||||||
var text = index < 0 ? html : html.substring( 0, index );
|
|
||||||
html = index < 0 ? "" : html.substring( index );
|
|
||||||
|
|
||||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
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) );
|
|
||||||
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
|
|
||||||
parseEndTag( "", stack.last() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( html == last ) {
|
|
||||||
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
|
|
||||||
"of html: {0}", html);
|
|
||||||
}
|
|
||||||
last = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any remaining tags
|
|
||||||
parseEndTag();
|
|
||||||
|
|
||||||
function parseStartTag( tag, tagName, rest, unary ) {
|
|
||||||
tagName = angular.lowercase(tagName);
|
|
||||||
if ( blockElements[ tagName ] ) {
|
|
||||||
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
|
||||||
parseEndTag( "", stack.last() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
|
|
||||||
parseEndTag( "", tagName );
|
|
||||||
}
|
|
||||||
|
|
||||||
unary = voidElements[ tagName ] || !!unary;
|
|
||||||
|
|
||||||
if ( !unary )
|
|
||||||
stack.push( tagName );
|
|
||||||
|
|
||||||
var attrs = {};
|
|
||||||
|
|
||||||
rest.replace(ATTR_REGEXP,
|
|
||||||
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
|
|
||||||
var value = doubleQuotedValue
|
|
||||||
|| singleQuotedValue
|
|
||||||
|| unquotedValue
|
|
||||||
|| '';
|
|
||||||
|
|
||||||
attrs[name] = decodeEntities(value);
|
|
||||||
});
|
|
||||||
if (handler.start) handler.start( tagName, attrs, unary );
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEndTag( tag, tagName ) {
|
|
||||||
var pos = 0, i;
|
|
||||||
tagName = angular.lowercase(tagName);
|
|
||||||
if ( tagName )
|
|
||||||
// Find the closest opened tag of the same type
|
|
||||||
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
|
||||||
if ( stack[ pos ] == tagName )
|
|
||||||
break;
|
|
||||||
|
|
||||||
if ( pos >= 0 ) {
|
|
||||||
// Close all the open elements, up the stack
|
|
||||||
for ( i = stack.length - 1; i >= pos; i-- )
|
|
||||||
if (handler.end) handler.end( stack[ i ] );
|
|
||||||
|
|
||||||
// Remove the open elements from the stack
|
|
||||||
stack.length = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
function decodeEntities(value) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes all potentially dangerous characters, so that the
|
|
||||||
* resulting string can be safely inserted into attribute or
|
|
||||||
* element text.
|
|
||||||
* @param value
|
|
||||||
* @returns escaped text
|
|
||||||
*/
|
|
||||||
function encodeEntities(value) {
|
|
||||||
return value.
|
|
||||||
replace(/&/g, '&').
|
|
||||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
|
||||||
return '&#' + value.charCodeAt(0) + ';';
|
|
||||||
}).
|
|
||||||
replace(/</g, '<').
|
|
||||||
replace(/>/g, '>');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create an HTML/XML writer which writes to buffer
|
|
||||||
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
|
||||||
* @returns {object} in the form of {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
function htmlSanitizeWriter(buf, uriValidator){
|
|
||||||
var ignore = false;
|
|
||||||
var out = angular.bind(buf, buf.push);
|
|
||||||
return {
|
|
||||||
start: function(tag, attrs, unary){
|
|
||||||
tag = angular.lowercase(tag);
|
|
||||||
if (!ignore && specialElements[tag]) {
|
|
||||||
ignore = tag;
|
|
||||||
}
|
|
||||||
if (!ignore && validElements[tag] === true) {
|
|
||||||
out('<');
|
|
||||||
out(tag);
|
|
||||||
angular.forEach(attrs, function(value, key){
|
|
||||||
var lkey=angular.lowercase(key);
|
|
||||||
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
|
|
||||||
if (validAttrs[lkey] === true &&
|
|
||||||
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
|
|
||||||
out(' ');
|
|
||||||
out(key);
|
|
||||||
out('="');
|
|
||||||
out(encodeEntities(value));
|
|
||||||
out('"');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
out(unary ? '/>' : '>');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end: function(tag){
|
|
||||||
tag = angular.lowercase(tag);
|
|
||||||
if (!ignore && validElements[tag] === true) {
|
|
||||||
out('</');
|
|
||||||
out(tag);
|
|
||||||
out('>');
|
|
||||||
}
|
|
||||||
if (tag == ignore) {
|
|
||||||
ignore = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chars: function(chars){
|
|
||||||
if (!ignore) {
|
|
||||||
out(encodeEntities(chars));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// define ngSanitize module and register $sanitize service
|
|
||||||
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
||||||
|
|
||||||
/* global sanitizeText: false */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc filter
|
|
||||||
* @name ngSanitize.filter:linky
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
|
||||||
* plain email address links.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
|
|
||||||
*
|
|
||||||
* @param {string} text Input text.
|
|
||||||
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
|
|
||||||
* @returns {string} Html-linkified text.
|
|
||||||
*
|
|
||||||
* @usage
|
|
||||||
<span ng-bind-html="linky_expression | linky"></span>
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example module="ngSanitize">
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function Ctrl($scope) {
|
|
||||||
$scope.snippet =
|
|
||||||
'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/.';
|
|
||||||
$scope.snippetWithTarget = 'http://angularjs.org/';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div ng-controller="Ctrl">
|
|
||||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Filter</td>
|
|
||||||
<td>Source</td>
|
|
||||||
<td>Rendered</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="linky-filter">
|
|
||||||
<td>linky filter</td>
|
|
||||||
<td>
|
|
||||||
<pre><div ng-bind-html="snippet | linky"><br></div></pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div ng-bind-html="snippet | linky"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="linky-target">
|
|
||||||
<td>linky target</td>
|
|
||||||
<td>
|
|
||||||
<pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="escaped-html">
|
|
||||||
<td>no filter</td>
|
|
||||||
<td><pre><div ng-bind="snippet"><br></div></pre></td>
|
|
||||||
<td><div ng-bind="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</doc:source>
|
|
||||||
<doc:protractor>
|
|
||||||
it('should linkify the snippet with urls', function() {
|
|
||||||
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(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() {
|
|
||||||
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(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:protractor>
|
|
||||||
</doc:example>
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
if (!text) return text;
|
|
||||||
var match;
|
|
||||||
var raw = text;
|
|
||||||
var html = [];
|
|
||||||
var url;
|
|
||||||
var i;
|
|
||||||
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;
|
|
||||||
addText(raw.substr(0, i));
|
|
||||||
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
|
|
||||||
raw = raw.substring(i + match[0].length);
|
|
||||||
}
|
|
||||||
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);
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for jstd scenario adapter
|
|
||||||
*/
|
|
||||||
var jstdScenarioAdapter = {
|
|
||||||
relativeUrlPrefix: '/build/docs/'
|
|
||||||
};
|
|
|
@ -1,202 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.5
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSTestDriver adapter for angular scenario tests
|
|
||||||
*
|
|
||||||
* Example of jsTestDriver.conf for running scenario tests with JSTD:
|
|
||||||
<pre>
|
|
||||||
server: http://localhost:9877
|
|
||||||
|
|
||||||
load:
|
|
||||||
- lib/angular-scenario.js
|
|
||||||
- lib/jstd-scenario-adapter-config.js
|
|
||||||
- lib/jstd-scenario-adapter.js
|
|
||||||
# your test files go here #
|
|
||||||
|
|
||||||
proxy:
|
|
||||||
- {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
|
|
||||||
* Note the order of files - it's important !
|
|
||||||
*
|
|
||||||
* Example of jstd-scenario-adapter-config.js
|
|
||||||
<pre>
|
|
||||||
var jstdScenarioAdapter = {
|
|
||||||
relativeUrlPrefix: '/your-prefix/'
|
|
||||||
};
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
|
|
||||||
* You have to configure this to work together with JSTD proxy.
|
|
||||||
*
|
|
||||||
* Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
|
|
||||||
* Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
|
|
||||||
* That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom type of test case
|
|
||||||
*
|
|
||||||
* @const
|
|
||||||
* @see jstestdriver.TestCaseInfo
|
|
||||||
*/
|
|
||||||
var SCENARIO_TYPE = 'scenario';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin for JSTestDriver
|
|
||||||
* Connection point between scenario's jstd output and jstestdriver.
|
|
||||||
*
|
|
||||||
* @see jstestdriver.PluginRegistrar
|
|
||||||
*/
|
|
||||||
function JstdPlugin() {
|
|
||||||
var nop = function() {};
|
|
||||||
|
|
||||||
this.reportResult = nop;
|
|
||||||
this.reportEnd = nop;
|
|
||||||
this.runScenario = nop;
|
|
||||||
|
|
||||||
this.name = 'Angular Scenario Adapter';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called for each JSTD TestCase
|
|
||||||
*
|
|
||||||
* Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
|
|
||||||
* Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
|
|
||||||
*
|
|
||||||
* @param {jstestdriver.TestRunConfiguration} configuration
|
|
||||||
* @param {Function} onTestDone
|
|
||||||
* @param {Function} onAllTestsComplete
|
|
||||||
* @returns {boolean} True if this type of test is handled by this plugin, false otherwise
|
|
||||||
*/
|
|
||||||
this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
|
|
||||||
if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
|
|
||||||
|
|
||||||
this.reportResult = onTestDone;
|
|
||||||
this.reportEnd = onAllTestsComplete;
|
|
||||||
this.runScenario();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
|
|
||||||
testRunsConfiguration.push(
|
|
||||||
new jstestdriver.TestRunConfiguration(
|
|
||||||
new jstestdriver.TestCaseInfo(
|
|
||||||
'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton instance of the plugin
|
|
||||||
* Accessed using closure by:
|
|
||||||
* - jstd output (reports to this plugin)
|
|
||||||
* - initScenarioAdapter (register the plugin to jstd)
|
|
||||||
*/
|
|
||||||
var plugin = new JstdPlugin();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise scenario jstd-adapter
|
|
||||||
* (only if jstestdriver is defined)
|
|
||||||
*
|
|
||||||
* @param {Object} jstestdriver Undefined when run from browser (without jstd)
|
|
||||||
* @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
|
|
||||||
* @param {Object=} config Configuration object, supported properties:
|
|
||||||
* - relativeUrlPrefix: prefix for all relative links when navigateTo()
|
|
||||||
*/
|
|
||||||
function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
|
|
||||||
if (jstestdriver) {
|
|
||||||
// create and register ScenarioPlugin
|
|
||||||
jstestdriver.pluginRegistrar.register(plugin);
|
|
||||||
plugin.runScenario = initScenarioAndRun;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HACK (angular.scenario.Application.navigateTo)
|
|
||||||
*
|
|
||||||
* We need to navigate to relative urls when running from browser (without JSTD),
|
|
||||||
* because we want to allow running scenario tests without creating its own virtual host.
|
|
||||||
* For example: http://angular.local/build/docs/docs-scenario.html
|
|
||||||
*
|
|
||||||
* On the other hand, when running with JSTD, we need to navigate to absolute urls,
|
|
||||||
* because of JSTD proxy. (proxy, because of same domain policy)
|
|
||||||
*
|
|
||||||
* So this hack is applied only if running with JSTD and change all relative urls to absolute.
|
|
||||||
*/
|
|
||||||
var appProto = angular.scenario.Application.prototype,
|
|
||||||
navigateTo = appProto.navigateTo,
|
|
||||||
relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
|
|
||||||
|
|
||||||
appProto.navigateTo = function(url, loadFn, errorFn) {
|
|
||||||
if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
|
|
||||||
url != 'about:blank' && !url.match(/^https?/)) {
|
|
||||||
url = relativeUrlPrefix + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return navigateTo.call(this, url, loadFn, errorFn);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds proper TestResult object from given model spec
|
|
||||||
*
|
|
||||||
* TODO(vojta) report error details
|
|
||||||
*
|
|
||||||
* @param {angular.scenario.ObjectModel.Spec} spec
|
|
||||||
* @returns {jstestdriver.TestResult}
|
|
||||||
*/
|
|
||||||
function createTestResultFromSpec(spec) {
|
|
||||||
var map = {
|
|
||||||
success: 'PASSED',
|
|
||||||
error: 'ERROR',
|
|
||||||
failure: 'FAILED'
|
|
||||||
};
|
|
||||||
|
|
||||||
return new jstestdriver.TestResult(
|
|
||||||
spec.fullDefinitionName,
|
|
||||||
spec.name,
|
|
||||||
jstestdriver.TestResult.RESULT[map[spec.status]],
|
|
||||||
spec.error || '',
|
|
||||||
spec.line || '',
|
|
||||||
spec.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates JSTD output (jstestdriver.TestResult)
|
|
||||||
*/
|
|
||||||
angular.scenario.output('jstd', function(context, runner, model) {
|
|
||||||
model.on('SpecEnd', function(spec) {
|
|
||||||
plugin.reportResult(createTestResultFromSpec(spec));
|
|
||||||
});
|
|
||||||
|
|
||||||
model.on('RunnerEnd', function() {
|
|
||||||
plugin.reportEnd();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
|
|
||||||
})(window);
|
|
|
@ -1,239 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhanced Select2 Dropmenus
|
|
||||||
*
|
|
||||||
* @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
|
|
||||||
* This change is so that you do not have to do an additional query yourself on top of Select2's own query
|
|
||||||
* @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
|
|
||||||
*/
|
|
||||||
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
|
|
||||||
var options = {};
|
|
||||||
if (uiSelect2Config) {
|
|
||||||
angular.extend(options, uiSelect2Config);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
require: 'ngModel',
|
|
||||||
priority: 1,
|
|
||||||
compile: function (tElm, tAttrs) {
|
|
||||||
var watch,
|
|
||||||
repeatOption,
|
|
||||||
repeatAttr,
|
|
||||||
isSelect = tElm.is('select'),
|
|
||||||
isMultiple = angular.isDefined(tAttrs.multiple);
|
|
||||||
|
|
||||||
// Enable watching of the options dataset if in use
|
|
||||||
if (tElm.is('select')) {
|
|
||||||
repeatOption = tElm.find( 'optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]');
|
|
||||||
|
|
||||||
if (repeatOption.length) {
|
|
||||||
repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
|
|
||||||
watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function (scope, elm, attrs, controller) {
|
|
||||||
// instance-specific options
|
|
||||||
var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert from Select2 view-model to Angular view-model.
|
|
||||||
*/
|
|
||||||
var convertToAngularModel = function(select2_data) {
|
|
||||||
var model;
|
|
||||||
if (opts.simple_tags) {
|
|
||||||
model = [];
|
|
||||||
angular.forEach(select2_data, function(value, index) {
|
|
||||||
model.push(value.id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
model = select2_data;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert from Angular view-model to Select2 view-model.
|
|
||||||
*/
|
|
||||||
var convertToSelect2Model = function(angular_data) {
|
|
||||||
var model = [];
|
|
||||||
if (!angular_data) {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.simple_tags) {
|
|
||||||
model = [];
|
|
||||||
angular.forEach(
|
|
||||||
angular_data,
|
|
||||||
function(value, index) {
|
|
||||||
model.push({'id': value, 'text': value});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
model = angular_data;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isSelect) {
|
|
||||||
// Use <select multiple> instead
|
|
||||||
delete opts.multiple;
|
|
||||||
delete opts.initSelection;
|
|
||||||
} else if (isMultiple) {
|
|
||||||
opts.multiple = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller) {
|
|
||||||
// Watch the model for programmatic changes
|
|
||||||
scope.$watch(tAttrs.ngModel, function(current, old) {
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (current === old) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
controller.$render();
|
|
||||||
}, true);
|
|
||||||
controller.$render = function () {
|
|
||||||
if (isSelect) {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
} else {
|
|
||||||
if (opts.multiple) {
|
|
||||||
var viewValue = controller.$viewValue;
|
|
||||||
if (angular.isString(viewValue)) {
|
|
||||||
viewValue = viewValue.split(',');
|
|
||||||
}
|
|
||||||
elm.select2(
|
|
||||||
'data', convertToSelect2Model(viewValue));
|
|
||||||
} else {
|
|
||||||
if (angular.isObject(controller.$viewValue)) {
|
|
||||||
elm.select2('data', controller.$viewValue);
|
|
||||||
} else if (!controller.$viewValue) {
|
|
||||||
elm.select2('data', null);
|
|
||||||
} else {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Watch the options dataset for changes
|
|
||||||
if (watch) {
|
|
||||||
scope.$watch(watch, function (newVal, oldVal, scope) {
|
|
||||||
if (angular.equals(newVal, oldVal)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Delayed so that the options have time to be rendered
|
|
||||||
$timeout(function () {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
// Refresh angular to remove the superfluous option
|
|
||||||
elm.trigger('change');
|
|
||||||
if(newVal && !oldVal && controller.$setPristine) {
|
|
||||||
controller.$setPristine(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update valid and dirty statuses
|
|
||||||
controller.$parsers.push(function (value) {
|
|
||||||
var div = elm.prev();
|
|
||||||
div
|
|
||||||
.toggleClass('ng-invalid', !controller.$valid)
|
|
||||||
.toggleClass('ng-valid', controller.$valid)
|
|
||||||
.toggleClass('ng-invalid-required', !controller.$valid)
|
|
||||||
.toggleClass('ng-valid-required', controller.$valid)
|
|
||||||
.toggleClass('ng-dirty', controller.$dirty)
|
|
||||||
.toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isSelect) {
|
|
||||||
// Set the view and model value and update the angular template manually for the ajax/multiple select2.
|
|
||||||
elm.bind("change", function (e) {
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
|
|
||||||
if (scope.$$phase || scope.$root.$$phase) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scope.$apply(function () {
|
|
||||||
controller.$setViewValue(
|
|
||||||
convertToAngularModel(elm.select2('data')));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.initSelection) {
|
|
||||||
var initSelection = opts.initSelection;
|
|
||||||
opts.initSelection = function (element, callback) {
|
|
||||||
initSelection(element, function (value) {
|
|
||||||
var isPristine = controller.$pristine;
|
|
||||||
controller.$setViewValue(convertToAngularModel(value));
|
|
||||||
callback(value);
|
|
||||||
if (isPristine) {
|
|
||||||
controller.$setPristine();
|
|
||||||
}
|
|
||||||
elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elm.bind("$destroy", function() {
|
|
||||||
elm.select2("destroy");
|
|
||||||
});
|
|
||||||
|
|
||||||
attrs.$observe('disabled', function (value) {
|
|
||||||
elm.select2('enable', !value);
|
|
||||||
});
|
|
||||||
|
|
||||||
attrs.$observe('readonly', function (value) {
|
|
||||||
elm.select2('readonly', !!value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (attrs.ngMultiple) {
|
|
||||||
scope.$watch(attrs.ngMultiple, function(newVal) {
|
|
||||||
attrs.$set('multiple', !!newVal);
|
|
||||||
elm.select2(opts);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the plugin late so that the injected DOM does not disrupt the template compiler
|
|
||||||
$timeout(function () {
|
|
||||||
elm.select2(opts);
|
|
||||||
|
|
||||||
// Set initial value - I'm not sure about this but it seems to need to be there
|
|
||||||
elm.select2('data', controller.$modelValue);
|
|
||||||
// important!
|
|
||||||
controller.$render();
|
|
||||||
|
|
||||||
// Not sure if I should just check for !isSelect OR if I should check for 'tags' key
|
|
||||||
if (!opts.initSelection && !isSelect) {
|
|
||||||
var isPristine = controller.$pristine;
|
|
||||||
controller.$setViewValue(
|
|
||||||
convertToAngularModel(elm.select2('data'))
|
|
||||||
);
|
|
||||||
if (isPristine) {
|
|
||||||
controller.$setPristine();
|
|
||||||
}
|
|
||||||
elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
||||||
{"full":"1.0.7","major":"1","minor":"0","dot":"7","codename":"monochromatic-rainbow","cdn":"1.0.6"}
|
|
|
@ -1 +0,0 @@
|
||||||
1.0.7
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"realm" : "google-identity-provider-realm",
|
|
||||||
"resource" : "google-authentication",
|
|
||||||
"auth-server-url": "/auth",
|
|
||||||
"ssl-required" : "external",
|
|
||||||
"public-client" : true
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-examples-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Broker Examples</name>
|
|
||||||
<description/>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-examples-broker-parent</artifactId>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<modules>
|
|
||||||
<module>facebook-authentication</module>
|
|
||||||
<module>google-authentication</module>
|
|
||||||
<module>saml-broker-authentication</module>
|
|
||||||
<module>twitter-authentication</module>
|
|
||||||
</modules>
|
|
||||||
</project>
|
|
|
@ -1,219 +0,0 @@
|
||||||
# Keycloak Broker: Brokering a Keycloak SAML v2 Identity Provider Quickstart
|
|
||||||
|
|
||||||
What is it?
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This example demonstrates how to broker a SAML Identity Provider in Keycloak. In this case, the SAML Identity Provider
|
|
||||||
belongs to a different realm than the application and we want to trust users from one realm to authenticate and access the
|
|
||||||
applications in another realm.
|
|
||||||
|
|
||||||
There are two main realms in this example:
|
|
||||||
|
|
||||||
* **saml-broker-realm.json**: the realm where the user belongs and that provides a SAML v2 Identity Provider.
|
|
||||||
* **saml-broker-authentication-realm.json**: the realm with all the necessary configuration to set up the application and the
|
|
||||||
identity provider responsible for brokering.
|
|
||||||
|
|
||||||
From this example you'll understand how to set up an identity provider in order to broker an external
|
|
||||||
SAML identity provider and allow users from different domains/realms to authenticate and access applications in a realm.
|
|
||||||
|
|
||||||
The *saml-broker-realm* realm provides two important configuration. The first one is the user that we are going to use to
|
|
||||||
authenticate with the SAML Identity Provider.
|
|
||||||
|
|
||||||
{
|
|
||||||
"username" : "user",
|
|
||||||
"enabled": true,
|
|
||||||
"email" : "user@saml-broker-realm",
|
|
||||||
"firstName": "User",
|
|
||||||
"lastName": "SAML",
|
|
||||||
"credentials" : [
|
|
||||||
{ "type" : "password",
|
|
||||||
"value" : "password" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
The second one is an application to configure a SAML Identity Provider to authenticate requests from the other realm.
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "http://localhost:8080/auth/",
|
|
||||||
"enabled": true,
|
|
||||||
"redirectUris": [
|
|
||||||
"http://localhost:8080/auth/broker/saml-broker-authentication-realm/saml-identity-provider"
|
|
||||||
],
|
|
||||||
"attributes": {
|
|
||||||
"saml.assertion.signature": "true",
|
|
||||||
"saml.server.signature": "true",
|
|
||||||
"saml.signature.algorithm": "RSA_SHA256",
|
|
||||||
"saml.client.signature": "true",
|
|
||||||
"saml.authnstatement": "true",
|
|
||||||
"saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
|
|
||||||
"saml.signing.certificate": "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
The *saml-broker-authentication-realm.json* realm provides the configuration for the application and also the necessary
|
|
||||||
configuration to set up an identity provider to broker the SAML Identity Provider from the other realm.
|
|
||||||
|
|
||||||
{
|
|
||||||
"id" : "saml-identity-provider",
|
|
||||||
"providerId" : "saml",
|
|
||||||
"name" : "SAML v2 Identity Provider",
|
|
||||||
"enabled": true,
|
|
||||||
"updateProfileFirstLogin" : "true",
|
|
||||||
"storeToken" : "true",
|
|
||||||
"addReadTokenRoleOnCreate": true,
|
|
||||||
"config": {
|
|
||||||
"singleSignOnServiceUrl": "http://localhost:8080/auth/realms/saml-broker-realm/protocol/saml",
|
|
||||||
"singleLogoutServiceUrl": "http://localhost:8080/auth/realms/saml-broker-realm/protocol/saml",
|
|
||||||
"nameIDPolicyFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
||||||
"signingCertificate": "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin",
|
|
||||||
"wantAuthnRequestsSigned": true,
|
|
||||||
"forceAuthn": true,
|
|
||||||
"validateSignature": true,
|
|
||||||
"postBindingResponse": true,
|
|
||||||
"postBindingAuthnRequest": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Basically, once you try to access the application for the first time, you'll be redirected to *saml-broker-authentication-realm*'s login page.
|
|
||||||
In this page you'll note that there is a "SAML v2 Identity Provider" button that allows you to authenticate with the SAML Identity Provider
|
|
||||||
in *saml-broker-realm* realm.
|
|
||||||
|
|
||||||
After clicking that button, you'll be redirected to *saml-broker-realm*'s login page from where you must authenticate using the
|
|
||||||
credentials for the default user:
|
|
||||||
|
|
||||||
Username: user
|
|
||||||
Password: password
|
|
||||||
|
|
||||||
If everything is fine, the *saml-broker-realm* realm will redirect you back to *saml-broker-authentication-realm* and at
|
|
||||||
this point you'll be asked to provide some basic profile information in order to create a new user in *saml-broker-authentication-realm* based
|
|
||||||
on the information returned by the SAML Identity Provider.
|
|
||||||
Once you update your profile, you'll be authenticated and redirected to the application.
|
|
||||||
|
|
||||||
At the end, the *user* user is now federated and can access the application in *saml-broker-authentication-realm* realm.
|
|
||||||
|
|
||||||
Import the necessary realms
|
|
||||||
--------------------------------------
|
|
||||||
Next thing you have to do is import the test realm for the demo. Clicking on the below link will bring you to the
|
|
||||||
create realm page in the Admin UI. The username/password is admin/admin to login in. Keycloak will ask you to
|
|
||||||
create a new admin password before you can go to the create realm page.
|
|
||||||
|
|
||||||
[http://localhost:8080/auth/admin/master/console/#/create/realm](http://localhost:8080/auth/admin/master/console/#/create/realm)
|
|
||||||
|
|
||||||
Import the following realms:
|
|
||||||
|
|
||||||
* **saml-broker-authentication-realm.json**
|
|
||||||
* **saml-broker-realm.json**
|
|
||||||
|
|
||||||
Make sure you've set up the Keycloak Server
|
|
||||||
--------------------------------------
|
|
||||||
The Keycloak Appliance Distribution comes with a preconfigured Keycloak server (based on Wildfly). You can use it out of
|
|
||||||
the box to run these demos. So, if you're using this, you can head to Step 2.
|
|
||||||
|
|
||||||
Alternatively, you can install the Keycloak Server onto any EAP 6.x, or Wildfly 8.x server, but there is
|
|
||||||
a few steps you must follow.
|
|
||||||
|
|
||||||
Obtain latest keycloak-war-dist-all.zip. This distro is used to install Keycloak onto an existing JBoss installation.
|
|
||||||
This installs the server.
|
|
||||||
|
|
||||||
$ cd ${wildfly.jboss.home}/standalone
|
|
||||||
$ cp -r ${keycloak-war-dist-all}/deployments .
|
|
||||||
|
|
||||||
To be able to run the demos you also need to install the Keycloak client adapter. For Wildfly:
|
|
||||||
|
|
||||||
$ cd ${wildfly.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-wildfly-adapter-dist.zip
|
|
||||||
|
|
||||||
For JBoss EAP 6.x
|
|
||||||
|
|
||||||
$ cd ${eap.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-eap6-adapter-dist.zip
|
|
||||||
|
|
||||||
For JBoss AS 7.1.1:
|
|
||||||
|
|
||||||
$ cd ${as7.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-as7-adapter-dist.zip
|
|
||||||
|
|
||||||
Unzipping the adapter ZIP only installs the JAR files. You must also add the Keycloak Subsystem to the server's
|
|
||||||
configuration (standalone/configuration/standalone.xml).
|
|
||||||
|
|
||||||
<server xmlns="urn:jboss:domain:1.4">
|
|
||||||
|
|
||||||
<extensions>
|
|
||||||
<extension module="org.keycloak.keycloak-subsystem"/>
|
|
||||||
...
|
|
||||||
</extensions>
|
|
||||||
|
|
||||||
<profile>
|
|
||||||
<subsystem xmlns="urn:jboss:domain:keycloak:1.0"/>
|
|
||||||
...
|
|
||||||
</profile>
|
|
||||||
|
|
||||||
Boot Keycloak Server
|
|
||||||
---------------------------------------
|
|
||||||
Where you go to start up the Keycloak Server depends on which distro you installed.
|
|
||||||
|
|
||||||
From appliance:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd keycloak/bin
|
|
||||||
$ ./standalone.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
From existing Wildfly/EAP6/AS7 distro
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd ${wildfly.jboss.home}/bin
|
|
||||||
$ ./standalone.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Start JBoss Enterprise Application Platform 6 or WildFly with the Web Profile
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
1. Open a command line and navigate to the root of the JBoss server directory.
|
|
||||||
2. The following shows the command line to start the server with the web profile:
|
|
||||||
|
|
||||||
For Linux: JBOSS_HOME/bin/standalone.sh
|
|
||||||
For Windows: JBOSS_HOME\bin\standalone.bat
|
|
||||||
|
|
||||||
|
|
||||||
Build and Deploy the Quickstart
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
_NOTE: The following build command assumes you have configured your Maven user settings. If you have not, you must include Maven setting arguments on the command line. See [Build and Deploy the Quickstarts](../README.md#build-and-deploy-the-quickstarts) for complete instructions and additional options._
|
|
||||||
|
|
||||||
1. Make sure you have started the JBoss Server as described above.
|
|
||||||
2. Open a command line and navigate to the root directory of this quickstart.
|
|
||||||
3. Type this command to build and deploy the archive:
|
|
||||||
|
|
||||||
For EAP 6: mvn clean package jboss-as:deploy
|
|
||||||
For WildFly: mvn clean package wildfly:deploy
|
|
||||||
|
|
||||||
4. This will deploy `target/saml-broker-authentication.war` to the running instance of the server.
|
|
||||||
|
|
||||||
|
|
||||||
Access the application
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The application will be running at the following URL: <http://localhost:8080/saml-broker-authentication>.
|
|
||||||
|
|
||||||
|
|
||||||
Undeploy the Archive
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
1. Make sure you have started the JBoss Server as described above.
|
|
||||||
2. Open a command line and navigate to the root directory of this quickstart.
|
|
||||||
3. When you are finished testing, type this command to undeploy the archive:
|
|
||||||
|
|
||||||
For EAP 6: mvn jboss-as:undeploy
|
|
||||||
For WildFly: mvn -Pwildfly wildfly:undeploy
|
|
||||||
|
|
||||||
|
|
||||||
Debug the Application
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
If you want to debug the source code or look at the Javadocs of any library in the project, run either of the following commands to pull them into your local repository. The IDE should then detect them.
|
|
||||||
|
|
||||||
mvn dependency:sources
|
|
||||||
mvn dependency:resolve -Dclassifier=javadoc
|
|
|
@ -1,56 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-examples-broker-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Keycloak Broker Examples - SAML Identity Provider Brokering</name>
|
|
||||||
<artifactId>saml-broker-authentication</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<description>
|
|
||||||
An example about how to broker a SAML v2 Identity Provider
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>saml-broker-authentication</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,69 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "saml-broker-authentication-realm",
|
|
||||||
"enabled": true,
|
|
||||||
"sslRequired": "external",
|
|
||||||
"privateKey": "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCCPyvTTb14vSMkpe/pds2P5Cqxk7bkeFnQiNMS1vyZ+HS2O79fxzp1eAguHnBTs4XTRT7SZJhIT/6utgqZjmDigKV5N7X5ptq8BM/W1qa1cYBRip261pc+tWf3IywJYQ9yFI9mUQarmIEl0D7GH16NSZklheaWfbodRVarvX+ML0amNtGYVDft/RftYmgbKKrK218qQp9R4GZFtf/Q/RmboNXN7weMINU8GWVkTRrccKBIXSunT6zXGfuj3Wp1YpVq20BWwY2OMM/P+yDAc7LKEO1LJqPBdT4r9BRn2lXiaga3AL24gTKZPKU/tu7uqfFciF+i4Rr58SMDNOzQcnklAgMBAAECggEAc0eibJYEO5d8QXW1kPgcHV2gBChv2mxDYnWYDLbIQSdNdfYP/qABt/MTmm5KkWr16fcCEYoD1w0mqFBrtVn1msSusUmEAYGTXJMNumOmjjX1kzaTQMmqeFBrwqwYz/xehWR5P+A7fSmwNV3KEeW19GvN5w5K96w0TLAQdFV3TQVPSytusDunwuR1yltMe1voaEDZ9z0Pi08YiEk2f6xhj5CMkoiw3mNImzfruphHullxU4FD05fH6tDeJ381527ILpAzDsgYZh4aFLKjUHem96bX4EL7FIzBJ6okgN78AZnUC/EaVfgFTw0qfhoWvZV4ruVXXiMhCg4CMMRDq/k9iQKBgQDBNWsJMT84OnnWmQoJmZogkFV+tsGrSK6Re+aJxLWpishh7dwAnT2OcagZvVdUb0FwNWu1D0B9/SKDDMRnnHBhOGDpH57m/eQdRU0oX1BD27xvffk0lLcfD4BTxnR5e9jss8K4twc9jf0P1rxC/loGJ2NtCH0BrPHgz54Ea+96ewKBgQCsk3JDaaPnFwzVYm2BXlhxOxLPsF4wvD2rIRAswZV4C5xebjand8nwiMmVpNd0PRLkEnkI+waURGv2EY/P3JsssoiY8Xqe8f/1G+SQKre7lbqOas8rFoALepC0BYDiZDFy0Z9ZnRAFzRI5sgIt7jpoMRD4xDNlmiV8X+yBxc3Y3wKBgQChDQsU1YUyNKQ8+sLAL9anEEkD4Ald4q8JPHN2IY+gLLxNzT0XEfsu0pTiJ8805axxgUYv3e/PVYNAJBNPnrqaf6lgiegl+jr9Hzhqz9CTUAYqFaL2boSakoxQyNtsLI0s+cb1vDN/3uy0GDZDzcty18BsMagqDmRtFgNNAj/UIwKBgQCahbeFBv0cOPZjxisY8Bou4N8aGehsqNBq/0LVYExuXa8YmoTTdJ3bgw9Er4G/ccQNdUDsuqAMeCtW/CiRzQ0ge4d1sprB4Rv3I4+HSsiS7SFKzfZLtWzXWlpg5qCdlWr1TR7qhYjIOPO9t1beO3YOvwhcRoliyyAPenBxTmTfbwKBgDtm2WJ5VlQgNpIdOs1CCiqd0DFmWOmvBPspPC1kySiy+Ndr9jNohRZkR7pEjgqA5E8rdzc88LirUN7bY5HFHRWN9KXrs5/o3O1K3GFCp64N6nvnPEYZ2zSJalcMC2fjSsJg26z8Dg1H+gfTIDUMoGiEAAnJXuqk+WayPU+fZMLn",
|
|
||||||
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQAB",
|
|
||||||
"defaultRoles": [ "user" ],
|
|
||||||
"users" : [
|
|
||||||
{
|
|
||||||
"username" : "admin",
|
|
||||||
"enabled": true,
|
|
||||||
"email" : "admin@admin.com",
|
|
||||||
"firstName": "Admin",
|
|
||||||
"lastName": "Burke",
|
|
||||||
"credentials" : [
|
|
||||||
{ "type" : "password",
|
|
||||||
"value" : "password" }
|
|
||||||
],
|
|
||||||
"realmRoles": [ "user","admin" ],
|
|
||||||
"clientRoles": {
|
|
||||||
"realm-management": [ "realm-admin" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles" : {
|
|
||||||
"realm" : [
|
|
||||||
{
|
|
||||||
"name": "user",
|
|
||||||
"description": "User privileges"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"clients": [
|
|
||||||
{
|
|
||||||
"clientId": "saml-broker-authentication",
|
|
||||||
"enabled": true,
|
|
||||||
"publicClient" : true,
|
|
||||||
"adminUrl": "/saml-broker-authentication",
|
|
||||||
"baseUrl": "/saml-broker-authentication",
|
|
||||||
"redirectUris": [
|
|
||||||
"/saml-broker-authentication/*"
|
|
||||||
],
|
|
||||||
"webOrigins": [
|
|
||||||
"http://localhost:8080"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"identityProviders": [
|
|
||||||
{
|
|
||||||
"alias" : "saml-identity-provider",
|
|
||||||
"providerId" : "saml",
|
|
||||||
"enabled": true,
|
|
||||||
"updateProfileFirstLogin" : "true",
|
|
||||||
"storeToken" : "true",
|
|
||||||
"addReadTokenRoleOnCreate": true,
|
|
||||||
"config": {
|
|
||||||
"singleSignOnServiceUrl": "http://localhost:8080/auth/realms/saml-broker-realm/protocol/saml",
|
|
||||||
"singleLogoutServiceUrl": "http://localhost:8080/auth/realms/saml-broker-realm/protocol/saml",
|
|
||||||
"nameIDPolicyFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
||||||
"signingCertificate": "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin",
|
|
||||||
"wantAuthnRequestsSigned": true,
|
|
||||||
"forceAuthn": true,
|
|
||||||
"validateSignature": true,
|
|
||||||
"postBindingResponse": true,
|
|
||||||
"postBindingAuthnRequest": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "saml-broker-realm",
|
|
||||||
"enabled": true,
|
|
||||||
"sslRequired": "external",
|
|
||||||
"privateKey": "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCCPyvTTb14vSMkpe/pds2P5Cqxk7bkeFnQiNMS1vyZ+HS2O79fxzp1eAguHnBTs4XTRT7SZJhIT/6utgqZjmDigKV5N7X5ptq8BM/W1qa1cYBRip261pc+tWf3IywJYQ9yFI9mUQarmIEl0D7GH16NSZklheaWfbodRVarvX+ML0amNtGYVDft/RftYmgbKKrK218qQp9R4GZFtf/Q/RmboNXN7weMINU8GWVkTRrccKBIXSunT6zXGfuj3Wp1YpVq20BWwY2OMM/P+yDAc7LKEO1LJqPBdT4r9BRn2lXiaga3AL24gTKZPKU/tu7uqfFciF+i4Rr58SMDNOzQcnklAgMBAAECggEAc0eibJYEO5d8QXW1kPgcHV2gBChv2mxDYnWYDLbIQSdNdfYP/qABt/MTmm5KkWr16fcCEYoD1w0mqFBrtVn1msSusUmEAYGTXJMNumOmjjX1kzaTQMmqeFBrwqwYz/xehWR5P+A7fSmwNV3KEeW19GvN5w5K96w0TLAQdFV3TQVPSytusDunwuR1yltMe1voaEDZ9z0Pi08YiEk2f6xhj5CMkoiw3mNImzfruphHullxU4FD05fH6tDeJ381527ILpAzDsgYZh4aFLKjUHem96bX4EL7FIzBJ6okgN78AZnUC/EaVfgFTw0qfhoWvZV4ruVXXiMhCg4CMMRDq/k9iQKBgQDBNWsJMT84OnnWmQoJmZogkFV+tsGrSK6Re+aJxLWpishh7dwAnT2OcagZvVdUb0FwNWu1D0B9/SKDDMRnnHBhOGDpH57m/eQdRU0oX1BD27xvffk0lLcfD4BTxnR5e9jss8K4twc9jf0P1rxC/loGJ2NtCH0BrPHgz54Ea+96ewKBgQCsk3JDaaPnFwzVYm2BXlhxOxLPsF4wvD2rIRAswZV4C5xebjand8nwiMmVpNd0PRLkEnkI+waURGv2EY/P3JsssoiY8Xqe8f/1G+SQKre7lbqOas8rFoALepC0BYDiZDFy0Z9ZnRAFzRI5sgIt7jpoMRD4xDNlmiV8X+yBxc3Y3wKBgQChDQsU1YUyNKQ8+sLAL9anEEkD4Ald4q8JPHN2IY+gLLxNzT0XEfsu0pTiJ8805axxgUYv3e/PVYNAJBNPnrqaf6lgiegl+jr9Hzhqz9CTUAYqFaL2boSakoxQyNtsLI0s+cb1vDN/3uy0GDZDzcty18BsMagqDmRtFgNNAj/UIwKBgQCahbeFBv0cOPZjxisY8Bou4N8aGehsqNBq/0LVYExuXa8YmoTTdJ3bgw9Er4G/ccQNdUDsuqAMeCtW/CiRzQ0ge4d1sprB4Rv3I4+HSsiS7SFKzfZLtWzXWlpg5qCdlWr1TR7qhYjIOPO9t1beO3YOvwhcRoliyyAPenBxTmTfbwKBgDtm2WJ5VlQgNpIdOs1CCiqd0DFmWOmvBPspPC1kySiy+Ndr9jNohRZkR7pEjgqA5E8rdzc88LirUN7bY5HFHRWN9KXrs5/o3O1K3GFCp64N6nvnPEYZ2zSJalcMC2fjSsJg26z8Dg1H+gfTIDUMoGiEAAnJXuqk+WayPU+fZMLn",
|
|
||||||
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQAB",
|
|
||||||
"defaultRoles": [ "user" ],
|
|
||||||
"users" : [
|
|
||||||
{
|
|
||||||
"username" : "user",
|
|
||||||
"enabled": true,
|
|
||||||
"email" : "user@saml-broker-realm",
|
|
||||||
"firstName": "User",
|
|
||||||
"lastName": "SAML",
|
|
||||||
"credentials" : [
|
|
||||||
{ "type" : "password",
|
|
||||||
"value" : "password" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles" : {
|
|
||||||
"realm" : [
|
|
||||||
{
|
|
||||||
"name": "user",
|
|
||||||
"description": "User privileges"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"clients": [
|
|
||||||
{
|
|
||||||
"clientId": "http://localhost:8080/auth/realms/saml-broker-authentication-realm",
|
|
||||||
"protocol": "saml",
|
|
||||||
"enabled": true,
|
|
||||||
"redirectUris": [
|
|
||||||
"http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint"
|
|
||||||
],
|
|
||||||
"attributes": {
|
|
||||||
"saml.assertion.signature": "true",
|
|
||||||
"saml.server.signature": "true",
|
|
||||||
"saml.signature.algorithm": "RSA_SHA256",
|
|
||||||
"saml.client.signature": "true",
|
|
||||||
"saml.authnstatement": "true",
|
|
||||||
"saml_assertion_consumer_url_post": "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint",
|
|
||||||
"saml_single_logout_service_url_post": "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint",
|
|
||||||
"saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
|
|
||||||
"saml.signing.certificate": "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Google Authentication Example</title>
|
|
||||||
|
|
||||||
<script src="js/lib/angular/angular.js"></script>
|
|
||||||
<script src="js/lib/angular/angular-resource.js"></script>
|
|
||||||
<script src="js/lib/angular/angular-route.js"></script>
|
|
||||||
<script src="js/lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
|
|
||||||
|
|
||||||
<script src="/auth/js/keycloak.js"></script>
|
|
||||||
<script src="js/app.js" type="text/javascript"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body data-ng-controller="GlobalCtrl">
|
|
||||||
|
|
||||||
<div id="content-area" class="col-md-9" role="main">
|
|
||||||
<div id="content">
|
|
||||||
<h2>Hello, {{identity.name}} [<a href="" ng-click="logout()">Sign Out</a>]</h2>
|
|
||||||
<div>
|
|
||||||
<p><b>This is your Keycloak Profile</b>:</p>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li><b>Id</b>: {{identity.sub}}</li>
|
|
||||||
<li><b>Username</b>: {{identity.preferred_username}}</li>
|
|
||||||
<li><b>Email</b>: {{identity.email}}</li>
|
|
||||||
<li><b>Full Name</b>: {{identity.name}}</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,97 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var module = angular.module('app', []);
|
|
||||||
|
|
||||||
angular.element(document).ready(function ($http) {
|
|
||||||
var keycloakAuth = new Keycloak('keycloak.json');
|
|
||||||
|
|
||||||
keycloakAuth.init({ onLoad: 'login-required' }).then(function () {
|
|
||||||
module.factory('Auth', function() {
|
|
||||||
var Auth = {};
|
|
||||||
|
|
||||||
Auth.logout = function() {
|
|
||||||
keycloakAuth.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.getIdentity = function() {
|
|
||||||
return keycloakAuth.idTokenParsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.getToken = function() {
|
|
||||||
return keycloakAuth.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Auth;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('authInterceptor', function($q) {
|
|
||||||
return {
|
|
||||||
request: function (config) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
config.headers = config.headers || {};
|
|
||||||
|
|
||||||
if (!config.headers.Authorization) {
|
|
||||||
config.headers.Authorization = 'Bearer ' + keycloakAuth.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolve(config);
|
|
||||||
|
|
||||||
if (keycloakAuth.token) {
|
|
||||||
keycloakAuth.updateToken(5).then(function() {
|
|
||||||
}).catch(function() {
|
|
||||||
deferred.reject('Failed to refresh token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
module.config(function($httpProvider) {
|
|
||||||
$httpProvider.responseInterceptors.push('errorInterceptor');
|
|
||||||
$httpProvider.interceptors.push('authInterceptor');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('errorInterceptor', function($q) {
|
|
||||||
return function(promise) {
|
|
||||||
return promise.then(function(response) {
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
angular.bootstrap(document, ["app"]);
|
|
||||||
}).catch(function () {
|
|
||||||
window.location = keycloakAuth.createLoginUrl({
|
|
||||||
idpHint: 'saml-identity-provider'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('GlobalCtrl', function($scope, $http, $location, Auth) {
|
|
||||||
$scope.logout = function() {
|
|
||||||
Auth.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.identity = Auth.getIdentity();
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,192 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.7
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var directive = {};
|
|
||||||
|
|
||||||
directive.dropdownToggle =
|
|
||||||
['$document', '$location', '$window',
|
|
||||||
function ($document, $location, $window) {
|
|
||||||
var openElement = null, close;
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.parent().bind('click', function(event) {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
var iWasOpen = false;
|
|
||||||
|
|
||||||
if (openElement) {
|
|
||||||
iWasOpen = openElement === element;
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!iWasOpen){
|
|
||||||
element.parent().addClass('open');
|
|
||||||
openElement = element;
|
|
||||||
|
|
||||||
close = function (event) {
|
|
||||||
event && event.preventDefault();
|
|
||||||
event && event.stopPropagation();
|
|
||||||
$document.unbind('click', close);
|
|
||||||
element.parent().removeClass('open');
|
|
||||||
close = null;
|
|
||||||
openElement = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$document.bind('click', close);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
|
|
||||||
|
|
||||||
directive.tabbable = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
compile: function(element) {
|
|
||||||
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
|
|
||||||
tabContent = angular.element('<div class="tab-content"></div>');
|
|
||||||
|
|
||||||
tabContent.append(element.contents());
|
|
||||||
element.append(navTabs).append(tabContent);
|
|
||||||
},
|
|
||||||
controller: ['$scope', '$element', function($scope, $element) {
|
|
||||||
var navTabs = $element.contents().eq(0),
|
|
||||||
ngModel = $element.controller('ngModel') || {},
|
|
||||||
tabs = [],
|
|
||||||
selectedTab;
|
|
||||||
|
|
||||||
ngModel.$render = function() {
|
|
||||||
var $viewValue = this.$viewValue;
|
|
||||||
|
|
||||||
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
|
|
||||||
if(selectedTab) {
|
|
||||||
selectedTab.paneElement.removeClass('active');
|
|
||||||
selectedTab.tabElement.removeClass('active');
|
|
||||||
selectedTab = null;
|
|
||||||
}
|
|
||||||
if($viewValue) {
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++) {
|
|
||||||
if ($viewValue == tabs[i].value) {
|
|
||||||
selectedTab = tabs[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedTab) {
|
|
||||||
selectedTab.paneElement.addClass('active');
|
|
||||||
selectedTab.tabElement.addClass('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addPane = function(element, attr) {
|
|
||||||
var li = angular.element('<li><a href></a></li>'),
|
|
||||||
a = li.find('a'),
|
|
||||||
tab = {
|
|
||||||
paneElement: element,
|
|
||||||
paneAttrs: attr,
|
|
||||||
tabElement: li
|
|
||||||
};
|
|
||||||
|
|
||||||
tabs.push(tab);
|
|
||||||
|
|
||||||
attr.$observe('value', update)();
|
|
||||||
attr.$observe('title', function(){ update(); a.text(tab.title); })();
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
tab.title = attr.title;
|
|
||||||
tab.value = attr.value || attr.title;
|
|
||||||
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
}
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
|
|
||||||
navTabs.append(li);
|
|
||||||
li.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (ngModel.$setViewValue) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
ngModel.$setViewValue(tab.value);
|
|
||||||
ngModel.$render();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
tab.tabElement.remove();
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
|
|
||||||
if (tab == tabs[i]) {
|
|
||||||
tabs.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.table = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
element[0].className = 'table table-bordered table-striped code-table';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.tabPane = function() {
|
|
||||||
return {
|
|
||||||
require: '^tabbable',
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs, tabsCtrl) {
|
|
||||||
element.bind('$remove', tabsCtrl.addPane(element, attrs));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('bootstrap', []).directive(directive);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,219 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngCookies
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngCookies
|
|
||||||
*
|
|
||||||
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
|
|
||||||
*
|
|
||||||
* {@installModule cookies}
|
|
||||||
*
|
|
||||||
* <div doc-module-components="ngCookies"></div>
|
|
||||||
*
|
|
||||||
* See {@link ngCookies.$cookies `$cookies`} and
|
|
||||||
* {@link ngCookies.$cookieStore `$cookieStore`} for usage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('ngCookies', ['ng']).
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngCookies.$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.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example>
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function ExampleController($cookies) {
|
|
||||||
// Retrieving a cookie
|
|
||||||
var favoriteCookie = $cookies.myFavorite;
|
|
||||||
// Setting a cookie
|
|
||||||
$cookies.myFavorite = 'oatmeal';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</doc:source>
|
|
||||||
</doc: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 ngCookies.$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.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*/
|
|
||||||
factory('$cookieStore', ['$cookies', function($cookies) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngCookies.$cookieStore#get
|
|
||||||
* @methodOf ngCookies.$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) {
|
|
||||||
var value = $cookies[key];
|
|
||||||
return value ? angular.fromJson(value) : value;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngCookies.$cookieStore#put
|
|
||||||
* @methodOf ngCookies.$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 ngCookies.$cookieStore#remove
|
|
||||||
* @methodOf ngCookies.$cookieStore
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Remove given cookie
|
|
||||||
*
|
|
||||||
* @param {string} key Id of the key-value pair to delete.
|
|
||||||
*/
|
|
||||||
remove: function(key) {
|
|
||||||
delete $cookies[key];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}]);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,427 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function() {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* This object provides a utility for producing rich Error messages within
|
|
||||||
* Angular. It can be called as follows:
|
|
||||||
*
|
|
||||||
* var exampleMinErr = minErr('example');
|
|
||||||
* throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
|
|
||||||
*
|
|
||||||
* The above creates an instance of minErr in the example namespace. The
|
|
||||||
* resulting error will have a namespaced error code of example.one. The
|
|
||||||
* resulting error will replace {0} with the value of foo, and {1} with the
|
|
||||||
* value of bar. The object is not restricted in the number of arguments it can
|
|
||||||
* take.
|
|
||||||
*
|
|
||||||
* If fewer arguments are specified than necessary for interpolation, the extra
|
|
||||||
* interpolation markers will be preserved in the final string.
|
|
||||||
*
|
|
||||||
* Since data will be parsed statically during a build step, some restrictions
|
|
||||||
* are applied with respect to how minErr instances are created and called.
|
|
||||||
* Instances should have names of the form namespaceMinErr for a minErr created
|
|
||||||
* using minErr('namespace') . Error codes, namespaces and template strings
|
|
||||||
* should all be static strings, not variables or general expressions.
|
|
||||||
*
|
|
||||||
* @param {string} module The namespace to use for the new minErr instance.
|
|
||||||
* @returns {function(string, string, ...): Error} instance
|
|
||||||
*/
|
|
||||||
|
|
||||||
function minErr(module) {
|
|
||||||
return function () {
|
|
||||||
var code = arguments[0],
|
|
||||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
|
||||||
template = arguments[1],
|
|
||||||
templateArgs = arguments,
|
|
||||||
stringify = function (obj) {
|
|
||||||
if (typeof obj === 'function') {
|
|
||||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
|
||||||
} else if (typeof obj === 'undefined') {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof obj !== 'string') {
|
|
||||||
return JSON.stringify(obj);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
message, i;
|
|
||||||
|
|
||||||
message = prefix + template.replace(/\{\d+\}/g, function (match) {
|
|
||||||
var index = +match.slice(1, -1), arg;
|
|
||||||
|
|
||||||
if (index + 2 < templateArgs.length) {
|
|
||||||
arg = templateArgs[index + 2];
|
|
||||||
if (typeof arg === 'function') {
|
|
||||||
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
|
|
||||||
} else if (typeof arg === 'undefined') {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof arg !== 'string') {
|
|
||||||
return toJson(arg);
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
|
|
||||||
message = message + '\nhttp://errors.angularjs.org/1.2.13/' +
|
|
||||||
(module ? module + '/' : '') + code;
|
|
||||||
for (i = 2; i < arguments.length; i++) {
|
|
||||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
|
|
||||||
encodeURIComponent(stringify(arguments[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Error(message);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc interface
|
|
||||||
* @name angular.Module
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* Interface for configuring angular {@link angular.module modules}.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function setupModuleLoader(window) {
|
|
||||||
|
|
||||||
var $injectorMinErr = minErr('$injector');
|
|
||||||
var ngMinErr = minErr('ng');
|
|
||||||
|
|
||||||
function ensure(obj, name, factory) {
|
|
||||||
return obj[name] || (obj[name] = factory());
|
|
||||||
}
|
|
||||||
|
|
||||||
var angular = ensure(window, 'angular', Object);
|
|
||||||
|
|
||||||
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
|
|
||||||
angular.$$minErr = angular.$$minErr || minErr;
|
|
||||||
|
|
||||||
return ensure(angular, 'module', function() {
|
|
||||||
/** @type {Object.<string, angular.Module>} */
|
|
||||||
var modules = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc function
|
|
||||||
* @name angular.module
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* The `angular.module` is a global place for creating, registering and retrieving Angular
|
|
||||||
* modules.
|
|
||||||
* All modules (angular core or 3rd party) that should be available to an application must be
|
|
||||||
* registered using this mechanism.
|
|
||||||
*
|
|
||||||
* When passed two or more arguments, a new module is created. If passed only one argument, an
|
|
||||||
* existing module (the name passed as the first argument to `module`) is retrieved.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* # Module
|
|
||||||
*
|
|
||||||
* A module is a collection of services, directives, filters, and configuration information.
|
|
||||||
* `angular.module` is used to configure the {@link AUTO.$injector $injector}.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* // Create a new module
|
|
||||||
* var myModule = angular.module('myModule', []);
|
|
||||||
*
|
|
||||||
* // register a new service
|
|
||||||
* myModule.value('appName', 'MyCoolApp');
|
|
||||||
*
|
|
||||||
* // configure existing services inside initialization blocks.
|
|
||||||
* myModule.config(function($locationProvider) {
|
|
||||||
* // Configure existing providers
|
|
||||||
* $locationProvider.hashPrefix('!');
|
|
||||||
* });
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* Then you can create an injector and load your modules like this:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* var injector = angular.injector(['ng', 'MyModule'])
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* However it's more likely that you'll just use
|
|
||||||
* {@link ng.directive:ngApp ngApp} or
|
|
||||||
* {@link angular.bootstrap} to simplify this process for you.
|
|
||||||
*
|
|
||||||
* @param {!string} name The name of the module to create or retrieve.
|
|
||||||
* @param {Array.<string>=} requires If specified then new module is being created. If
|
|
||||||
* unspecified then the the module is being retrieved for further configuration.
|
|
||||||
* @param {Function} configFn Optional configuration function for the module. Same as
|
|
||||||
* {@link angular.Module#methods_config Module#config()}.
|
|
||||||
* @returns {module} new module with the {@link angular.Module} api.
|
|
||||||
*/
|
|
||||||
return function module(name, requires, configFn) {
|
|
||||||
var assertNotHasOwnProperty = function(name, context) {
|
|
||||||
if (name === 'hasOwnProperty') {
|
|
||||||
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
assertNotHasOwnProperty(name, 'module');
|
|
||||||
if (requires && modules.hasOwnProperty(name)) {
|
|
||||||
modules[name] = null;
|
|
||||||
}
|
|
||||||
return ensure(modules, name, function() {
|
|
||||||
if (!requires) {
|
|
||||||
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
|
|
||||||
"the module name or forgot to load it. If registering a module ensure that you " +
|
|
||||||
"specify the dependencies as the second argument.", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {!Array.<Array.<*>>} */
|
|
||||||
var invokeQueue = [];
|
|
||||||
|
|
||||||
/** @type {!Array.<Function>} */
|
|
||||||
var runBlocks = [];
|
|
||||||
|
|
||||||
var config = invokeLater('$injector', 'invoke');
|
|
||||||
|
|
||||||
/** @type {angular.Module} */
|
|
||||||
var moduleInstance = {
|
|
||||||
// Private state
|
|
||||||
_invokeQueue: invokeQueue,
|
|
||||||
_runBlocks: runBlocks,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc property
|
|
||||||
* @name angular.Module#requires
|
|
||||||
* @propertyOf angular.Module
|
|
||||||
* @returns {Array.<string>} List of module names which must be loaded before this module.
|
|
||||||
* @description
|
|
||||||
* Holds the list of modules which the injector will load before the current module is
|
|
||||||
* loaded.
|
|
||||||
*/
|
|
||||||
requires: requires,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc property
|
|
||||||
* @name angular.Module#name
|
|
||||||
* @propertyOf angular.Module
|
|
||||||
* @returns {string} Name of the module.
|
|
||||||
* @description
|
|
||||||
*/
|
|
||||||
name: name,
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#provider
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} providerType Construction function for creating new instance of the
|
|
||||||
* service.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#provider $provide.provider()}.
|
|
||||||
*/
|
|
||||||
provider: invokeLater('$provide', 'provider'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#factory
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} providerFunction Function for creating new instance of the service.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#factory $provide.factory()}.
|
|
||||||
*/
|
|
||||||
factory: invokeLater('$provide', 'factory'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#service
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} constructor A constructor function that will be instantiated.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#service $provide.service()}.
|
|
||||||
*/
|
|
||||||
service: invokeLater('$provide', 'service'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#value
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {*} object Service instance object.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#value $provide.value()}.
|
|
||||||
*/
|
|
||||||
value: invokeLater('$provide', 'value'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#constant
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name constant name
|
|
||||||
* @param {*} object Constant value.
|
|
||||||
* @description
|
|
||||||
* Because the constant are fixed, they get applied before other provide methods.
|
|
||||||
* See {@link AUTO.$provide#constant $provide.constant()}.
|
|
||||||
*/
|
|
||||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#animation
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name animation name
|
|
||||||
* @param {Function} animationFactory Factory function for creating new instance of an
|
|
||||||
* animation.
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Defines an animation hook that can be later used with
|
|
||||||
* {@link ngAnimate.$animate $animate} service and directives that use this service.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* module.animation('.animation-name', function($inject1, $inject2) {
|
|
||||||
* return {
|
|
||||||
* eventName : function(element, done) {
|
|
||||||
* //code to run the animation
|
|
||||||
* //once complete, then run done()
|
|
||||||
* return function cancellationFunction(element) {
|
|
||||||
* //code to cancel the animation
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* })
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
|
|
||||||
* {@link ngAnimate ngAnimate module} for more information.
|
|
||||||
*/
|
|
||||||
animation: invokeLater('$animateProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#filter
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name Filter name.
|
|
||||||
* @param {Function} filterFactory Factory function for creating new instance of filter.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$filterProvider#register $filterProvider.register()}.
|
|
||||||
*/
|
|
||||||
filter: invokeLater('$filterProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#controller
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string|Object} name Controller name, or an object map of controllers where the
|
|
||||||
* keys are the names and the values are the constructors.
|
|
||||||
* @param {Function} constructor Controller constructor function.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
|
|
||||||
*/
|
|
||||||
controller: invokeLater('$controllerProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#directive
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string|Object} name Directive name, or an object map of directives where the
|
|
||||||
* keys are the names and the values are the factories.
|
|
||||||
* @param {Function} directiveFactory Factory function for creating new instance of
|
|
||||||
* directives.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}.
|
|
||||||
*/
|
|
||||||
directive: invokeLater('$compileProvider', 'directive'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#config
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {Function} configFn Execute this function on module load. Useful for service
|
|
||||||
* configuration.
|
|
||||||
* @description
|
|
||||||
* Use this method to register work which needs to be performed on module loading.
|
|
||||||
*/
|
|
||||||
config: config,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#run
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {Function} initializationFn Execute this function after injector creation.
|
|
||||||
* Useful for application initialization.
|
|
||||||
* @description
|
|
||||||
* Use this method to register work which should be performed when the injector is done
|
|
||||||
* loading all modules.
|
|
||||||
*/
|
|
||||||
run: function(block) {
|
|
||||||
runBlocks.push(block);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (configFn) {
|
|
||||||
config(configFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return moduleInstance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} provider
|
|
||||||
* @param {string} method
|
|
||||||
* @param {String=} insertMethod
|
|
||||||
* @returns {angular.Module}
|
|
||||||
*/
|
|
||||||
function invokeLater(provider, method, insertMethod) {
|
|
||||||
return function() {
|
|
||||||
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
|
|
||||||
return moduleInstance;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setupModuleLoader(window);
|
|
||||||
})(window);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closure compiler type information
|
|
||||||
*
|
|
||||||
* @typedef { {
|
|
||||||
* requires: !Array.<string>,
|
|
||||||
* invokeQueue: !Array.<Array.<*>>,
|
|
||||||
*
|
|
||||||
* service: function(string, Function):angular.Module,
|
|
||||||
* factory: function(string, Function):angular.Module,
|
|
||||||
* value: function(string, *):angular.Module,
|
|
||||||
*
|
|
||||||
* filter: function(string, Function):angular.Module,
|
|
||||||
*
|
|
||||||
* init: function(Function):angular.Module
|
|
||||||
* } }
|
|
||||||
*/
|
|
||||||
angular.Module;
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,613 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 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
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngResource
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngResource.$resource
|
|
||||||
* @requires $http
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* A factory which creates a resource object that lets you interact with
|
|
||||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
|
||||||
*
|
|
||||||
* The returned resource object has action methods which provide high-level behaviors without
|
|
||||||
* the need to interact with the low level {@link ng.$http $http} service.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngResource `ngResource`} module to be installed.
|
|
||||||
*
|
|
||||||
* @param {string} url A parametrized URL template with parameters prefixed by `:` as in
|
|
||||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
|
||||||
* `http://example.com:8080/api`), it will be respected.
|
|
||||||
*
|
|
||||||
* If you are using a url with a suffix, just add the suffix, like this:
|
|
||||||
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
|
|
||||||
* or even `$resource('http://example.com/resource/:resource_id.:format')`
|
|
||||||
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
|
|
||||||
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
|
|
||||||
* can escape it with `/\.`.
|
|
||||||
*
|
|
||||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
|
||||||
* `actions` methods. 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).
|
|
||||||
*
|
|
||||||
* Each key value in the parameter object is first bound to url template if present and then any
|
|
||||||
* excess keys are appended to the url search query after the `?`.
|
|
||||||
*
|
|
||||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
|
||||||
* URL `/path/greet?salutation=Hello`.
|
|
||||||
*
|
|
||||||
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
|
|
||||||
* the data object (useful for non-GET operations).
|
|
||||||
*
|
|
||||||
* @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#usage_parameters $http.config}:
|
|
||||||
*
|
|
||||||
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
|
|
||||||
* action2: {method:?, params:?, isArray:?, headers:?, ...},
|
|
||||||
* ...}
|
|
||||||
*
|
|
||||||
* 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)>}` –
|
|
||||||
* 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)>}` –
|
|
||||||
* 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
|
|
||||||
* GET request, otherwise if a cache instance built with
|
|
||||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
|
||||||
* caching.
|
|
||||||
* - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
|
|
||||||
* should abort the request when resolved.
|
|
||||||
* - **`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
|
|
||||||
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
|
|
||||||
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
|
|
||||||
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
|
|
||||||
* with `http response` object. See {@link ng.$http $http interceptors}.
|
|
||||||
*
|
|
||||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
|
||||||
* optionally extended with custom `actions`. The default set contains these actions:
|
|
||||||
*
|
|
||||||
* { 'get': {method:'GET'},
|
|
||||||
* 'save': {method:'POST'},
|
|
||||||
* 'query': {method:'GET', isArray:true},
|
|
||||||
* 'remove': {method:'DELETE'},
|
|
||||||
* 'delete': {method:'DELETE'} };
|
|
||||||
*
|
|
||||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
|
||||||
* destination and parameters. When the data is returned from the server then the object is an
|
|
||||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
|
||||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
|
||||||
* read, update, delete) on server-side data like this:
|
|
||||||
* <pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
var user = User.get({userId:123}, function() {
|
|
||||||
user.abc = true;
|
|
||||||
user.$save();
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* It is important to realize that invoking a $resource object method immediately returns an
|
|
||||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
|
||||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
|
||||||
* 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 cases 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:
|
|
||||||
*
|
|
||||||
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
|
|
||||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
|
|
||||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
|
||||||
*
|
|
||||||
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
|
|
||||||
* with (httpResponse) argument.
|
|
||||||
*
|
|
||||||
* Class actions return empty instance (with additional properties below).
|
|
||||||
* Instance actions return promise of the action.
|
|
||||||
*
|
|
||||||
* The Resource instances and collection have these additional properties:
|
|
||||||
*
|
|
||||||
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
|
|
||||||
* instance or collection.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* # Credit card resource
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
// Define CreditCard class
|
|
||||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
|
||||||
{userId:123, cardId:'@id'}, {
|
|
||||||
charge: {method:'POST', params:{charge:true}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// We can retrieve a collection from the server
|
|
||||||
var cards = CreditCard.query(function() {
|
|
||||||
// GET: /user/123/card
|
|
||||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
|
||||||
|
|
||||||
var card = cards[0];
|
|
||||||
// each item is an instance of CreditCard
|
|
||||||
expect(card instanceof CreditCard).toEqual(true);
|
|
||||||
card.name = "J. Smith";
|
|
||||||
// non GET methods are mapped onto the instances
|
|
||||||
card.$save();
|
|
||||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
|
||||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
|
||||||
|
|
||||||
// our custom method is mapped as well.
|
|
||||||
card.$charge({amount:9.99});
|
|
||||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
|
||||||
});
|
|
||||||
|
|
||||||
// we can create an instance as well
|
|
||||||
var newCard = new CreditCard({number:'0123'});
|
|
||||||
newCard.name = "Mike Smith";
|
|
||||||
newCard.$save();
|
|
||||||
// POST: /user/123/card {number:'0123', 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`.
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
var user = User.get({userId:123}, function() {
|
|
||||||
user.abc = true;
|
|
||||||
user.$save();
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* It's worth noting that the success callback for `get`, `query` and other methods 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:
|
|
||||||
*
|
|
||||||
<pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
User.get({userId:123}, function(u, getResponseHeaders){
|
|
||||||
u.abc = true;
|
|
||||||
u.$save(function(u, putResponseHeaders) {
|
|
||||||
//u => saved user object
|
|
||||||
//putResponseHeaders => $http header getter
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
* # 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', '$q', function($http, $q) {
|
|
||||||
|
|
||||||
var DEFAULT_ACTIONS = {
|
|
||||||
'get': {method:'GET'},
|
|
||||||
'save': {method:'POST'},
|
|
||||||
'query': {method:'GET', isArray:true},
|
|
||||||
'remove': {method:'DELETE'},
|
|
||||||
'delete': {method:'DELETE'}
|
|
||||||
};
|
|
||||||
var noop = angular.noop,
|
|
||||||
forEach = angular.forEach,
|
|
||||||
extend = angular.extend,
|
|
||||||
copy = angular.copy,
|
|
||||||
isFunction = angular.isFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
|
|
||||||
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
|
|
||||||
* segments:
|
|
||||||
* segment = *pchar
|
|
||||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
|
||||||
* pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
|
||||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
||||||
* / "*" / "+" / "," / ";" / "="
|
|
||||||
*/
|
|
||||||
function encodeUriSegment(val) {
|
|
||||||
return encodeUriQuery(val, true).
|
|
||||||
replace(/%26/gi, '&').
|
|
||||||
replace(/%3D/gi, '=').
|
|
||||||
replace(/%2B/gi, '+');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 / "-" / "." / "_" / "~"
|
|
||||||
* pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
||||||
* / "*" / "+" / "," / ";" / "="
|
|
||||||
*/
|
|
||||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
|
||||||
return encodeURIComponent(val).
|
|
||||||
replace(/%40/gi, '@').
|
|
||||||
replace(/%3A/gi, ':').
|
|
||||||
replace(/%24/g, '$').
|
|
||||||
replace(/%2C/gi, ',').
|
|
||||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function Route(template, defaults) {
|
|
||||||
this.template = template;
|
|
||||||
this.defaults = defaults || {};
|
|
||||||
this.urlParams = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Route.prototype = {
|
|
||||||
setUrlParams: function(config, params, actionUrl) {
|
|
||||||
var self = this,
|
|
||||||
url = actionUrl || self.template,
|
|
||||||
val,
|
|
||||||
encodedVal;
|
|
||||||
|
|
||||||
var urlParams = self.urlParams = {};
|
|
||||||
forEach(url.split(/\W/), function(param){
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
url = url.replace(/\\:/g, ':');
|
|
||||||
|
|
||||||
params = params || {};
|
|
||||||
forEach(self.urlParams, function(_, urlParam){
|
|
||||||
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"), function(match, p1) {
|
|
||||||
return encodedVal + p1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
|
|
||||||
leadingSlashes, tail) {
|
|
||||||
if (tail.charAt(0) == '/') {
|
|
||||||
return tail;
|
|
||||||
} else {
|
|
||||||
return leadingSlashes + tail;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// strip trailing slashes and set the url
|
|
||||||
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+($|\?))/, '.');
|
|
||||||
// replace escaped `/\.` with `/.`
|
|
||||||
config.url = url.replace(/\/\\\./, '/.');
|
|
||||||
|
|
||||||
|
|
||||||
// set params - delegate param encoding to $http
|
|
||||||
forEach(params, function(value, key){
|
|
||||||
if (!self.urlParams[key]) {
|
|
||||||
config.params = config.params || {};
|
|
||||||
config.params[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function resourceFactory(url, paramDefaults, actions) {
|
|
||||||
var route = new Route(url);
|
|
||||||
|
|
||||||
actions = extend({}, DEFAULT_ACTIONS, actions);
|
|
||||||
|
|
||||||
function extractParams(data, actionParams){
|
|
||||||
var ids = {};
|
|
||||||
actionParams = extend({}, paramDefaults, actionParams);
|
|
||||||
forEach(actionParams, function(value, key){
|
|
||||||
if (isFunction(value)) { value = value(); }
|
|
||||||
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
|
|
||||||
lookupDottedPath(data, value.substr(1)) : value;
|
|
||||||
});
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultResponseInterceptor(response) {
|
|
||||||
return response.resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Resource(value){
|
|
||||||
shallowClearAndCopy(value || {}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(actions, function(action, name) {
|
|
||||||
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
|
|
||||||
|
|
||||||
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;
|
|
||||||
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 (hasBody) data = a1;
|
|
||||||
else params = a1;
|
|
||||||
break;
|
|
||||||
case 0: break;
|
|
||||||
default:
|
|
||||||
throw $resourceMinErr('badargs',
|
|
||||||
"Expected up to 4 arguments [params, data, success, error], got {0} arguments",
|
|
||||||
arguments.length);
|
|
||||||
}
|
|
||||||
/* jshint +W086 */ /* (purposefully fall through case statements) */
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
forEach(action, function(value, key) {
|
|
||||||
if (key != 'params' && key != 'isArray' && key != 'interceptor') {
|
|
||||||
httpConfig[key] = copy(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// 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 {
|
|
||||||
shallowClearAndCopy(data, value);
|
|
||||||
value.$promise = promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value.$resolved = true;
|
|
||||||
|
|
||||||
response.resource = value;
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
value.$resolved = true;
|
|
||||||
|
|
||||||
(error||noop)(response);
|
|
||||||
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then(
|
|
||||||
function(response) {
|
|
||||||
var value = responseInterceptor(response);
|
|
||||||
(success||noop)(value, response.headers);
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
responseErrorInterceptor);
|
|
||||||
|
|
||||||
if (!isInstanceCall) {
|
|
||||||
// we are creating instance / collection
|
|
||||||
// - set the initial promise
|
|
||||||
// - return the instance / collection
|
|
||||||
value.$promise = promise;
|
|
||||||
value.$resolved = false;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// instance call
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Resource.prototype['$' + name] = function(params, success, error) {
|
|
||||||
if (isFunction(params)) {
|
|
||||||
error = success; success = params; params = {};
|
|
||||||
}
|
|
||||||
var result = Resource[name].call(this, params, this, success, error);
|
|
||||||
return result.$promise || result;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
Resource.bind = function(additionalParamDefaults){
|
|
||||||
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
return Resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceFactory;
|
|
||||||
}]);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,938 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngRoute
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngRoute
|
|
||||||
*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$routeProvider
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* 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 = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$routeProvider#when
|
|
||||||
* @methodOf ngRoute.$routeProvider
|
|
||||||
*
|
|
||||||
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
|
|
||||||
* contains redundant 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 exactly match the
|
|
||||||
* route definition.
|
|
||||||
*
|
|
||||||
* * `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:
|
|
||||||
* 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: e.g.`:name?`.
|
|
||||||
*
|
|
||||||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
|
|
||||||
* `/color/brown/largecode/code/with/slashs/edit` and extract:
|
|
||||||
*
|
|
||||||
* * `color: brown`
|
|
||||||
* * `largecode: code/with/slashs`.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param {Object} route Mapping information to be assigned to `$route.current` on route
|
|
||||||
* match.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* - `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
|
|
||||||
* returns an html template as a string which should be used by {@link
|
|
||||||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
|
|
||||||
* This property takes precedence over `templateUrl`.
|
|
||||||
*
|
|
||||||
* If `template` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
|
||||||
* `$location.path()` by applying the current route
|
|
||||||
*
|
|
||||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
|
|
||||||
* template that should be used by {@link ngRoute.directive:ngView ngView}.
|
|
||||||
*
|
|
||||||
* If `templateUrl` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
|
||||||
* `$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, 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.
|
|
||||||
*
|
|
||||||
* - `redirectTo` – {(string|function())=} – value to update
|
|
||||||
* {@link ng.$location $location} path with and trigger route redirection.
|
|
||||||
*
|
|
||||||
* If `redirectTo` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Object.<string>}` - route parameters extracted from the current
|
|
||||||
* `$location.path()` by applying the current route templateUrl.
|
|
||||||
* - `{string}` - current `$location.path()`
|
|
||||||
* - `{Object}` - current `$location.search()`
|
|
||||||
*
|
|
||||||
* The custom `redirectTo` function is expected to return a string which will be used
|
|
||||||
* to update `$location.path()` and `$location.search()`.
|
|
||||||
*
|
|
||||||
* - `[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.
|
|
||||||
*
|
|
||||||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
|
|
||||||
*
|
|
||||||
* If the option is set to `true`, then the particular route can be matched without being
|
|
||||||
* case sensitive
|
|
||||||
*
|
|
||||||
* @returns {Object} self
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Adds a new route definition to the `$route` service.
|
|
||||||
*/
|
|
||||||
this.when = function(path, route) {
|
|
||||||
routes[path] = angular.extend(
|
|
||||||
{reloadOnSearch: true},
|
|
||||||
route,
|
|
||||||
path && pathRegExp(path, route)
|
|
||||||
);
|
|
||||||
|
|
||||||
// create redirection for trailing slashes
|
|
||||||
if (path) {
|
|
||||||
var redirectPath = (path[path.length-1] == '/')
|
|
||||||
? path.substr(0, path.length-1)
|
|
||||||
: path +'/';
|
|
||||||
|
|
||||||
routes[redirectPath] = angular.extend(
|
|
||||||
{redirectTo: path},
|
|
||||||
pathRegExp(redirectPath, route)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param path {string} path
|
|
||||||
* @param opts {Object} options
|
|
||||||
* @return {?Object}
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Normalizes the given path, returning a regular expression
|
|
||||||
* and the original path.
|
|
||||||
*
|
|
||||||
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
|
|
||||||
*/
|
|
||||||
function pathRegExp(path, opts) {
|
|
||||||
var insensitive = opts.caseInsensitiveMatch,
|
|
||||||
ret = {
|
|
||||||
originalPath: path,
|
|
||||||
regexp: path
|
|
||||||
},
|
|
||||||
keys = ret.keys = [];
|
|
||||||
|
|
||||||
path = path
|
|
||||||
.replace(/([().])/g, '\\$1')
|
|
||||||
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){
|
|
||||||
var optional = option === '?' ? option : null;
|
|
||||||
var star = option === '*' ? option : null;
|
|
||||||
keys.push({ name: key, optional: !!optional });
|
|
||||||
slash = slash || '';
|
|
||||||
return ''
|
|
||||||
+ (optional ? '' : slash)
|
|
||||||
+ '(?:'
|
|
||||||
+ (optional ? slash : '')
|
|
||||||
+ (star && '(.+?)' || '([^/]+)')
|
|
||||||
+ (optional || '')
|
|
||||||
+ ')'
|
|
||||||
+ (optional || '');
|
|
||||||
})
|
|
||||||
.replace(/([\/$\*])/g, '\\$1');
|
|
||||||
|
|
||||||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$routeProvider#otherwise
|
|
||||||
* @methodOf ngRoute.$routeProvider
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Sets route definition that will be used on route change when no other route definition
|
|
||||||
* is matched.
|
|
||||||
*
|
|
||||||
* @param {Object} params Mapping information to be assigned to `$route.current`.
|
|
||||||
* @returns {Object} self
|
|
||||||
*/
|
|
||||||
this.otherwise = function(params) {
|
|
||||||
this.when(null, params);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
this.$get = ['$rootScope',
|
|
||||||
'$location',
|
|
||||||
'$routeParams',
|
|
||||||
'$q',
|
|
||||||
'$injector',
|
|
||||||
'$http',
|
|
||||||
'$templateCache',
|
|
||||||
'$sce',
|
|
||||||
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$route
|
|
||||||
* @requires $location
|
|
||||||
* @requires $routeParams
|
|
||||||
*
|
|
||||||
* @property {Object} current Reference to the current route definition.
|
|
||||||
* The route definition contains:
|
|
||||||
*
|
|
||||||
* - `controller`: The controller constructor as define in route definition.
|
|
||||||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
|
|
||||||
* controller instantiation. The `locals` contain
|
|
||||||
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
|
|
||||||
*
|
|
||||||
* - `$scope` - The current route scope.
|
|
||||||
* - `$template` - The current route template HTML.
|
|
||||||
*
|
|
||||||
* @property {Array.<Object>} routes Array of all configured routes.
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials).
|
|
||||||
* It watches `$location.url()` and tries to map the path to an existing route definition.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
This example shows how changing the URL hash causes the `$route` to match a route against the
|
|
||||||
URL, and the `ngView` pulls in the partial.
|
|
||||||
|
|
||||||
Note that this example is using {@link ng.directive:script inlined templates}
|
|
||||||
to get it working on jsfiddle as well.
|
|
||||||
|
|
||||||
<example module="ngViewExample" deps="angular-route.js">
|
|
||||||
<file name="index.html">
|
|
||||||
<div ng-controller="MainCntl">
|
|
||||||
Choose:
|
|
||||||
<a href="Book/Moby">Moby</a> |
|
|
||||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
|
||||||
<a href="Book/Gatsby">Gatsby</a> |
|
|
||||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
|
||||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
|
||||||
|
|
||||||
<div ng-view></div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre>$location.path() = {{$location.path()}}</pre>
|
|
||||||
<pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
|
|
||||||
<pre>$route.current.params = {{$route.current.params}}</pre>
|
|
||||||
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
|
|
||||||
<pre>$routeParams = {{$routeParams}}</pre>
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="book.html">
|
|
||||||
controller: {{name}}<br />
|
|
||||||
Book Id: {{params.bookId}}<br />
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="chapter.html">
|
|
||||||
controller: {{name}}<br />
|
|
||||||
Book Id: {{params.bookId}}<br />
|
|
||||||
Chapter Id: {{params.chapterId}}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="script.js">
|
|
||||||
angular.module('ngViewExample', ['ngRoute'])
|
|
||||||
|
|
||||||
.config(function($routeProvider, $locationProvider) {
|
|
||||||
$routeProvider.when('/Book/:bookId', {
|
|
||||||
templateUrl: 'book.html',
|
|
||||||
controller: BookCntl,
|
|
||||||
resolve: {
|
|
||||||
// I will cause a 1 second delay
|
|
||||||
delay: function($q, $timeout) {
|
|
||||||
var delay = $q.defer();
|
|
||||||
$timeout(delay.resolve, 1000);
|
|
||||||
return delay.promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
|
|
||||||
templateUrl: 'chapter.html',
|
|
||||||
controller: ChapterCntl
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure html5 to get links working on jsfiddle
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
function MainCntl($scope, $route, $routeParams, $location) {
|
|
||||||
$scope.$route = $route;
|
|
||||||
$scope.$location = $location;
|
|
||||||
$scope.$routeParams = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookCntl($scope, $routeParams) {
|
|
||||||
$scope.name = "BookCntl";
|
|
||||||
$scope.params = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChapterCntl($scope, $routeParams) {
|
|
||||||
$scope.name = "ChapterCntl";
|
|
||||||
$scope.params = $routeParams;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="protractorTest.js">
|
|
||||||
it('should load and compile correct template', function() {
|
|
||||||
element(by.linkText('Moby: Ch1')).click();
|
|
||||||
var content = element(by.css('.doc-example-live [ng-view]')).getText();
|
|
||||||
expect(content).toMatch(/controller\: ChapterCntl/);
|
|
||||||
expect(content).toMatch(/Book Id\: Moby/);
|
|
||||||
expect(content).toMatch(/Chapter Id\: 1/);
|
|
||||||
|
|
||||||
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/);
|
|
||||||
});
|
|
||||||
</file>
|
|
||||||
</example>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeStart
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @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.
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeSuccess
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @description
|
|
||||||
* Broadcasted after a route dependencies are resolved.
|
|
||||||
* {@link ngRoute.directive:ngView ngView} listens for the directive
|
|
||||||
* to instantiate the controller and render the view.
|
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeError
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeUpdate
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* The `reloadOnSearch` property has been set to false, and we are reusing the same
|
|
||||||
* instance of the Controller.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var forceReload = false,
|
|
||||||
$route = {
|
|
||||||
routes: routes,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$route#reload
|
|
||||||
* @methodOf ngRoute.$route
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Causes `$route` service to reload the current route even if
|
|
||||||
* {@link ng.$location $location} hasn't changed.
|
|
||||||
*
|
|
||||||
* As a result of that, {@link ngRoute.directive:ngView ngView}
|
|
||||||
* creates new scope, reinstantiates the controller.
|
|
||||||
*/
|
|
||||||
reload: function() {
|
|
||||||
forceReload = true;
|
|
||||||
$rootScope.$evalAsync(updateRoute);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.$on('$locationChangeSuccess', updateRoute);
|
|
||||||
|
|
||||||
return $route;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param on {string} current url
|
|
||||||
* @param route {Object} route regexp to match the url against
|
|
||||||
* @return {?Object}
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Check if the route matches the current url.
|
|
||||||
*
|
|
||||||
* Inspired by match in
|
|
||||||
* visionmedia/express/lib/router/router.js.
|
|
||||||
*/
|
|
||||||
function switchRouteMatcher(on, route) {
|
|
||||||
var keys = route.keys,
|
|
||||||
params = {};
|
|
||||||
|
|
||||||
if (!route.regexp) return null;
|
|
||||||
|
|
||||||
var m = route.regexp.exec(on);
|
|
||||||
if (!m) return null;
|
|
||||||
|
|
||||||
for (var i = 1, len = m.length; i < len; ++i) {
|
|
||||||
var key = keys[i - 1];
|
|
||||||
|
|
||||||
var val = 'string' == typeof m[i]
|
|
||||||
? decodeURIComponent(m[i])
|
|
||||||
: m[i];
|
|
||||||
|
|
||||||
if (key && val) {
|
|
||||||
params[key.name] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRoute() {
|
|
||||||
var next = parseRoute(),
|
|
||||||
last = $route.current;
|
|
||||||
|
|
||||||
if (next && last && next.$$route === last.$$route
|
|
||||||
&& angular.equals(next.pathParams, last.pathParams)
|
|
||||||
&& !next.reloadOnSearch && !forceReload) {
|
|
||||||
last.params = next.params;
|
|
||||||
angular.copy(last.params, $routeParams);
|
|
||||||
$rootScope.$broadcast('$routeUpdate', last);
|
|
||||||
} else if (next || last) {
|
|
||||||
forceReload = false;
|
|
||||||
$rootScope.$broadcast('$routeChangeStart', next, last);
|
|
||||||
$route.current = next;
|
|
||||||
if (next) {
|
|
||||||
if (next.redirectTo) {
|
|
||||||
if (angular.isString(next.redirectTo)) {
|
|
||||||
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
|
|
||||||
.replace();
|
|
||||||
} else {
|
|
||||||
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
|
|
||||||
.replace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$q.when(next).
|
|
||||||
then(function() {
|
|
||||||
if (next) {
|
|
||||||
var locals = angular.extend({}, next.resolve),
|
|
||||||
template, templateUrl;
|
|
||||||
|
|
||||||
angular.forEach(locals, function(value, key) {
|
|
||||||
locals[key] = angular.isString(value) ?
|
|
||||||
$injector.get(value) : $injector.invoke(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (angular.isDefined(template = next.template)) {
|
|
||||||
if (angular.isFunction(template)) {
|
|
||||||
template = template(next.params);
|
|
||||||
}
|
|
||||||
} else if (angular.isDefined(templateUrl = next.templateUrl)) {
|
|
||||||
if (angular.isFunction(templateUrl)) {
|
|
||||||
templateUrl = templateUrl(next.params);
|
|
||||||
}
|
|
||||||
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
|
|
||||||
if (angular.isDefined(templateUrl)) {
|
|
||||||
next.loadedTemplateUrl = templateUrl;
|
|
||||||
template = $http.get(templateUrl, {cache: $templateCache}).
|
|
||||||
then(function(response) { return response.data; });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (angular.isDefined(template)) {
|
|
||||||
locals['$template'] = template;
|
|
||||||
}
|
|
||||||
return $q.all(locals);
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
// after route change
|
|
||||||
then(function(locals) {
|
|
||||||
if (next == $route.current) {
|
|
||||||
if (next) {
|
|
||||||
next.locals = locals;
|
|
||||||
angular.copy(next.params, $routeParams);
|
|
||||||
}
|
|
||||||
$rootScope.$broadcast('$routeChangeSuccess', next, last);
|
|
||||||
}
|
|
||||||
}, function(error) {
|
|
||||||
if (next == $route.current) {
|
|
||||||
$rootScope.$broadcast('$routeChangeError', next, last, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns the current active route, by matching it against the URL
|
|
||||||
*/
|
|
||||||
function parseRoute() {
|
|
||||||
// Match a route
|
|
||||||
var params, match;
|
|
||||||
angular.forEach(routes, function(route, path) {
|
|
||||||
if (!match && (params = switchRouteMatcher($location.path(), route))) {
|
|
||||||
match = inherit(route, {
|
|
||||||
params: angular.extend({}, $location.search(), params),
|
|
||||||
pathParams: params});
|
|
||||||
match.$$route = route;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// No route matched; fallback to "otherwise" route
|
|
||||||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns interpolation of the redirect path with the parameters
|
|
||||||
*/
|
|
||||||
function interpolate(string, params) {
|
|
||||||
var result = [];
|
|
||||||
angular.forEach((string||'').split(':'), function(segment, i) {
|
|
||||||
if (i === 0) {
|
|
||||||
result.push(segment);
|
|
||||||
} else {
|
|
||||||
var segmentMatch = segment.match(/(\w+)(.*)/);
|
|
||||||
var key = segmentMatch[1];
|
|
||||||
result.push(params[key]);
|
|
||||||
result.push(segmentMatch[2] || '');
|
|
||||||
delete params[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result.join('');
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
ngRouteModule.provider('$routeParams', $RouteParamsProvider);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$routeParams
|
|
||||||
* @requires $route
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* The `$routeParams` service allows you to retrieve the current set of route parameters.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* The route parameters are a combination of {@link ng.$location `$location`}'s
|
|
||||||
* {@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.
|
|
||||||
*
|
|
||||||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged
|
|
||||||
* (but its properties will likely change) even when a route change occurs.
|
|
||||||
*
|
|
||||||
* Note that the `$routeParams` are only updated *after* a route change completes successfully.
|
|
||||||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions.
|
|
||||||
* Instead you can use `$route.current.params` to access the new route's parameters.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* <pre>
|
|
||||||
* // Given:
|
|
||||||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
|
|
||||||
* // Route: /Chapter/:chapterId/Section/:sectionId
|
|
||||||
* //
|
|
||||||
* // Then
|
|
||||||
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
function $RouteParamsProvider() {
|
|
||||||
this.$get = function() { return {}; };
|
|
||||||
}
|
|
||||||
|
|
||||||
ngRouteModule.directive('ngView', ngViewFactory);
|
|
||||||
ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc directive
|
|
||||||
* @name ngRoute.directive:ngView
|
|
||||||
* @restrict ECA
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* # Overview
|
|
||||||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
|
|
||||||
* including the rendered template of the current route into the main layout (`index.html`) file.
|
|
||||||
* Every time the current route changes, the included view changes with it according to the
|
|
||||||
* configuration of the `$route` service.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* @animations
|
|
||||||
* enter - animation is used to bring new content into the browser.
|
|
||||||
* leave - animation is used to animate existing content away.
|
|
||||||
*
|
|
||||||
* 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">
|
|
||||||
<div ng-controller="MainCntl as main">
|
|
||||||
Choose:
|
|
||||||
<a href="Book/Moby">Moby</a> |
|
|
||||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
|
||||||
<a href="Book/Gatsby">Gatsby</a> |
|
|
||||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
|
||||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
|
||||||
|
|
||||||
<div class="view-animate-container">
|
|
||||||
<div ng-view class="view-animate"></div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre>$location.path() = {{main.$location.path()}}</pre>
|
|
||||||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
|
|
||||||
<pre>$route.current.params = {{main.$route.current.params}}</pre>
|
|
||||||
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
|
|
||||||
<pre>$routeParams = {{main.$routeParams}}</pre>
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="book.html">
|
|
||||||
<div>
|
|
||||||
controller: {{book.name}}<br />
|
|
||||||
Book Id: {{book.params.bookId}}<br />
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="chapter.html">
|
|
||||||
<div>
|
|
||||||
controller: {{chapter.name}}<br />
|
|
||||||
Book Id: {{chapter.params.bookId}}<br />
|
|
||||||
Chapter Id: {{chapter.params.chapterId}}
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="animations.css">
|
|
||||||
.view-animate-container {
|
|
||||||
position:relative;
|
|
||||||
height:100px!important;
|
|
||||||
position:relative;
|
|
||||||
background:white;
|
|
||||||
border:1px solid black;
|
|
||||||
height:40px;
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate {
|
|
||||||
padding:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate.ng-enter, .view-animate.ng-leave {
|
|
||||||
-webkit-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;
|
|
||||||
width:100%;
|
|
||||||
border-left:1px solid black;
|
|
||||||
|
|
||||||
position:absolute;
|
|
||||||
top:0;
|
|
||||||
left:0;
|
|
||||||
right:0;
|
|
||||||
bottom:0;
|
|
||||||
padding:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate.ng-enter {
|
|
||||||
left:100%;
|
|
||||||
}
|
|
||||||
.view-animate.ng-enter.ng-enter-active {
|
|
||||||
left:0;
|
|
||||||
}
|
|
||||||
.view-animate.ng-leave.ng-leave-active {
|
|
||||||
left:-100%;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="script.js">
|
|
||||||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'],
|
|
||||||
function($routeProvider, $locationProvider) {
|
|
||||||
$routeProvider.when('/Book/:bookId', {
|
|
||||||
templateUrl: 'book.html',
|
|
||||||
controller: BookCntl,
|
|
||||||
controllerAs: 'book'
|
|
||||||
});
|
|
||||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
|
|
||||||
templateUrl: 'chapter.html',
|
|
||||||
controller: ChapterCntl,
|
|
||||||
controllerAs: 'chapter'
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure html5 to get links working on jsfiddle
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
function MainCntl($route, $routeParams, $location) {
|
|
||||||
this.$route = $route;
|
|
||||||
this.$location = $location;
|
|
||||||
this.$routeParams = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookCntl($routeParams) {
|
|
||||||
this.name = "BookCntl";
|
|
||||||
this.params = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChapterCntl($routeParams) {
|
|
||||||
this.name = "ChapterCntl";
|
|
||||||
this.params = $routeParams;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="protractorTest.js">
|
|
||||||
it('should load and compile correct template', function() {
|
|
||||||
element(by.linkText('Moby: Ch1')).click();
|
|
||||||
var content = element(by.css('.doc-example-live [ng-view]')).getText();
|
|
||||||
expect(content).toMatch(/controller\: ChapterCntl/);
|
|
||||||
expect(content).toMatch(/Book Id\: Moby/);
|
|
||||||
expect(content).toMatch(/Chapter Id\: 1/);
|
|
||||||
|
|
||||||
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/);
|
|
||||||
});
|
|
||||||
</file>
|
|
||||||
</example>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.directive:ngView#$viewContentLoaded
|
|
||||||
* @eventOf ngRoute.directive:ngView
|
|
||||||
* @eventType emit on the current ngView scope
|
|
||||||
* @description
|
|
||||||
* Emitted every time the ngView content is reloaded.
|
|
||||||
*/
|
|
||||||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
|
|
||||||
function ngViewFactory( $route, $anchorScroll, $animate) {
|
|
||||||
return {
|
|
||||||
restrict: 'ECA',
|
|
||||||
terminal: true,
|
|
||||||
priority: 400,
|
|
||||||
transclude: 'element',
|
|
||||||
link: function(scope, $element, attr, ctrl, $transclude) {
|
|
||||||
var currentScope,
|
|
||||||
currentElement,
|
|
||||||
autoScrollExp = attr.autoscroll,
|
|
||||||
onloadExp = attr.onload || '';
|
|
||||||
|
|
||||||
scope.$on('$routeChangeSuccess', update);
|
|
||||||
update();
|
|
||||||
|
|
||||||
function cleanupLastView() {
|
|
||||||
if (currentScope) {
|
|
||||||
currentScope.$destroy();
|
|
||||||
currentScope = null;
|
|
||||||
}
|
|
||||||
if(currentElement) {
|
|
||||||
$animate.leave(currentElement);
|
|
||||||
currentElement = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var locals = $route.current && $route.current.locals,
|
|
||||||
template = locals && locals.$template;
|
|
||||||
|
|
||||||
if (angular.isDefined(template)) {
|
|
||||||
var newScope = scope.$new();
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
currentElement = clone;
|
|
||||||
currentScope = current.scope = newScope;
|
|
||||||
currentScope.$emit('$viewContentLoaded');
|
|
||||||
currentScope.$eval(onloadExp);
|
|
||||||
} else {
|
|
||||||
cleanupLastView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
$element.html(locals.$template);
|
|
||||||
|
|
||||||
var link = $compile($element.contents());
|
|
||||||
|
|
||||||
if (current.controller) {
|
|
||||||
locals.$scope = scope;
|
|
||||||
var controller = $controller(current.controller, locals);
|
|
||||||
if (current.controllerAs) {
|
|
||||||
scope[current.controllerAs] = controller;
|
|
||||||
}
|
|
||||||
$element.data('$ngControllerController', controller);
|
|
||||||
$element.children().data('$ngControllerController', controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
link(scope);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,642 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngSanitize
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngSanitize
|
|
||||||
*
|
|
||||||
* The `ngSanitize` module provides functionality to sanitize HTML.
|
|
||||||
*
|
|
||||||
* {@installModule sanitize}
|
|
||||||
*
|
|
||||||
* <div doc-module-components="ngSanitize"></div>
|
|
||||||
*
|
|
||||||
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* HTML Parser By Misko Hevery (misko@hevery.com)
|
|
||||||
* based on: HTML Parser By John Resig (ejohn.org)
|
|
||||||
* Original code by Erik Arvidsson, Mozilla Public License
|
|
||||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
|
||||||
*
|
|
||||||
* // Use like so:
|
|
||||||
* htmlParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc service
|
|
||||||
* @name ngSanitize.$sanitize
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
|
||||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example module="ngSanitize">
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function Ctrl($scope, $sce) {
|
|
||||||
$scope.snippet =
|
|
||||||
'<p style="color:blue">an html\n' +
|
|
||||||
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
|
|
||||||
'snippet</p>';
|
|
||||||
$scope.deliberatelyTrustDangerousSnippet = function() {
|
|
||||||
return $sce.trustAsHtml($scope.snippet);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div ng-controller="Ctrl">
|
|
||||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Directive</td>
|
|
||||||
<td>How</td>
|
|
||||||
<td>Source</td>
|
|
||||||
<td>Rendered</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="bind-html-with-sanitize">
|
|
||||||
<td>ng-bind-html</td>
|
|
||||||
<td>Automatically uses $sanitize</td>
|
|
||||||
<td><pre><div ng-bind-html="snippet"><br/></div></pre></td>
|
|
||||||
<td><div ng-bind-html="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
<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()">
|
|
||||||
</div></pre>
|
|
||||||
</td>
|
|
||||||
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="bind-default">
|
|
||||||
<td>ng-bind</td>
|
|
||||||
<td>Automatically escapes</td>
|
|
||||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
|
||||||
<td><div ng-bind="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</doc:source>
|
|
||||||
<doc:protractor>
|
|
||||||
it('should sanitize the html snippet by default', function() {
|
|
||||||
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
|
|
||||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should inline raw snippet if bound to a trusted value', function() {
|
|
||||||
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
|
|
||||||
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(element(by.css('#bind-default div')).getInnerHtml()).
|
|
||||||
toBe("<p style=\"color:blue\">an html\n" +
|
|
||||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
|
||||||
"snippet</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update', function() {
|
|
||||||
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:protractor>
|
|
||||||
</doc:example>
|
|
||||||
*/
|
|
||||||
function $SanitizeProvider() {
|
|
||||||
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
|
|
||||||
return function(html) {
|
|
||||||
var 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*>/,
|
|
||||||
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,
|
|
||||||
// Match everything outside of normal chars and " (quote character)
|
|
||||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
|
|
||||||
|
|
||||||
|
|
||||||
// Good source of info about elements and attributes
|
|
||||||
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
|
||||||
// http://simon.html5.org/html-elements
|
|
||||||
|
|
||||||
// Safe Void Elements - HTML5
|
|
||||||
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
|
||||||
var voidElements = makeMap("area,br,col,hr,img,wbr");
|
|
||||||
|
|
||||||
// Elements that you can, intentionally, leave open (and which close themselves)
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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"));
|
|
||||||
|
|
||||||
// Inline Elements - HTML5
|
|
||||||
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
|
|
||||||
"bdi,bdo,big,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"));
|
|
||||||
|
|
||||||
|
|
||||||
// Special Elements (can contain anything)
|
|
||||||
var specialElements = makeMap("script,style");
|
|
||||||
|
|
||||||
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");
|
|
||||||
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,size,span,start,summary,target,title,type,'+
|
|
||||||
'valign,value,vspace,width'));
|
|
||||||
|
|
||||||
function makeMap(str) {
|
|
||||||
var obj = {}, items = str.split(','), i;
|
|
||||||
for (i = 0; i < items.length; i++) obj[items[i]] = true;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example
|
|
||||||
* htmlParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {string} html string
|
|
||||||
* @param {object} handler
|
|
||||||
*/
|
|
||||||
function htmlParser( html, handler ) {
|
|
||||||
var index, chars, match, stack = [], last = html;
|
|
||||||
stack.last = function() { return stack[ stack.length - 1 ]; };
|
|
||||||
|
|
||||||
while ( html ) {
|
|
||||||
chars = true;
|
|
||||||
|
|
||||||
// Make sure we're not in a script or style element
|
|
||||||
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
|
||||||
|
|
||||||
// Comment
|
|
||||||
if ( html.indexOf("<!--") === 0 ) {
|
|
||||||
// comments containing -- are not allowed unless they terminate the comment
|
|
||||||
index = html.indexOf("--", 4);
|
|
||||||
|
|
||||||
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 );
|
|
||||||
|
|
||||||
if ( match ) {
|
|
||||||
html = html.substring( match[0].length );
|
|
||||||
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start tag
|
|
||||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
|
||||||
match = html.match( START_TAG_REGEXP );
|
|
||||||
|
|
||||||
if ( match ) {
|
|
||||||
html = html.substring( match[0].length );
|
|
||||||
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( chars ) {
|
|
||||||
index = html.indexOf("<");
|
|
||||||
|
|
||||||
var text = index < 0 ? html : html.substring( 0, index );
|
|
||||||
html = index < 0 ? "" : html.substring( index );
|
|
||||||
|
|
||||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
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) );
|
|
||||||
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
|
|
||||||
parseEndTag( "", stack.last() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( html == last ) {
|
|
||||||
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
|
|
||||||
"of html: {0}", html);
|
|
||||||
}
|
|
||||||
last = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any remaining tags
|
|
||||||
parseEndTag();
|
|
||||||
|
|
||||||
function parseStartTag( tag, tagName, rest, unary ) {
|
|
||||||
tagName = angular.lowercase(tagName);
|
|
||||||
if ( blockElements[ tagName ] ) {
|
|
||||||
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
|
||||||
parseEndTag( "", stack.last() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
|
|
||||||
parseEndTag( "", tagName );
|
|
||||||
}
|
|
||||||
|
|
||||||
unary = voidElements[ tagName ] || !!unary;
|
|
||||||
|
|
||||||
if ( !unary )
|
|
||||||
stack.push( tagName );
|
|
||||||
|
|
||||||
var attrs = {};
|
|
||||||
|
|
||||||
rest.replace(ATTR_REGEXP,
|
|
||||||
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
|
|
||||||
var value = doubleQuotedValue
|
|
||||||
|| singleQuotedValue
|
|
||||||
|| unquotedValue
|
|
||||||
|| '';
|
|
||||||
|
|
||||||
attrs[name] = decodeEntities(value);
|
|
||||||
});
|
|
||||||
if (handler.start) handler.start( tagName, attrs, unary );
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEndTag( tag, tagName ) {
|
|
||||||
var pos = 0, i;
|
|
||||||
tagName = angular.lowercase(tagName);
|
|
||||||
if ( tagName )
|
|
||||||
// Find the closest opened tag of the same type
|
|
||||||
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
|
||||||
if ( stack[ pos ] == tagName )
|
|
||||||
break;
|
|
||||||
|
|
||||||
if ( pos >= 0 ) {
|
|
||||||
// Close all the open elements, up the stack
|
|
||||||
for ( i = stack.length - 1; i >= pos; i-- )
|
|
||||||
if (handler.end) handler.end( stack[ i ] );
|
|
||||||
|
|
||||||
// Remove the open elements from the stack
|
|
||||||
stack.length = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
function decodeEntities(value) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes all potentially dangerous characters, so that the
|
|
||||||
* resulting string can be safely inserted into attribute or
|
|
||||||
* element text.
|
|
||||||
* @param value
|
|
||||||
* @returns escaped text
|
|
||||||
*/
|
|
||||||
function encodeEntities(value) {
|
|
||||||
return value.
|
|
||||||
replace(/&/g, '&').
|
|
||||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
|
||||||
return '&#' + value.charCodeAt(0) + ';';
|
|
||||||
}).
|
|
||||||
replace(/</g, '<').
|
|
||||||
replace(/>/g, '>');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create an HTML/XML writer which writes to buffer
|
|
||||||
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
|
||||||
* @returns {object} in the form of {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
function htmlSanitizeWriter(buf, uriValidator){
|
|
||||||
var ignore = false;
|
|
||||||
var out = angular.bind(buf, buf.push);
|
|
||||||
return {
|
|
||||||
start: function(tag, attrs, unary){
|
|
||||||
tag = angular.lowercase(tag);
|
|
||||||
if (!ignore && specialElements[tag]) {
|
|
||||||
ignore = tag;
|
|
||||||
}
|
|
||||||
if (!ignore && validElements[tag] === true) {
|
|
||||||
out('<');
|
|
||||||
out(tag);
|
|
||||||
angular.forEach(attrs, function(value, key){
|
|
||||||
var lkey=angular.lowercase(key);
|
|
||||||
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
|
|
||||||
if (validAttrs[lkey] === true &&
|
|
||||||
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
|
|
||||||
out(' ');
|
|
||||||
out(key);
|
|
||||||
out('="');
|
|
||||||
out(encodeEntities(value));
|
|
||||||
out('"');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
out(unary ? '/>' : '>');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end: function(tag){
|
|
||||||
tag = angular.lowercase(tag);
|
|
||||||
if (!ignore && validElements[tag] === true) {
|
|
||||||
out('</');
|
|
||||||
out(tag);
|
|
||||||
out('>');
|
|
||||||
}
|
|
||||||
if (tag == ignore) {
|
|
||||||
ignore = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chars: function(chars){
|
|
||||||
if (!ignore) {
|
|
||||||
out(encodeEntities(chars));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// define ngSanitize module and register $sanitize service
|
|
||||||
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
||||||
|
|
||||||
/* global sanitizeText: false */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc filter
|
|
||||||
* @name ngSanitize.filter:linky
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
|
||||||
* plain email address links.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
|
|
||||||
*
|
|
||||||
* @param {string} text Input text.
|
|
||||||
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
|
|
||||||
* @returns {string} Html-linkified text.
|
|
||||||
*
|
|
||||||
* @usage
|
|
||||||
<span ng-bind-html="linky_expression | linky"></span>
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example module="ngSanitize">
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function Ctrl($scope) {
|
|
||||||
$scope.snippet =
|
|
||||||
'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/.';
|
|
||||||
$scope.snippetWithTarget = 'http://angularjs.org/';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div ng-controller="Ctrl">
|
|
||||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Filter</td>
|
|
||||||
<td>Source</td>
|
|
||||||
<td>Rendered</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="linky-filter">
|
|
||||||
<td>linky filter</td>
|
|
||||||
<td>
|
|
||||||
<pre><div ng-bind-html="snippet | linky"><br></div></pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div ng-bind-html="snippet | linky"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="linky-target">
|
|
||||||
<td>linky target</td>
|
|
||||||
<td>
|
|
||||||
<pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="escaped-html">
|
|
||||||
<td>no filter</td>
|
|
||||||
<td><pre><div ng-bind="snippet"><br></div></pre></td>
|
|
||||||
<td><div ng-bind="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</doc:source>
|
|
||||||
<doc:protractor>
|
|
||||||
it('should linkify the snippet with urls', function() {
|
|
||||||
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(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() {
|
|
||||||
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(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:protractor>
|
|
||||||
</doc:example>
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
if (!text) return text;
|
|
||||||
var match;
|
|
||||||
var raw = text;
|
|
||||||
var html = [];
|
|
||||||
var url;
|
|
||||||
var i;
|
|
||||||
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;
|
|
||||||
addText(raw.substr(0, i));
|
|
||||||
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
|
|
||||||
raw = raw.substring(i + match[0].length);
|
|
||||||
}
|
|
||||||
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);
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for jstd scenario adapter
|
|
||||||
*/
|
|
||||||
var jstdScenarioAdapter = {
|
|
||||||
relativeUrlPrefix: '/build/docs/'
|
|
||||||
};
|
|
|
@ -1,202 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.5
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSTestDriver adapter for angular scenario tests
|
|
||||||
*
|
|
||||||
* Example of jsTestDriver.conf for running scenario tests with JSTD:
|
|
||||||
<pre>
|
|
||||||
server: http://localhost:9877
|
|
||||||
|
|
||||||
load:
|
|
||||||
- lib/angular-scenario.js
|
|
||||||
- lib/jstd-scenario-adapter-config.js
|
|
||||||
- lib/jstd-scenario-adapter.js
|
|
||||||
# your test files go here #
|
|
||||||
|
|
||||||
proxy:
|
|
||||||
- {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
|
|
||||||
* Note the order of files - it's important !
|
|
||||||
*
|
|
||||||
* Example of jstd-scenario-adapter-config.js
|
|
||||||
<pre>
|
|
||||||
var jstdScenarioAdapter = {
|
|
||||||
relativeUrlPrefix: '/your-prefix/'
|
|
||||||
};
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
|
|
||||||
* You have to configure this to work together with JSTD proxy.
|
|
||||||
*
|
|
||||||
* Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
|
|
||||||
* Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
|
|
||||||
* That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom type of test case
|
|
||||||
*
|
|
||||||
* @const
|
|
||||||
* @see jstestdriver.TestCaseInfo
|
|
||||||
*/
|
|
||||||
var SCENARIO_TYPE = 'scenario';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin for JSTestDriver
|
|
||||||
* Connection point between scenario's jstd output and jstestdriver.
|
|
||||||
*
|
|
||||||
* @see jstestdriver.PluginRegistrar
|
|
||||||
*/
|
|
||||||
function JstdPlugin() {
|
|
||||||
var nop = function() {};
|
|
||||||
|
|
||||||
this.reportResult = nop;
|
|
||||||
this.reportEnd = nop;
|
|
||||||
this.runScenario = nop;
|
|
||||||
|
|
||||||
this.name = 'Angular Scenario Adapter';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called for each JSTD TestCase
|
|
||||||
*
|
|
||||||
* Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
|
|
||||||
* Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
|
|
||||||
*
|
|
||||||
* @param {jstestdriver.TestRunConfiguration} configuration
|
|
||||||
* @param {Function} onTestDone
|
|
||||||
* @param {Function} onAllTestsComplete
|
|
||||||
* @returns {boolean} True if this type of test is handled by this plugin, false otherwise
|
|
||||||
*/
|
|
||||||
this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
|
|
||||||
if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
|
|
||||||
|
|
||||||
this.reportResult = onTestDone;
|
|
||||||
this.reportEnd = onAllTestsComplete;
|
|
||||||
this.runScenario();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
|
|
||||||
testRunsConfiguration.push(
|
|
||||||
new jstestdriver.TestRunConfiguration(
|
|
||||||
new jstestdriver.TestCaseInfo(
|
|
||||||
'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton instance of the plugin
|
|
||||||
* Accessed using closure by:
|
|
||||||
* - jstd output (reports to this plugin)
|
|
||||||
* - initScenarioAdapter (register the plugin to jstd)
|
|
||||||
*/
|
|
||||||
var plugin = new JstdPlugin();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise scenario jstd-adapter
|
|
||||||
* (only if jstestdriver is defined)
|
|
||||||
*
|
|
||||||
* @param {Object} jstestdriver Undefined when run from browser (without jstd)
|
|
||||||
* @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
|
|
||||||
* @param {Object=} config Configuration object, supported properties:
|
|
||||||
* - relativeUrlPrefix: prefix for all relative links when navigateTo()
|
|
||||||
*/
|
|
||||||
function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
|
|
||||||
if (jstestdriver) {
|
|
||||||
// create and register ScenarioPlugin
|
|
||||||
jstestdriver.pluginRegistrar.register(plugin);
|
|
||||||
plugin.runScenario = initScenarioAndRun;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HACK (angular.scenario.Application.navigateTo)
|
|
||||||
*
|
|
||||||
* We need to navigate to relative urls when running from browser (without JSTD),
|
|
||||||
* because we want to allow running scenario tests without creating its own virtual host.
|
|
||||||
* For example: http://angular.local/build/docs/docs-scenario.html
|
|
||||||
*
|
|
||||||
* On the other hand, when running with JSTD, we need to navigate to absolute urls,
|
|
||||||
* because of JSTD proxy. (proxy, because of same domain policy)
|
|
||||||
*
|
|
||||||
* So this hack is applied only if running with JSTD and change all relative urls to absolute.
|
|
||||||
*/
|
|
||||||
var appProto = angular.scenario.Application.prototype,
|
|
||||||
navigateTo = appProto.navigateTo,
|
|
||||||
relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
|
|
||||||
|
|
||||||
appProto.navigateTo = function(url, loadFn, errorFn) {
|
|
||||||
if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
|
|
||||||
url != 'about:blank' && !url.match(/^https?/)) {
|
|
||||||
url = relativeUrlPrefix + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return navigateTo.call(this, url, loadFn, errorFn);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds proper TestResult object from given model spec
|
|
||||||
*
|
|
||||||
* TODO(vojta) report error details
|
|
||||||
*
|
|
||||||
* @param {angular.scenario.ObjectModel.Spec} spec
|
|
||||||
* @returns {jstestdriver.TestResult}
|
|
||||||
*/
|
|
||||||
function createTestResultFromSpec(spec) {
|
|
||||||
var map = {
|
|
||||||
success: 'PASSED',
|
|
||||||
error: 'ERROR',
|
|
||||||
failure: 'FAILED'
|
|
||||||
};
|
|
||||||
|
|
||||||
return new jstestdriver.TestResult(
|
|
||||||
spec.fullDefinitionName,
|
|
||||||
spec.name,
|
|
||||||
jstestdriver.TestResult.RESULT[map[spec.status]],
|
|
||||||
spec.error || '',
|
|
||||||
spec.line || '',
|
|
||||||
spec.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates JSTD output (jstestdriver.TestResult)
|
|
||||||
*/
|
|
||||||
angular.scenario.output('jstd', function(context, runner, model) {
|
|
||||||
model.on('SpecEnd', function(spec) {
|
|
||||||
plugin.reportResult(createTestResultFromSpec(spec));
|
|
||||||
});
|
|
||||||
|
|
||||||
model.on('RunnerEnd', function() {
|
|
||||||
plugin.reportEnd();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
|
|
||||||
})(window);
|
|
|
@ -1,239 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhanced Select2 Dropmenus
|
|
||||||
*
|
|
||||||
* @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
|
|
||||||
* This change is so that you do not have to do an additional query yourself on top of Select2's own query
|
|
||||||
* @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
|
|
||||||
*/
|
|
||||||
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
|
|
||||||
var options = {};
|
|
||||||
if (uiSelect2Config) {
|
|
||||||
angular.extend(options, uiSelect2Config);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
require: 'ngModel',
|
|
||||||
priority: 1,
|
|
||||||
compile: function (tElm, tAttrs) {
|
|
||||||
var watch,
|
|
||||||
repeatOption,
|
|
||||||
repeatAttr,
|
|
||||||
isSelect = tElm.is('select'),
|
|
||||||
isMultiple = angular.isDefined(tAttrs.multiple);
|
|
||||||
|
|
||||||
// Enable watching of the options dataset if in use
|
|
||||||
if (tElm.is('select')) {
|
|
||||||
repeatOption = tElm.find( 'optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]');
|
|
||||||
|
|
||||||
if (repeatOption.length) {
|
|
||||||
repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
|
|
||||||
watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function (scope, elm, attrs, controller) {
|
|
||||||
// instance-specific options
|
|
||||||
var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert from Select2 view-model to Angular view-model.
|
|
||||||
*/
|
|
||||||
var convertToAngularModel = function(select2_data) {
|
|
||||||
var model;
|
|
||||||
if (opts.simple_tags) {
|
|
||||||
model = [];
|
|
||||||
angular.forEach(select2_data, function(value, index) {
|
|
||||||
model.push(value.id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
model = select2_data;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert from Angular view-model to Select2 view-model.
|
|
||||||
*/
|
|
||||||
var convertToSelect2Model = function(angular_data) {
|
|
||||||
var model = [];
|
|
||||||
if (!angular_data) {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.simple_tags) {
|
|
||||||
model = [];
|
|
||||||
angular.forEach(
|
|
||||||
angular_data,
|
|
||||||
function(value, index) {
|
|
||||||
model.push({'id': value, 'text': value});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
model = angular_data;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isSelect) {
|
|
||||||
// Use <select multiple> instead
|
|
||||||
delete opts.multiple;
|
|
||||||
delete opts.initSelection;
|
|
||||||
} else if (isMultiple) {
|
|
||||||
opts.multiple = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller) {
|
|
||||||
// Watch the model for programmatic changes
|
|
||||||
scope.$watch(tAttrs.ngModel, function(current, old) {
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (current === old) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
controller.$render();
|
|
||||||
}, true);
|
|
||||||
controller.$render = function () {
|
|
||||||
if (isSelect) {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
} else {
|
|
||||||
if (opts.multiple) {
|
|
||||||
var viewValue = controller.$viewValue;
|
|
||||||
if (angular.isString(viewValue)) {
|
|
||||||
viewValue = viewValue.split(',');
|
|
||||||
}
|
|
||||||
elm.select2(
|
|
||||||
'data', convertToSelect2Model(viewValue));
|
|
||||||
} else {
|
|
||||||
if (angular.isObject(controller.$viewValue)) {
|
|
||||||
elm.select2('data', controller.$viewValue);
|
|
||||||
} else if (!controller.$viewValue) {
|
|
||||||
elm.select2('data', null);
|
|
||||||
} else {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Watch the options dataset for changes
|
|
||||||
if (watch) {
|
|
||||||
scope.$watch(watch, function (newVal, oldVal, scope) {
|
|
||||||
if (angular.equals(newVal, oldVal)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Delayed so that the options have time to be rendered
|
|
||||||
$timeout(function () {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
// Refresh angular to remove the superfluous option
|
|
||||||
elm.trigger('change');
|
|
||||||
if(newVal && !oldVal && controller.$setPristine) {
|
|
||||||
controller.$setPristine(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update valid and dirty statuses
|
|
||||||
controller.$parsers.push(function (value) {
|
|
||||||
var div = elm.prev();
|
|
||||||
div
|
|
||||||
.toggleClass('ng-invalid', !controller.$valid)
|
|
||||||
.toggleClass('ng-valid', controller.$valid)
|
|
||||||
.toggleClass('ng-invalid-required', !controller.$valid)
|
|
||||||
.toggleClass('ng-valid-required', controller.$valid)
|
|
||||||
.toggleClass('ng-dirty', controller.$dirty)
|
|
||||||
.toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isSelect) {
|
|
||||||
// Set the view and model value and update the angular template manually for the ajax/multiple select2.
|
|
||||||
elm.bind("change", function (e) {
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
|
|
||||||
if (scope.$$phase || scope.$root.$$phase) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scope.$apply(function () {
|
|
||||||
controller.$setViewValue(
|
|
||||||
convertToAngularModel(elm.select2('data')));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.initSelection) {
|
|
||||||
var initSelection = opts.initSelection;
|
|
||||||
opts.initSelection = function (element, callback) {
|
|
||||||
initSelection(element, function (value) {
|
|
||||||
var isPristine = controller.$pristine;
|
|
||||||
controller.$setViewValue(convertToAngularModel(value));
|
|
||||||
callback(value);
|
|
||||||
if (isPristine) {
|
|
||||||
controller.$setPristine();
|
|
||||||
}
|
|
||||||
elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elm.bind("$destroy", function() {
|
|
||||||
elm.select2("destroy");
|
|
||||||
});
|
|
||||||
|
|
||||||
attrs.$observe('disabled', function (value) {
|
|
||||||
elm.select2('enable', !value);
|
|
||||||
});
|
|
||||||
|
|
||||||
attrs.$observe('readonly', function (value) {
|
|
||||||
elm.select2('readonly', !!value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (attrs.ngMultiple) {
|
|
||||||
scope.$watch(attrs.ngMultiple, function(newVal) {
|
|
||||||
attrs.$set('multiple', !!newVal);
|
|
||||||
elm.select2(opts);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the plugin late so that the injected DOM does not disrupt the template compiler
|
|
||||||
$timeout(function () {
|
|
||||||
elm.select2(opts);
|
|
||||||
|
|
||||||
// Set initial value - I'm not sure about this but it seems to need to be there
|
|
||||||
elm.select2('data', controller.$modelValue);
|
|
||||||
// important!
|
|
||||||
controller.$render();
|
|
||||||
|
|
||||||
// Not sure if I should just check for !isSelect OR if I should check for 'tags' key
|
|
||||||
if (!opts.initSelection && !isSelect) {
|
|
||||||
var isPristine = controller.$pristine;
|
|
||||||
controller.$setViewValue(
|
|
||||||
convertToAngularModel(elm.select2('data'))
|
|
||||||
);
|
|
||||||
if (isPristine) {
|
|
||||||
controller.$setPristine();
|
|
||||||
}
|
|
||||||
elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
||||||
{"full":"1.0.7","major":"1","minor":"0","dot":"7","codename":"monochromatic-rainbow","cdn":"1.0.6"}
|
|
|
@ -1 +0,0 @@
|
||||||
1.0.7
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"realm" : "saml-broker-authentication-realm",
|
|
||||||
"resource" : "saml-broker-authentication",
|
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "/auth",
|
|
||||||
"ssl-required" : "external",
|
|
||||||
"public-client" : true
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
# Keycloak Broker: Twitter Social Identity Provider Quickstarts
|
|
||||||
|
|
||||||
What is it?
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This example demonstrates how to use Social Identity Providers with Keycloak to authenticate users. In this case,
|
|
||||||
users are authenticated with Twitter using Keycloak Identity Broker capabilities using the oAuth 2 protocol.
|
|
||||||
|
|
||||||
From this example, you'll learn how to:
|
|
||||||
|
|
||||||
* Set up a social identity provider for a specific realm
|
|
||||||
* Store tokens from a social identity provider and use these tokens to invoke the social provider API
|
|
||||||
|
|
||||||
Basically, once you try to access the application for the first time, you'll be redirected to Keycloak's login page.
|
|
||||||
In this page you'll note that there is a "Twitter" button that allows you to authenticate with Twitter Identity Provider.
|
|
||||||
|
|
||||||
After clicking the "Twitter" button, you'll be redirected to Twitter's login page from where you must authenticate
|
|
||||||
and grant the necessary permissions to Keycloak in order to access your personal information from Twitter.
|
|
||||||
|
|
||||||
If everything is fine, Twitter will redirect you back to Keycloak and at this point you'll be asked to provide some
|
|
||||||
basic profile information in order to create a new user in Keycloak based on your social account. Once you update your profile,
|
|
||||||
you'll be authenticated and redirected to the application.
|
|
||||||
|
|
||||||
Basically, what the application does is obtain some basic information for the authenticated user and also allow users to
|
|
||||||
load their profile from Twitter. For that, this application demonstrates how to retrieve the token issued by a social provider
|
|
||||||
for the authenticated user and use this token to invoke Twitter's API.
|
|
||||||
|
|
||||||
Make sure you've set up an application in Twitter
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
This example application requires you to create a Twitter Application. How to create it is beyond the scope of this
|
|
||||||
documentation.
|
|
||||||
|
|
||||||
Please take a look on [Twitter Developer Console](https://dev.twitter.com/apps) for more details.
|
|
||||||
|
|
||||||
Once you have a Twitter Application configured, you need to obtain both **Consumer Key** and **Consumer Secret** and update the
|
|
||||||
**twitter-identity-provider-realm.json** configuration file with these information. There you'll find a section as follows:
|
|
||||||
|
|
||||||
"identityProviders": [
|
|
||||||
{
|
|
||||||
"id" : "twitter",
|
|
||||||
"providerId" : "twitter",
|
|
||||||
"name" : "Twitter",
|
|
||||||
"enabled": true,
|
|
||||||
"updateProfileFirstLogin" : "true",
|
|
||||||
"storeToken" : "true",
|
|
||||||
"config": {
|
|
||||||
"clientId": "CHANGE_CLIENT_ID",
|
|
||||||
"clientSecret": "CHANGE_CLIENT_SECRET"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Please, update both *clientId* and *clientSecret* configuration options with the **Consumer Key** and **Consumer Secret**.
|
|
||||||
|
|
||||||
Make sure you've set up the Keycloak Server
|
|
||||||
--------------------------------------
|
|
||||||
The Keycloak Appliance Distribution comes with a preconfigured Keycloak server (based on Wildfly). You can use it out of
|
|
||||||
the box to run these demos. So, if you're using this, you can head to Step 2.
|
|
||||||
|
|
||||||
Alternatively, you can install the Keycloak Server onto any EAP 6.x, or Wildfly 8.x server, but there is
|
|
||||||
a few steps you must follow.
|
|
||||||
|
|
||||||
Obtain latest keycloak-war-dist-all.zip. This distro is used to install Keycloak onto an existing JBoss installation.
|
|
||||||
This installs the server.
|
|
||||||
|
|
||||||
$ cd ${wildfly.jboss.home}/standalone
|
|
||||||
$ cp -r ${keycloak-war-dist-all}/deployments .
|
|
||||||
|
|
||||||
To be able to run the demos you also need to install the Keycloak client adapter. For Wildfly:
|
|
||||||
|
|
||||||
$ cd ${wildfly.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-wildfly-adapter-dist.zip
|
|
||||||
|
|
||||||
For JBoss EAP 6.x
|
|
||||||
|
|
||||||
$ cd ${eap.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-eap6-adapter-dist.zip
|
|
||||||
|
|
||||||
For JBoss AS 7.1.1:
|
|
||||||
|
|
||||||
$ cd ${as7.home}
|
|
||||||
$ unzip ${keycloak-war-dist-all}/adapters/keycloak-as7-adapter-dist.zip
|
|
||||||
|
|
||||||
Unzipping the adapter ZIP only installs the JAR files. You must also add the Keycloak Subsystem to the server's
|
|
||||||
configuration (standalone/configuration/standalone.xml).
|
|
||||||
|
|
||||||
<server xmlns="urn:jboss:domain:1.4">
|
|
||||||
|
|
||||||
<extensions>
|
|
||||||
<extension module="org.keycloak.keycloak-subsystem"/>
|
|
||||||
...
|
|
||||||
</extensions>
|
|
||||||
|
|
||||||
<profile>
|
|
||||||
<subsystem xmlns="urn:jboss:domain:keycloak:1.0"/>
|
|
||||||
...
|
|
||||||
</profile>
|
|
||||||
|
|
||||||
Boot Keycloak Server
|
|
||||||
---------------------------------------
|
|
||||||
Where you go to start up the Keycloak Server depends on which distro you installed.
|
|
||||||
|
|
||||||
From appliance:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd keycloak/bin
|
|
||||||
$ ./standalone.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
From existing Wildfly/EAP6/AS7 distro
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd ${wildfly.jboss.home}/bin
|
|
||||||
$ ./standalone.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Import the Test Realm
|
|
||||||
---------------------------------------
|
|
||||||
Next thing you have to do is import the test realm for the demo. Clicking on the below link will bring you to the
|
|
||||||
create realm page in the Admin UI. The username/password is admin/admin to login in. Keycloak will ask you to
|
|
||||||
create a new admin password before you can go to the create realm page.
|
|
||||||
|
|
||||||
[http://localhost:8080/auth/admin/master/console/#/create/realm](http://localhost:8080/auth/admin/master/console/#/create/realm)
|
|
||||||
|
|
||||||
Import the **twitter-identity-provider-realm.json** file that is in the twitter-authentication/ example directory.
|
|
||||||
|
|
||||||
|
|
||||||
Start JBoss Enterprise Application Platform 6 or WildFly with the Web Profile
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
1. Open a command line and navigate to the root of the JBoss server directory.
|
|
||||||
2. The following shows the command line to start the server with the web profile:
|
|
||||||
|
|
||||||
For Linux: JBOSS_HOME/bin/standalone.sh
|
|
||||||
For Windows: JBOSS_HOME\bin\standalone.bat
|
|
||||||
|
|
||||||
|
|
||||||
Build and Deploy the Quickstart
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
_NOTE: The following build command assumes you have configured your Maven user settings. If you have not, you must include Maven setting arguments on the command line. See [Build and Deploy the Quickstarts](../README.md#build-and-deploy-the-quickstarts) for complete instructions and additional options._
|
|
||||||
|
|
||||||
1. Make sure you have started the JBoss Server as described above.
|
|
||||||
2. Open a command line and navigate to the root directory of this quickstart.
|
|
||||||
3. Type this command to build and deploy the archive:
|
|
||||||
|
|
||||||
For EAP 6: mvn clean package jboss-as:deploy
|
|
||||||
For WildFly: mvn -Pwildfly clean package wildfly:deploy
|
|
||||||
|
|
||||||
4. This will deploy `target/twitter-authentication.war` to the running instance of the server.
|
|
||||||
|
|
||||||
|
|
||||||
Access the application
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The application will be running at the following URL: <http://localhost:8080/twitter-authentication/index.html>. This is angular based application.
|
|
||||||
In addition, the example contains testing servlet, which will show you JSON with full info about your Twitter account. Servlet is accessible
|
|
||||||
on [http://localhost:8080/twitter-authentication/twitter/showUser](http://localhost:8080/twitter-authentication/twitter/showUser) .
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Undeploy the Archive
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
1. Make sure you have started the JBoss Server as described above.
|
|
||||||
2. Open a command line and navigate to the root directory of this quickstart.
|
|
||||||
3. When you are finished testing, type this command to undeploy the archive:
|
|
||||||
|
|
||||||
For EAP 6: mvn jboss-as:undeploy
|
|
||||||
For WildFly: mvn -Pwildfly wildfly:undeploy
|
|
||||||
|
|
||||||
|
|
||||||
Debug the Application
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
If you want to debug the source code or look at the Javadocs of any library in the project, run either of the following commands to pull them into your local repository. The IDE should then detect them.
|
|
||||||
|
|
||||||
mvn dependency:sources
|
|
||||||
mvn dependency:resolve -Dclassifier=javadoc
|
|
|
@ -1,82 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-examples-broker-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Keycloak Broker Examples - Twitter Authentication</name>
|
|
||||||
<artifactId>twitter-authentication</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<description>
|
|
||||||
An example about how to authenticate with Twitter
|
|
||||||
</description>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-admin-client</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.twitter4j</groupId>
|
|
||||||
<artifactId>twitter4j-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
|
||||||
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>twitter-authentication</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.examples.broker.twitter;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author pedroigor
|
|
||||||
*/
|
|
||||||
public class TwitterOAuthResponse {
|
|
||||||
|
|
||||||
@JsonProperty("oauth_token")
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
@JsonProperty("oauth_token_secret")
|
|
||||||
private String tokenSecret;
|
|
||||||
|
|
||||||
@JsonProperty("screen_name")
|
|
||||||
private String screenName;
|
|
||||||
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
public String getToken() {
|
|
||||||
return this.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToken(String token) {
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTokenSecret() {
|
|
||||||
return this.tokenSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTokenSecret(String tokenSecret) {
|
|
||||||
this.tokenSecret = tokenSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getScreenName() {
|
|
||||||
return this.screenName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setScreenName(String screenName) {
|
|
||||||
this.screenName = screenName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserId() {
|
|
||||||
return this.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserId(String userId) {
|
|
||||||
this.userId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.examples.broker.twitter;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.admin.client.Keycloak;
|
|
||||||
import org.keycloak.admin.client.resource.IdentityProvidersResource;
|
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
|
||||||
import twitter4j.Twitter;
|
|
||||||
import twitter4j.TwitterException;
|
|
||||||
import twitter4j.TwitterFactory;
|
|
||||||
import twitter4j.User;
|
|
||||||
import twitter4j.conf.ConfigurationBuilder;
|
|
||||||
|
|
||||||
import javax.servlet.ServletConfig;
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.annotation.WebServlet;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.ws.rs.client.Client;
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
import javax.ws.rs.client.ClientRequestContext;
|
|
||||||
import javax.ws.rs.client.ClientRequestFilter;
|
|
||||||
import javax.ws.rs.client.WebTarget;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>A simple servlet to proxy Twitter API using the Twitter4j library.</p>
|
|
||||||
*
|
|
||||||
* <p>It provides some additional code to properly handle token retrieval from the Twitter identity provider in Keycloak
|
|
||||||
* and use that token to invoke Twitter's API.</p>
|
|
||||||
*
|
|
||||||
* @author pedroigor
|
|
||||||
*/
|
|
||||||
@WebServlet(urlPatterns = "/twitter/showUser")
|
|
||||||
public class TwitterShowUserServlet extends HttpServlet {
|
|
||||||
|
|
||||||
private Keycloak keycloak;
|
|
||||||
private String authServer;
|
|
||||||
private String realmName;
|
|
||||||
private IdentityProviderRepresentation identityProvider;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(ServletConfig config) throws ServletException {
|
|
||||||
initKeycloakClient(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
this.keycloak.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doGet(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
|
||||||
TwitterOAuthResponse twitterOAuthResponse = getTwitterOAuthResponse(request);
|
|
||||||
ConfigurationBuilder cb = new ConfigurationBuilder();
|
|
||||||
|
|
||||||
cb.setDebugEnabled(true)
|
|
||||||
.setOAuthConsumerKey(this.identityProvider.getConfig().get("clientId"))
|
|
||||||
.setOAuthConsumerSecret(this.identityProvider.getConfig().get("clientSecret"))
|
|
||||||
.setOAuthAccessToken(twitterOAuthResponse.getToken())
|
|
||||||
.setOAuthAccessTokenSecret(twitterOAuthResponse.getTokenSecret());
|
|
||||||
|
|
||||||
TwitterFactory tf = new TwitterFactory(cb.build());
|
|
||||||
Twitter twitter = tf.getInstance();
|
|
||||||
|
|
||||||
try {
|
|
||||||
User user = twitter.users().showUser(twitterOAuthResponse.getScreenName());
|
|
||||||
|
|
||||||
response.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
|
|
||||||
PrintWriter writer = response.getWriter();
|
|
||||||
|
|
||||||
writer.println(new ObjectMapper().writeValueAsString(user));
|
|
||||||
|
|
||||||
writer.flush();
|
|
||||||
} catch (TwitterException e) {
|
|
||||||
throw new RuntimeException("Could not load social profile.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TwitterOAuthResponse getTwitterOAuthResponse(final HttpServletRequest req) {
|
|
||||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
|
||||||
@Override
|
|
||||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
|
||||||
KeycloakSecurityContext securityContext = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
|
|
||||||
String accessToken = securityContext.getTokenString();
|
|
||||||
|
|
||||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
|
||||||
WebTarget target = client.target(getIdentityProviderTokenUrl());
|
|
||||||
|
|
||||||
return target.request().get().readEntity(TwitterOAuthResponse.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getIdentityProviderTokenUrl() {
|
|
||||||
return this.authServer + "/realms/" + this.realmName + "/broker/" + this.identityProvider.getAlias() + "/token";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initKeycloakClient(ServletConfig config) {
|
|
||||||
ServletContext servletContext = config.getServletContext();
|
|
||||||
JsonNode keycloakConfig;
|
|
||||||
|
|
||||||
try {
|
|
||||||
keycloakConfig = new ObjectMapper().readTree(servletContext.getResourceAsStream("WEB-INF/keycloak.json"));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Could not parse keycloak config.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.authServer = keycloakConfig.get("auth-server-url").asText();
|
|
||||||
this.realmName = keycloakConfig.get("realm").asText();
|
|
||||||
this.keycloak = Keycloak.getInstance(authServer, realmName, "admin", "password", "admin-client", "password");
|
|
||||||
IdentityProvidersResource providersResource = keycloak.realm(realmName).identityProviders();
|
|
||||||
this.identityProvider = providersResource.get("twitter").toRepresentation();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"realm" : "twitter-identity-provider-realm",
|
|
||||||
"resource" : "twitter-authentication",
|
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
|
||||||
"ssl-required" : "external",
|
|
||||||
"public-client" : true
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
|
||||||
version="3.0">
|
|
||||||
|
|
||||||
<module-name>twitter-authentication</module-name>
|
|
||||||
|
|
||||||
<security-constraint>
|
|
||||||
<web-resource-collection>
|
|
||||||
<web-resource-name>Protected Resources</web-resource-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</web-resource-collection>
|
|
||||||
<!-- <user-data-constraint>
|
|
||||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
|
||||||
</user-data-constraint> -->
|
|
||||||
<auth-constraint>
|
|
||||||
<role-name>user</role-name>
|
|
||||||
</auth-constraint>
|
|
||||||
</security-constraint>
|
|
||||||
|
|
||||||
<login-config>
|
|
||||||
<auth-method>KEYCLOAK</auth-method>
|
|
||||||
<realm-name>twitter-authentication</realm-name>
|
|
||||||
</login-config>
|
|
||||||
|
|
||||||
<security-role>
|
|
||||||
<role-name>user</role-name>
|
|
||||||
</security-role>
|
|
||||||
</web-app>
|
|
|
@ -1,68 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Twitter Authentication Example</title>
|
|
||||||
|
|
||||||
<script src="js/lib/angular/angular.js"></script>
|
|
||||||
<script src="js/lib/angular/angular-resource.js"></script>
|
|
||||||
<script src="js/lib/angular/angular-route.js"></script>
|
|
||||||
<script src="js/lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
|
|
||||||
|
|
||||||
<script src="/auth/js/keycloak.js"></script>
|
|
||||||
<script src="js/app.js" type="text/javascript"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body data-ng-controller="GlobalCtrl">
|
|
||||||
|
|
||||||
<div id="content-area" class="col-md-9" role="main">
|
|
||||||
<div id="content">
|
|
||||||
<h2>Hello, {{identity.name}} [<a href="" ng-click="logout()">Sign Out</a>]</h2>
|
|
||||||
<div>
|
|
||||||
<p><b>This is your Keycloak Profile</b>:</p>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li><b>Id</b>: {{identity.sub}}</li>
|
|
||||||
<li><b>Username</b>: {{identity.preferred_username}}</li>
|
|
||||||
<li><b>Email</b>: {{identity.email}}</li>
|
|
||||||
<li><b>Full Name</b>: {{identity.name}}</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p><button type="submit" data-ng-click="loadSocialProfile()">Load your social profile</button></p>
|
|
||||||
|
|
||||||
<div data-ng-show="socialProfile">
|
|
||||||
<p><b>This is your Twitter Profile</b>:</p>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li><b>Id</b>: {{socialProfile.id}}</li>
|
|
||||||
<li><b>Name</b>: {{socialProfile.name}}</li>
|
|
||||||
<li><b>Username</b>: {{socialProfile.screenName}}</li>
|
|
||||||
<li><b>Profile Link</b>: <a href="https://twitter.com/{{socialProfile.screenName}}">https://twitter.com/{{socialProfile.screenName}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,116 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var module = angular.module('app', []);
|
|
||||||
|
|
||||||
angular.element(document).ready(function ($http) {
|
|
||||||
var keycloakAuth = new Keycloak('keycloak.json');
|
|
||||||
|
|
||||||
keycloakAuth.init({ onLoad: 'login-required' }).then(function () {
|
|
||||||
module.factory('Auth', function() {
|
|
||||||
var Auth = {};
|
|
||||||
|
|
||||||
Auth.logout = function() {
|
|
||||||
keycloakAuth.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.getIdentity = function() {
|
|
||||||
return keycloakAuth.idTokenParsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.getToken = function() {
|
|
||||||
return keycloakAuth.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
Auth.refreshToken = function() {
|
|
||||||
var url = keycloakAuth.createLoginUrl({
|
|
||||||
idpHint: 'twitter'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(url);
|
|
||||||
|
|
||||||
window.location = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Auth;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('authInterceptor', function($q) {
|
|
||||||
return {
|
|
||||||
request: function (config) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
config.headers = config.headers || {};
|
|
||||||
|
|
||||||
if (!config.headers.Authorization) {
|
|
||||||
config.headers.Authorization = 'Bearer ' + keycloakAuth.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolve(config);
|
|
||||||
|
|
||||||
if (keycloakAuth.token) {
|
|
||||||
keycloakAuth.updateToken(5).then(function() {
|
|
||||||
}).catch(function() {
|
|
||||||
deferred.reject('Failed to refresh token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
module.config(function($httpProvider) {
|
|
||||||
$httpProvider.responseInterceptors.push('errorInterceptor');
|
|
||||||
$httpProvider.interceptors.push('authInterceptor');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('errorInterceptor', function($q) {
|
|
||||||
return function(promise) {
|
|
||||||
return promise.then(function(response) {
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
angular.bootstrap(document, ["app"]);
|
|
||||||
}).catch(function () {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('GlobalCtrl', function($scope, $http, $location, Auth) {
|
|
||||||
$scope.logout = function() {
|
|
||||||
Auth.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.identity = Auth.getIdentity();
|
|
||||||
|
|
||||||
$scope.loadSocialProfile = function() {
|
|
||||||
$http.get('/twitter-authentication/twitter/showUser')
|
|
||||||
.success(function(profile) {
|
|
||||||
$scope.socialProfile = profile;
|
|
||||||
})
|
|
||||||
.error(function(data, status, headers, config) {
|
|
||||||
$scope.socialProfile = 'Could not obtain social profile. Trying to refresh your token.';
|
|
||||||
Auth.refreshToken();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,192 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.7
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var directive = {};
|
|
||||||
|
|
||||||
directive.dropdownToggle =
|
|
||||||
['$document', '$location', '$window',
|
|
||||||
function ($document, $location, $window) {
|
|
||||||
var openElement = null, close;
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.parent().bind('click', function(event) {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
var iWasOpen = false;
|
|
||||||
|
|
||||||
if (openElement) {
|
|
||||||
iWasOpen = openElement === element;
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!iWasOpen){
|
|
||||||
element.parent().addClass('open');
|
|
||||||
openElement = element;
|
|
||||||
|
|
||||||
close = function (event) {
|
|
||||||
event && event.preventDefault();
|
|
||||||
event && event.stopPropagation();
|
|
||||||
$document.unbind('click', close);
|
|
||||||
element.parent().removeClass('open');
|
|
||||||
close = null;
|
|
||||||
openElement = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$document.bind('click', close);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
|
|
||||||
|
|
||||||
directive.tabbable = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
compile: function(element) {
|
|
||||||
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
|
|
||||||
tabContent = angular.element('<div class="tab-content"></div>');
|
|
||||||
|
|
||||||
tabContent.append(element.contents());
|
|
||||||
element.append(navTabs).append(tabContent);
|
|
||||||
},
|
|
||||||
controller: ['$scope', '$element', function($scope, $element) {
|
|
||||||
var navTabs = $element.contents().eq(0),
|
|
||||||
ngModel = $element.controller('ngModel') || {},
|
|
||||||
tabs = [],
|
|
||||||
selectedTab;
|
|
||||||
|
|
||||||
ngModel.$render = function() {
|
|
||||||
var $viewValue = this.$viewValue;
|
|
||||||
|
|
||||||
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
|
|
||||||
if(selectedTab) {
|
|
||||||
selectedTab.paneElement.removeClass('active');
|
|
||||||
selectedTab.tabElement.removeClass('active');
|
|
||||||
selectedTab = null;
|
|
||||||
}
|
|
||||||
if($viewValue) {
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++) {
|
|
||||||
if ($viewValue == tabs[i].value) {
|
|
||||||
selectedTab = tabs[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedTab) {
|
|
||||||
selectedTab.paneElement.addClass('active');
|
|
||||||
selectedTab.tabElement.addClass('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addPane = function(element, attr) {
|
|
||||||
var li = angular.element('<li><a href></a></li>'),
|
|
||||||
a = li.find('a'),
|
|
||||||
tab = {
|
|
||||||
paneElement: element,
|
|
||||||
paneAttrs: attr,
|
|
||||||
tabElement: li
|
|
||||||
};
|
|
||||||
|
|
||||||
tabs.push(tab);
|
|
||||||
|
|
||||||
attr.$observe('value', update)();
|
|
||||||
attr.$observe('title', function(){ update(); a.text(tab.title); })();
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
tab.title = attr.title;
|
|
||||||
tab.value = attr.value || attr.title;
|
|
||||||
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
}
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
|
|
||||||
navTabs.append(li);
|
|
||||||
li.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (ngModel.$setViewValue) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
ngModel.$setViewValue(tab.value);
|
|
||||||
ngModel.$render();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
tab.tabElement.remove();
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
|
|
||||||
if (tab == tabs[i]) {
|
|
||||||
tabs.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.table = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
element[0].className = 'table table-bordered table-striped code-table';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.tabPane = function() {
|
|
||||||
return {
|
|
||||||
require: '^tabbable',
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs, tabsCtrl) {
|
|
||||||
element.bind('$remove', tabsCtrl.addPane(element, attrs));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('bootstrap', []).directive(directive);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,219 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngCookies
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngCookies
|
|
||||||
*
|
|
||||||
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
|
|
||||||
*
|
|
||||||
* {@installModule cookies}
|
|
||||||
*
|
|
||||||
* <div doc-module-components="ngCookies"></div>
|
|
||||||
*
|
|
||||||
* See {@link ngCookies.$cookies `$cookies`} and
|
|
||||||
* {@link ngCookies.$cookieStore `$cookieStore`} for usage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('ngCookies', ['ng']).
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngCookies.$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.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example>
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function ExampleController($cookies) {
|
|
||||||
// Retrieving a cookie
|
|
||||||
var favoriteCookie = $cookies.myFavorite;
|
|
||||||
// Setting a cookie
|
|
||||||
$cookies.myFavorite = 'oatmeal';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</doc:source>
|
|
||||||
</doc: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 ngCookies.$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.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*/
|
|
||||||
factory('$cookieStore', ['$cookies', function($cookies) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngCookies.$cookieStore#get
|
|
||||||
* @methodOf ngCookies.$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) {
|
|
||||||
var value = $cookies[key];
|
|
||||||
return value ? angular.fromJson(value) : value;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngCookies.$cookieStore#put
|
|
||||||
* @methodOf ngCookies.$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 ngCookies.$cookieStore#remove
|
|
||||||
* @methodOf ngCookies.$cookieStore
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Remove given cookie
|
|
||||||
*
|
|
||||||
* @param {string} key Id of the key-value pair to delete.
|
|
||||||
*/
|
|
||||||
remove: function(key) {
|
|
||||||
delete $cookies[key];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}]);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,427 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function() {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* This object provides a utility for producing rich Error messages within
|
|
||||||
* Angular. It can be called as follows:
|
|
||||||
*
|
|
||||||
* var exampleMinErr = minErr('example');
|
|
||||||
* throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
|
|
||||||
*
|
|
||||||
* The above creates an instance of minErr in the example namespace. The
|
|
||||||
* resulting error will have a namespaced error code of example.one. The
|
|
||||||
* resulting error will replace {0} with the value of foo, and {1} with the
|
|
||||||
* value of bar. The object is not restricted in the number of arguments it can
|
|
||||||
* take.
|
|
||||||
*
|
|
||||||
* If fewer arguments are specified than necessary for interpolation, the extra
|
|
||||||
* interpolation markers will be preserved in the final string.
|
|
||||||
*
|
|
||||||
* Since data will be parsed statically during a build step, some restrictions
|
|
||||||
* are applied with respect to how minErr instances are created and called.
|
|
||||||
* Instances should have names of the form namespaceMinErr for a minErr created
|
|
||||||
* using minErr('namespace') . Error codes, namespaces and template strings
|
|
||||||
* should all be static strings, not variables or general expressions.
|
|
||||||
*
|
|
||||||
* @param {string} module The namespace to use for the new minErr instance.
|
|
||||||
* @returns {function(string, string, ...): Error} instance
|
|
||||||
*/
|
|
||||||
|
|
||||||
function minErr(module) {
|
|
||||||
return function () {
|
|
||||||
var code = arguments[0],
|
|
||||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
|
||||||
template = arguments[1],
|
|
||||||
templateArgs = arguments,
|
|
||||||
stringify = function (obj) {
|
|
||||||
if (typeof obj === 'function') {
|
|
||||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
|
||||||
} else if (typeof obj === 'undefined') {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof obj !== 'string') {
|
|
||||||
return JSON.stringify(obj);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
message, i;
|
|
||||||
|
|
||||||
message = prefix + template.replace(/\{\d+\}/g, function (match) {
|
|
||||||
var index = +match.slice(1, -1), arg;
|
|
||||||
|
|
||||||
if (index + 2 < templateArgs.length) {
|
|
||||||
arg = templateArgs[index + 2];
|
|
||||||
if (typeof arg === 'function') {
|
|
||||||
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
|
|
||||||
} else if (typeof arg === 'undefined') {
|
|
||||||
return 'undefined';
|
|
||||||
} else if (typeof arg !== 'string') {
|
|
||||||
return toJson(arg);
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
|
|
||||||
message = message + '\nhttp://errors.angularjs.org/1.2.13/' +
|
|
||||||
(module ? module + '/' : '') + code;
|
|
||||||
for (i = 2; i < arguments.length; i++) {
|
|
||||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
|
|
||||||
encodeURIComponent(stringify(arguments[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Error(message);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc interface
|
|
||||||
* @name angular.Module
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* Interface for configuring angular {@link angular.module modules}.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function setupModuleLoader(window) {
|
|
||||||
|
|
||||||
var $injectorMinErr = minErr('$injector');
|
|
||||||
var ngMinErr = minErr('ng');
|
|
||||||
|
|
||||||
function ensure(obj, name, factory) {
|
|
||||||
return obj[name] || (obj[name] = factory());
|
|
||||||
}
|
|
||||||
|
|
||||||
var angular = ensure(window, 'angular', Object);
|
|
||||||
|
|
||||||
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
|
|
||||||
angular.$$minErr = angular.$$minErr || minErr;
|
|
||||||
|
|
||||||
return ensure(angular, 'module', function() {
|
|
||||||
/** @type {Object.<string, angular.Module>} */
|
|
||||||
var modules = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc function
|
|
||||||
* @name angular.module
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* The `angular.module` is a global place for creating, registering and retrieving Angular
|
|
||||||
* modules.
|
|
||||||
* All modules (angular core or 3rd party) that should be available to an application must be
|
|
||||||
* registered using this mechanism.
|
|
||||||
*
|
|
||||||
* When passed two or more arguments, a new module is created. If passed only one argument, an
|
|
||||||
* existing module (the name passed as the first argument to `module`) is retrieved.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* # Module
|
|
||||||
*
|
|
||||||
* A module is a collection of services, directives, filters, and configuration information.
|
|
||||||
* `angular.module` is used to configure the {@link AUTO.$injector $injector}.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* // Create a new module
|
|
||||||
* var myModule = angular.module('myModule', []);
|
|
||||||
*
|
|
||||||
* // register a new service
|
|
||||||
* myModule.value('appName', 'MyCoolApp');
|
|
||||||
*
|
|
||||||
* // configure existing services inside initialization blocks.
|
|
||||||
* myModule.config(function($locationProvider) {
|
|
||||||
* // Configure existing providers
|
|
||||||
* $locationProvider.hashPrefix('!');
|
|
||||||
* });
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* Then you can create an injector and load your modules like this:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* var injector = angular.injector(['ng', 'MyModule'])
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* However it's more likely that you'll just use
|
|
||||||
* {@link ng.directive:ngApp ngApp} or
|
|
||||||
* {@link angular.bootstrap} to simplify this process for you.
|
|
||||||
*
|
|
||||||
* @param {!string} name The name of the module to create or retrieve.
|
|
||||||
* @param {Array.<string>=} requires If specified then new module is being created. If
|
|
||||||
* unspecified then the the module is being retrieved for further configuration.
|
|
||||||
* @param {Function} configFn Optional configuration function for the module. Same as
|
|
||||||
* {@link angular.Module#methods_config Module#config()}.
|
|
||||||
* @returns {module} new module with the {@link angular.Module} api.
|
|
||||||
*/
|
|
||||||
return function module(name, requires, configFn) {
|
|
||||||
var assertNotHasOwnProperty = function(name, context) {
|
|
||||||
if (name === 'hasOwnProperty') {
|
|
||||||
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
assertNotHasOwnProperty(name, 'module');
|
|
||||||
if (requires && modules.hasOwnProperty(name)) {
|
|
||||||
modules[name] = null;
|
|
||||||
}
|
|
||||||
return ensure(modules, name, function() {
|
|
||||||
if (!requires) {
|
|
||||||
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
|
|
||||||
"the module name or forgot to load it. If registering a module ensure that you " +
|
|
||||||
"specify the dependencies as the second argument.", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {!Array.<Array.<*>>} */
|
|
||||||
var invokeQueue = [];
|
|
||||||
|
|
||||||
/** @type {!Array.<Function>} */
|
|
||||||
var runBlocks = [];
|
|
||||||
|
|
||||||
var config = invokeLater('$injector', 'invoke');
|
|
||||||
|
|
||||||
/** @type {angular.Module} */
|
|
||||||
var moduleInstance = {
|
|
||||||
// Private state
|
|
||||||
_invokeQueue: invokeQueue,
|
|
||||||
_runBlocks: runBlocks,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc property
|
|
||||||
* @name angular.Module#requires
|
|
||||||
* @propertyOf angular.Module
|
|
||||||
* @returns {Array.<string>} List of module names which must be loaded before this module.
|
|
||||||
* @description
|
|
||||||
* Holds the list of modules which the injector will load before the current module is
|
|
||||||
* loaded.
|
|
||||||
*/
|
|
||||||
requires: requires,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc property
|
|
||||||
* @name angular.Module#name
|
|
||||||
* @propertyOf angular.Module
|
|
||||||
* @returns {string} Name of the module.
|
|
||||||
* @description
|
|
||||||
*/
|
|
||||||
name: name,
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#provider
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} providerType Construction function for creating new instance of the
|
|
||||||
* service.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#provider $provide.provider()}.
|
|
||||||
*/
|
|
||||||
provider: invokeLater('$provide', 'provider'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#factory
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} providerFunction Function for creating new instance of the service.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#factory $provide.factory()}.
|
|
||||||
*/
|
|
||||||
factory: invokeLater('$provide', 'factory'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#service
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {Function} constructor A constructor function that will be instantiated.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#service $provide.service()}.
|
|
||||||
*/
|
|
||||||
service: invokeLater('$provide', 'service'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#value
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name service name
|
|
||||||
* @param {*} object Service instance object.
|
|
||||||
* @description
|
|
||||||
* See {@link AUTO.$provide#value $provide.value()}.
|
|
||||||
*/
|
|
||||||
value: invokeLater('$provide', 'value'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#constant
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name constant name
|
|
||||||
* @param {*} object Constant value.
|
|
||||||
* @description
|
|
||||||
* Because the constant are fixed, they get applied before other provide methods.
|
|
||||||
* See {@link AUTO.$provide#constant $provide.constant()}.
|
|
||||||
*/
|
|
||||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#animation
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name animation name
|
|
||||||
* @param {Function} animationFactory Factory function for creating new instance of an
|
|
||||||
* animation.
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Defines an animation hook that can be later used with
|
|
||||||
* {@link ngAnimate.$animate $animate} service and directives that use this service.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* module.animation('.animation-name', function($inject1, $inject2) {
|
|
||||||
* return {
|
|
||||||
* eventName : function(element, done) {
|
|
||||||
* //code to run the animation
|
|
||||||
* //once complete, then run done()
|
|
||||||
* return function cancellationFunction(element) {
|
|
||||||
* //code to cancel the animation
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* })
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
|
|
||||||
* {@link ngAnimate ngAnimate module} for more information.
|
|
||||||
*/
|
|
||||||
animation: invokeLater('$animateProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#filter
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string} name Filter name.
|
|
||||||
* @param {Function} filterFactory Factory function for creating new instance of filter.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$filterProvider#register $filterProvider.register()}.
|
|
||||||
*/
|
|
||||||
filter: invokeLater('$filterProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#controller
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string|Object} name Controller name, or an object map of controllers where the
|
|
||||||
* keys are the names and the values are the constructors.
|
|
||||||
* @param {Function} constructor Controller constructor function.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
|
|
||||||
*/
|
|
||||||
controller: invokeLater('$controllerProvider', 'register'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#directive
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {string|Object} name Directive name, or an object map of directives where the
|
|
||||||
* keys are the names and the values are the factories.
|
|
||||||
* @param {Function} directiveFactory Factory function for creating new instance of
|
|
||||||
* directives.
|
|
||||||
* @description
|
|
||||||
* See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}.
|
|
||||||
*/
|
|
||||||
directive: invokeLater('$compileProvider', 'directive'),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#config
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {Function} configFn Execute this function on module load. Useful for service
|
|
||||||
* configuration.
|
|
||||||
* @description
|
|
||||||
* Use this method to register work which needs to be performed on module loading.
|
|
||||||
*/
|
|
||||||
config: config,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name angular.Module#run
|
|
||||||
* @methodOf angular.Module
|
|
||||||
* @param {Function} initializationFn Execute this function after injector creation.
|
|
||||||
* Useful for application initialization.
|
|
||||||
* @description
|
|
||||||
* Use this method to register work which should be performed when the injector is done
|
|
||||||
* loading all modules.
|
|
||||||
*/
|
|
||||||
run: function(block) {
|
|
||||||
runBlocks.push(block);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (configFn) {
|
|
||||||
config(configFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return moduleInstance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} provider
|
|
||||||
* @param {string} method
|
|
||||||
* @param {String=} insertMethod
|
|
||||||
* @returns {angular.Module}
|
|
||||||
*/
|
|
||||||
function invokeLater(provider, method, insertMethod) {
|
|
||||||
return function() {
|
|
||||||
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
|
|
||||||
return moduleInstance;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setupModuleLoader(window);
|
|
||||||
})(window);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closure compiler type information
|
|
||||||
*
|
|
||||||
* @typedef { {
|
|
||||||
* requires: !Array.<string>,
|
|
||||||
* invokeQueue: !Array.<Array.<*>>,
|
|
||||||
*
|
|
||||||
* service: function(string, Function):angular.Module,
|
|
||||||
* factory: function(string, Function):angular.Module,
|
|
||||||
* value: function(string, *):angular.Module,
|
|
||||||
*
|
|
||||||
* filter: function(string, Function):angular.Module,
|
|
||||||
*
|
|
||||||
* init: function(Function):angular.Module
|
|
||||||
* } }
|
|
||||||
*/
|
|
||||||
angular.Module;
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,613 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 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
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngResource
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngResource.$resource
|
|
||||||
* @requires $http
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* A factory which creates a resource object that lets you interact with
|
|
||||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
|
||||||
*
|
|
||||||
* The returned resource object has action methods which provide high-level behaviors without
|
|
||||||
* the need to interact with the low level {@link ng.$http $http} service.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngResource `ngResource`} module to be installed.
|
|
||||||
*
|
|
||||||
* @param {string} url A parametrized URL template with parameters prefixed by `:` as in
|
|
||||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
|
||||||
* `http://example.com:8080/api`), it will be respected.
|
|
||||||
*
|
|
||||||
* If you are using a url with a suffix, just add the suffix, like this:
|
|
||||||
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
|
|
||||||
* or even `$resource('http://example.com/resource/:resource_id.:format')`
|
|
||||||
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
|
|
||||||
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
|
|
||||||
* can escape it with `/\.`.
|
|
||||||
*
|
|
||||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
|
||||||
* `actions` methods. 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).
|
|
||||||
*
|
|
||||||
* Each key value in the parameter object is first bound to url template if present and then any
|
|
||||||
* excess keys are appended to the url search query after the `?`.
|
|
||||||
*
|
|
||||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
|
||||||
* URL `/path/greet?salutation=Hello`.
|
|
||||||
*
|
|
||||||
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
|
|
||||||
* the data object (useful for non-GET operations).
|
|
||||||
*
|
|
||||||
* @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#usage_parameters $http.config}:
|
|
||||||
*
|
|
||||||
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
|
|
||||||
* action2: {method:?, params:?, isArray:?, headers:?, ...},
|
|
||||||
* ...}
|
|
||||||
*
|
|
||||||
* 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)>}` –
|
|
||||||
* 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)>}` –
|
|
||||||
* 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
|
|
||||||
* GET request, otherwise if a cache instance built with
|
|
||||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
|
||||||
* caching.
|
|
||||||
* - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
|
|
||||||
* should abort the request when resolved.
|
|
||||||
* - **`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
|
|
||||||
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
|
|
||||||
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
|
|
||||||
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
|
|
||||||
* with `http response` object. See {@link ng.$http $http interceptors}.
|
|
||||||
*
|
|
||||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
|
||||||
* optionally extended with custom `actions`. The default set contains these actions:
|
|
||||||
*
|
|
||||||
* { 'get': {method:'GET'},
|
|
||||||
* 'save': {method:'POST'},
|
|
||||||
* 'query': {method:'GET', isArray:true},
|
|
||||||
* 'remove': {method:'DELETE'},
|
|
||||||
* 'delete': {method:'DELETE'} };
|
|
||||||
*
|
|
||||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
|
||||||
* destination and parameters. When the data is returned from the server then the object is an
|
|
||||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
|
||||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
|
||||||
* read, update, delete) on server-side data like this:
|
|
||||||
* <pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
var user = User.get({userId:123}, function() {
|
|
||||||
user.abc = true;
|
|
||||||
user.$save();
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* It is important to realize that invoking a $resource object method immediately returns an
|
|
||||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
|
||||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
|
||||||
* 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 cases 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:
|
|
||||||
*
|
|
||||||
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
|
|
||||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
|
|
||||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
|
||||||
*
|
|
||||||
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
|
|
||||||
* with (httpResponse) argument.
|
|
||||||
*
|
|
||||||
* Class actions return empty instance (with additional properties below).
|
|
||||||
* Instance actions return promise of the action.
|
|
||||||
*
|
|
||||||
* The Resource instances and collection have these additional properties:
|
|
||||||
*
|
|
||||||
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
|
|
||||||
* instance or collection.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* # Credit card resource
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
// Define CreditCard class
|
|
||||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
|
||||||
{userId:123, cardId:'@id'}, {
|
|
||||||
charge: {method:'POST', params:{charge:true}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// We can retrieve a collection from the server
|
|
||||||
var cards = CreditCard.query(function() {
|
|
||||||
// GET: /user/123/card
|
|
||||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
|
||||||
|
|
||||||
var card = cards[0];
|
|
||||||
// each item is an instance of CreditCard
|
|
||||||
expect(card instanceof CreditCard).toEqual(true);
|
|
||||||
card.name = "J. Smith";
|
|
||||||
// non GET methods are mapped onto the instances
|
|
||||||
card.$save();
|
|
||||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
|
||||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
|
||||||
|
|
||||||
// our custom method is mapped as well.
|
|
||||||
card.$charge({amount:9.99});
|
|
||||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
|
||||||
});
|
|
||||||
|
|
||||||
// we can create an instance as well
|
|
||||||
var newCard = new CreditCard({number:'0123'});
|
|
||||||
newCard.name = "Mike Smith";
|
|
||||||
newCard.$save();
|
|
||||||
// POST: /user/123/card {number:'0123', 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`.
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
var user = User.get({userId:123}, function() {
|
|
||||||
user.abc = true;
|
|
||||||
user.$save();
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* It's worth noting that the success callback for `get`, `query` and other methods 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:
|
|
||||||
*
|
|
||||||
<pre>
|
|
||||||
var User = $resource('/user/:userId', {userId:'@id'});
|
|
||||||
User.get({userId:123}, function(u, getResponseHeaders){
|
|
||||||
u.abc = true;
|
|
||||||
u.$save(function(u, putResponseHeaders) {
|
|
||||||
//u => saved user object
|
|
||||||
//putResponseHeaders => $http header getter
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
* # 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', '$q', function($http, $q) {
|
|
||||||
|
|
||||||
var DEFAULT_ACTIONS = {
|
|
||||||
'get': {method:'GET'},
|
|
||||||
'save': {method:'POST'},
|
|
||||||
'query': {method:'GET', isArray:true},
|
|
||||||
'remove': {method:'DELETE'},
|
|
||||||
'delete': {method:'DELETE'}
|
|
||||||
};
|
|
||||||
var noop = angular.noop,
|
|
||||||
forEach = angular.forEach,
|
|
||||||
extend = angular.extend,
|
|
||||||
copy = angular.copy,
|
|
||||||
isFunction = angular.isFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
|
|
||||||
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
|
|
||||||
* segments:
|
|
||||||
* segment = *pchar
|
|
||||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
|
||||||
* pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
|
||||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
||||||
* / "*" / "+" / "," / ";" / "="
|
|
||||||
*/
|
|
||||||
function encodeUriSegment(val) {
|
|
||||||
return encodeUriQuery(val, true).
|
|
||||||
replace(/%26/gi, '&').
|
|
||||||
replace(/%3D/gi, '=').
|
|
||||||
replace(/%2B/gi, '+');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 / "-" / "." / "_" / "~"
|
|
||||||
* pct-encoded = "%" HEXDIG HEXDIG
|
|
||||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
|
||||||
* / "*" / "+" / "," / ";" / "="
|
|
||||||
*/
|
|
||||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
|
||||||
return encodeURIComponent(val).
|
|
||||||
replace(/%40/gi, '@').
|
|
||||||
replace(/%3A/gi, ':').
|
|
||||||
replace(/%24/g, '$').
|
|
||||||
replace(/%2C/gi, ',').
|
|
||||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function Route(template, defaults) {
|
|
||||||
this.template = template;
|
|
||||||
this.defaults = defaults || {};
|
|
||||||
this.urlParams = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Route.prototype = {
|
|
||||||
setUrlParams: function(config, params, actionUrl) {
|
|
||||||
var self = this,
|
|
||||||
url = actionUrl || self.template,
|
|
||||||
val,
|
|
||||||
encodedVal;
|
|
||||||
|
|
||||||
var urlParams = self.urlParams = {};
|
|
||||||
forEach(url.split(/\W/), function(param){
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
url = url.replace(/\\:/g, ':');
|
|
||||||
|
|
||||||
params = params || {};
|
|
||||||
forEach(self.urlParams, function(_, urlParam){
|
|
||||||
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"), function(match, p1) {
|
|
||||||
return encodedVal + p1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
|
|
||||||
leadingSlashes, tail) {
|
|
||||||
if (tail.charAt(0) == '/') {
|
|
||||||
return tail;
|
|
||||||
} else {
|
|
||||||
return leadingSlashes + tail;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// strip trailing slashes and set the url
|
|
||||||
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+($|\?))/, '.');
|
|
||||||
// replace escaped `/\.` with `/.`
|
|
||||||
config.url = url.replace(/\/\\\./, '/.');
|
|
||||||
|
|
||||||
|
|
||||||
// set params - delegate param encoding to $http
|
|
||||||
forEach(params, function(value, key){
|
|
||||||
if (!self.urlParams[key]) {
|
|
||||||
config.params = config.params || {};
|
|
||||||
config.params[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function resourceFactory(url, paramDefaults, actions) {
|
|
||||||
var route = new Route(url);
|
|
||||||
|
|
||||||
actions = extend({}, DEFAULT_ACTIONS, actions);
|
|
||||||
|
|
||||||
function extractParams(data, actionParams){
|
|
||||||
var ids = {};
|
|
||||||
actionParams = extend({}, paramDefaults, actionParams);
|
|
||||||
forEach(actionParams, function(value, key){
|
|
||||||
if (isFunction(value)) { value = value(); }
|
|
||||||
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
|
|
||||||
lookupDottedPath(data, value.substr(1)) : value;
|
|
||||||
});
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultResponseInterceptor(response) {
|
|
||||||
return response.resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Resource(value){
|
|
||||||
shallowClearAndCopy(value || {}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(actions, function(action, name) {
|
|
||||||
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
|
|
||||||
|
|
||||||
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;
|
|
||||||
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 (hasBody) data = a1;
|
|
||||||
else params = a1;
|
|
||||||
break;
|
|
||||||
case 0: break;
|
|
||||||
default:
|
|
||||||
throw $resourceMinErr('badargs',
|
|
||||||
"Expected up to 4 arguments [params, data, success, error], got {0} arguments",
|
|
||||||
arguments.length);
|
|
||||||
}
|
|
||||||
/* jshint +W086 */ /* (purposefully fall through case statements) */
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
forEach(action, function(value, key) {
|
|
||||||
if (key != 'params' && key != 'isArray' && key != 'interceptor') {
|
|
||||||
httpConfig[key] = copy(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// 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 {
|
|
||||||
shallowClearAndCopy(data, value);
|
|
||||||
value.$promise = promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value.$resolved = true;
|
|
||||||
|
|
||||||
response.resource = value;
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
value.$resolved = true;
|
|
||||||
|
|
||||||
(error||noop)(response);
|
|
||||||
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
promise = promise.then(
|
|
||||||
function(response) {
|
|
||||||
var value = responseInterceptor(response);
|
|
||||||
(success||noop)(value, response.headers);
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
responseErrorInterceptor);
|
|
||||||
|
|
||||||
if (!isInstanceCall) {
|
|
||||||
// we are creating instance / collection
|
|
||||||
// - set the initial promise
|
|
||||||
// - return the instance / collection
|
|
||||||
value.$promise = promise;
|
|
||||||
value.$resolved = false;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// instance call
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Resource.prototype['$' + name] = function(params, success, error) {
|
|
||||||
if (isFunction(params)) {
|
|
||||||
error = success; success = params; params = {};
|
|
||||||
}
|
|
||||||
var result = Resource[name].call(this, params, this, success, error);
|
|
||||||
return result.$promise || result;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
Resource.bind = function(additionalParamDefaults){
|
|
||||||
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
return Resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceFactory;
|
|
||||||
}]);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,938 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngRoute
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngRoute
|
|
||||||
*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$routeProvider
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* 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 = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$routeProvider#when
|
|
||||||
* @methodOf ngRoute.$routeProvider
|
|
||||||
*
|
|
||||||
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
|
|
||||||
* contains redundant 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 exactly match the
|
|
||||||
* route definition.
|
|
||||||
*
|
|
||||||
* * `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:
|
|
||||||
* 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: e.g.`:name?`.
|
|
||||||
*
|
|
||||||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
|
|
||||||
* `/color/brown/largecode/code/with/slashs/edit` and extract:
|
|
||||||
*
|
|
||||||
* * `color: brown`
|
|
||||||
* * `largecode: code/with/slashs`.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param {Object} route Mapping information to be assigned to `$route.current` on route
|
|
||||||
* match.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* - `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
|
|
||||||
* returns an html template as a string which should be used by {@link
|
|
||||||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
|
|
||||||
* This property takes precedence over `templateUrl`.
|
|
||||||
*
|
|
||||||
* If `template` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
|
||||||
* `$location.path()` by applying the current route
|
|
||||||
*
|
|
||||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
|
|
||||||
* template that should be used by {@link ngRoute.directive:ngView ngView}.
|
|
||||||
*
|
|
||||||
* If `templateUrl` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
|
||||||
* `$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, 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.
|
|
||||||
*
|
|
||||||
* - `redirectTo` – {(string|function())=} – value to update
|
|
||||||
* {@link ng.$location $location} path with and trigger route redirection.
|
|
||||||
*
|
|
||||||
* If `redirectTo` is a function, it will be called with the following parameters:
|
|
||||||
*
|
|
||||||
* - `{Object.<string>}` - route parameters extracted from the current
|
|
||||||
* `$location.path()` by applying the current route templateUrl.
|
|
||||||
* - `{string}` - current `$location.path()`
|
|
||||||
* - `{Object}` - current `$location.search()`
|
|
||||||
*
|
|
||||||
* The custom `redirectTo` function is expected to return a string which will be used
|
|
||||||
* to update `$location.path()` and `$location.search()`.
|
|
||||||
*
|
|
||||||
* - `[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.
|
|
||||||
*
|
|
||||||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
|
|
||||||
*
|
|
||||||
* If the option is set to `true`, then the particular route can be matched without being
|
|
||||||
* case sensitive
|
|
||||||
*
|
|
||||||
* @returns {Object} self
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Adds a new route definition to the `$route` service.
|
|
||||||
*/
|
|
||||||
this.when = function(path, route) {
|
|
||||||
routes[path] = angular.extend(
|
|
||||||
{reloadOnSearch: true},
|
|
||||||
route,
|
|
||||||
path && pathRegExp(path, route)
|
|
||||||
);
|
|
||||||
|
|
||||||
// create redirection for trailing slashes
|
|
||||||
if (path) {
|
|
||||||
var redirectPath = (path[path.length-1] == '/')
|
|
||||||
? path.substr(0, path.length-1)
|
|
||||||
: path +'/';
|
|
||||||
|
|
||||||
routes[redirectPath] = angular.extend(
|
|
||||||
{redirectTo: path},
|
|
||||||
pathRegExp(redirectPath, route)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param path {string} path
|
|
||||||
* @param opts {Object} options
|
|
||||||
* @return {?Object}
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Normalizes the given path, returning a regular expression
|
|
||||||
* and the original path.
|
|
||||||
*
|
|
||||||
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
|
|
||||||
*/
|
|
||||||
function pathRegExp(path, opts) {
|
|
||||||
var insensitive = opts.caseInsensitiveMatch,
|
|
||||||
ret = {
|
|
||||||
originalPath: path,
|
|
||||||
regexp: path
|
|
||||||
},
|
|
||||||
keys = ret.keys = [];
|
|
||||||
|
|
||||||
path = path
|
|
||||||
.replace(/([().])/g, '\\$1')
|
|
||||||
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){
|
|
||||||
var optional = option === '?' ? option : null;
|
|
||||||
var star = option === '*' ? option : null;
|
|
||||||
keys.push({ name: key, optional: !!optional });
|
|
||||||
slash = slash || '';
|
|
||||||
return ''
|
|
||||||
+ (optional ? '' : slash)
|
|
||||||
+ '(?:'
|
|
||||||
+ (optional ? slash : '')
|
|
||||||
+ (star && '(.+?)' || '([^/]+)')
|
|
||||||
+ (optional || '')
|
|
||||||
+ ')'
|
|
||||||
+ (optional || '');
|
|
||||||
})
|
|
||||||
.replace(/([\/$\*])/g, '\\$1');
|
|
||||||
|
|
||||||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$routeProvider#otherwise
|
|
||||||
* @methodOf ngRoute.$routeProvider
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Sets route definition that will be used on route change when no other route definition
|
|
||||||
* is matched.
|
|
||||||
*
|
|
||||||
* @param {Object} params Mapping information to be assigned to `$route.current`.
|
|
||||||
* @returns {Object} self
|
|
||||||
*/
|
|
||||||
this.otherwise = function(params) {
|
|
||||||
this.when(null, params);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
this.$get = ['$rootScope',
|
|
||||||
'$location',
|
|
||||||
'$routeParams',
|
|
||||||
'$q',
|
|
||||||
'$injector',
|
|
||||||
'$http',
|
|
||||||
'$templateCache',
|
|
||||||
'$sce',
|
|
||||||
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$route
|
|
||||||
* @requires $location
|
|
||||||
* @requires $routeParams
|
|
||||||
*
|
|
||||||
* @property {Object} current Reference to the current route definition.
|
|
||||||
* The route definition contains:
|
|
||||||
*
|
|
||||||
* - `controller`: The controller constructor as define in route definition.
|
|
||||||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
|
|
||||||
* controller instantiation. The `locals` contain
|
|
||||||
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
|
|
||||||
*
|
|
||||||
* - `$scope` - The current route scope.
|
|
||||||
* - `$template` - The current route template HTML.
|
|
||||||
*
|
|
||||||
* @property {Array.<Object>} routes Array of all configured routes.
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials).
|
|
||||||
* It watches `$location.url()` and tries to map the path to an existing route definition.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
This example shows how changing the URL hash causes the `$route` to match a route against the
|
|
||||||
URL, and the `ngView` pulls in the partial.
|
|
||||||
|
|
||||||
Note that this example is using {@link ng.directive:script inlined templates}
|
|
||||||
to get it working on jsfiddle as well.
|
|
||||||
|
|
||||||
<example module="ngViewExample" deps="angular-route.js">
|
|
||||||
<file name="index.html">
|
|
||||||
<div ng-controller="MainCntl">
|
|
||||||
Choose:
|
|
||||||
<a href="Book/Moby">Moby</a> |
|
|
||||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
|
||||||
<a href="Book/Gatsby">Gatsby</a> |
|
|
||||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
|
||||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
|
||||||
|
|
||||||
<div ng-view></div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre>$location.path() = {{$location.path()}}</pre>
|
|
||||||
<pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
|
|
||||||
<pre>$route.current.params = {{$route.current.params}}</pre>
|
|
||||||
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
|
|
||||||
<pre>$routeParams = {{$routeParams}}</pre>
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="book.html">
|
|
||||||
controller: {{name}}<br />
|
|
||||||
Book Id: {{params.bookId}}<br />
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="chapter.html">
|
|
||||||
controller: {{name}}<br />
|
|
||||||
Book Id: {{params.bookId}}<br />
|
|
||||||
Chapter Id: {{params.chapterId}}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="script.js">
|
|
||||||
angular.module('ngViewExample', ['ngRoute'])
|
|
||||||
|
|
||||||
.config(function($routeProvider, $locationProvider) {
|
|
||||||
$routeProvider.when('/Book/:bookId', {
|
|
||||||
templateUrl: 'book.html',
|
|
||||||
controller: BookCntl,
|
|
||||||
resolve: {
|
|
||||||
// I will cause a 1 second delay
|
|
||||||
delay: function($q, $timeout) {
|
|
||||||
var delay = $q.defer();
|
|
||||||
$timeout(delay.resolve, 1000);
|
|
||||||
return delay.promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
|
|
||||||
templateUrl: 'chapter.html',
|
|
||||||
controller: ChapterCntl
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure html5 to get links working on jsfiddle
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
function MainCntl($scope, $route, $routeParams, $location) {
|
|
||||||
$scope.$route = $route;
|
|
||||||
$scope.$location = $location;
|
|
||||||
$scope.$routeParams = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookCntl($scope, $routeParams) {
|
|
||||||
$scope.name = "BookCntl";
|
|
||||||
$scope.params = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChapterCntl($scope, $routeParams) {
|
|
||||||
$scope.name = "ChapterCntl";
|
|
||||||
$scope.params = $routeParams;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="protractorTest.js">
|
|
||||||
it('should load and compile correct template', function() {
|
|
||||||
element(by.linkText('Moby: Ch1')).click();
|
|
||||||
var content = element(by.css('.doc-example-live [ng-view]')).getText();
|
|
||||||
expect(content).toMatch(/controller\: ChapterCntl/);
|
|
||||||
expect(content).toMatch(/Book Id\: Moby/);
|
|
||||||
expect(content).toMatch(/Chapter Id\: 1/);
|
|
||||||
|
|
||||||
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/);
|
|
||||||
});
|
|
||||||
</file>
|
|
||||||
</example>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeStart
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @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.
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeSuccess
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @description
|
|
||||||
* Broadcasted after a route dependencies are resolved.
|
|
||||||
* {@link ngRoute.directive:ngView ngView} listens for the directive
|
|
||||||
* to instantiate the controller and render the view.
|
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeChangeError
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.$route#$routeUpdate
|
|
||||||
* @eventOf ngRoute.$route
|
|
||||||
* @eventType broadcast on root scope
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* The `reloadOnSearch` property has been set to false, and we are reusing the same
|
|
||||||
* instance of the Controller.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var forceReload = false,
|
|
||||||
$route = {
|
|
||||||
routes: routes,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc method
|
|
||||||
* @name ngRoute.$route#reload
|
|
||||||
* @methodOf ngRoute.$route
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Causes `$route` service to reload the current route even if
|
|
||||||
* {@link ng.$location $location} hasn't changed.
|
|
||||||
*
|
|
||||||
* As a result of that, {@link ngRoute.directive:ngView ngView}
|
|
||||||
* creates new scope, reinstantiates the controller.
|
|
||||||
*/
|
|
||||||
reload: function() {
|
|
||||||
forceReload = true;
|
|
||||||
$rootScope.$evalAsync(updateRoute);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.$on('$locationChangeSuccess', updateRoute);
|
|
||||||
|
|
||||||
return $route;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param on {string} current url
|
|
||||||
* @param route {Object} route regexp to match the url against
|
|
||||||
* @return {?Object}
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Check if the route matches the current url.
|
|
||||||
*
|
|
||||||
* Inspired by match in
|
|
||||||
* visionmedia/express/lib/router/router.js.
|
|
||||||
*/
|
|
||||||
function switchRouteMatcher(on, route) {
|
|
||||||
var keys = route.keys,
|
|
||||||
params = {};
|
|
||||||
|
|
||||||
if (!route.regexp) return null;
|
|
||||||
|
|
||||||
var m = route.regexp.exec(on);
|
|
||||||
if (!m) return null;
|
|
||||||
|
|
||||||
for (var i = 1, len = m.length; i < len; ++i) {
|
|
||||||
var key = keys[i - 1];
|
|
||||||
|
|
||||||
var val = 'string' == typeof m[i]
|
|
||||||
? decodeURIComponent(m[i])
|
|
||||||
: m[i];
|
|
||||||
|
|
||||||
if (key && val) {
|
|
||||||
params[key.name] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRoute() {
|
|
||||||
var next = parseRoute(),
|
|
||||||
last = $route.current;
|
|
||||||
|
|
||||||
if (next && last && next.$$route === last.$$route
|
|
||||||
&& angular.equals(next.pathParams, last.pathParams)
|
|
||||||
&& !next.reloadOnSearch && !forceReload) {
|
|
||||||
last.params = next.params;
|
|
||||||
angular.copy(last.params, $routeParams);
|
|
||||||
$rootScope.$broadcast('$routeUpdate', last);
|
|
||||||
} else if (next || last) {
|
|
||||||
forceReload = false;
|
|
||||||
$rootScope.$broadcast('$routeChangeStart', next, last);
|
|
||||||
$route.current = next;
|
|
||||||
if (next) {
|
|
||||||
if (next.redirectTo) {
|
|
||||||
if (angular.isString(next.redirectTo)) {
|
|
||||||
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
|
|
||||||
.replace();
|
|
||||||
} else {
|
|
||||||
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
|
|
||||||
.replace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$q.when(next).
|
|
||||||
then(function() {
|
|
||||||
if (next) {
|
|
||||||
var locals = angular.extend({}, next.resolve),
|
|
||||||
template, templateUrl;
|
|
||||||
|
|
||||||
angular.forEach(locals, function(value, key) {
|
|
||||||
locals[key] = angular.isString(value) ?
|
|
||||||
$injector.get(value) : $injector.invoke(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (angular.isDefined(template = next.template)) {
|
|
||||||
if (angular.isFunction(template)) {
|
|
||||||
template = template(next.params);
|
|
||||||
}
|
|
||||||
} else if (angular.isDefined(templateUrl = next.templateUrl)) {
|
|
||||||
if (angular.isFunction(templateUrl)) {
|
|
||||||
templateUrl = templateUrl(next.params);
|
|
||||||
}
|
|
||||||
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
|
|
||||||
if (angular.isDefined(templateUrl)) {
|
|
||||||
next.loadedTemplateUrl = templateUrl;
|
|
||||||
template = $http.get(templateUrl, {cache: $templateCache}).
|
|
||||||
then(function(response) { return response.data; });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (angular.isDefined(template)) {
|
|
||||||
locals['$template'] = template;
|
|
||||||
}
|
|
||||||
return $q.all(locals);
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
// after route change
|
|
||||||
then(function(locals) {
|
|
||||||
if (next == $route.current) {
|
|
||||||
if (next) {
|
|
||||||
next.locals = locals;
|
|
||||||
angular.copy(next.params, $routeParams);
|
|
||||||
}
|
|
||||||
$rootScope.$broadcast('$routeChangeSuccess', next, last);
|
|
||||||
}
|
|
||||||
}, function(error) {
|
|
||||||
if (next == $route.current) {
|
|
||||||
$rootScope.$broadcast('$routeChangeError', next, last, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns the current active route, by matching it against the URL
|
|
||||||
*/
|
|
||||||
function parseRoute() {
|
|
||||||
// Match a route
|
|
||||||
var params, match;
|
|
||||||
angular.forEach(routes, function(route, path) {
|
|
||||||
if (!match && (params = switchRouteMatcher($location.path(), route))) {
|
|
||||||
match = inherit(route, {
|
|
||||||
params: angular.extend({}, $location.search(), params),
|
|
||||||
pathParams: params});
|
|
||||||
match.$$route = route;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// No route matched; fallback to "otherwise" route
|
|
||||||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns interpolation of the redirect path with the parameters
|
|
||||||
*/
|
|
||||||
function interpolate(string, params) {
|
|
||||||
var result = [];
|
|
||||||
angular.forEach((string||'').split(':'), function(segment, i) {
|
|
||||||
if (i === 0) {
|
|
||||||
result.push(segment);
|
|
||||||
} else {
|
|
||||||
var segmentMatch = segment.match(/(\w+)(.*)/);
|
|
||||||
var key = segmentMatch[1];
|
|
||||||
result.push(params[key]);
|
|
||||||
result.push(segmentMatch[2] || '');
|
|
||||||
delete params[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result.join('');
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
ngRouteModule.provider('$routeParams', $RouteParamsProvider);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc object
|
|
||||||
* @name ngRoute.$routeParams
|
|
||||||
* @requires $route
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* The `$routeParams` service allows you to retrieve the current set of route parameters.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* The route parameters are a combination of {@link ng.$location `$location`}'s
|
|
||||||
* {@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.
|
|
||||||
*
|
|
||||||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged
|
|
||||||
* (but its properties will likely change) even when a route change occurs.
|
|
||||||
*
|
|
||||||
* Note that the `$routeParams` are only updated *after* a route change completes successfully.
|
|
||||||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions.
|
|
||||||
* Instead you can use `$route.current.params` to access the new route's parameters.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* <pre>
|
|
||||||
* // Given:
|
|
||||||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
|
|
||||||
* // Route: /Chapter/:chapterId/Section/:sectionId
|
|
||||||
* //
|
|
||||||
* // Then
|
|
||||||
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
function $RouteParamsProvider() {
|
|
||||||
this.$get = function() { return {}; };
|
|
||||||
}
|
|
||||||
|
|
||||||
ngRouteModule.directive('ngView', ngViewFactory);
|
|
||||||
ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc directive
|
|
||||||
* @name ngRoute.directive:ngView
|
|
||||||
* @restrict ECA
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* # Overview
|
|
||||||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
|
|
||||||
* including the rendered template of the current route into the main layout (`index.html`) file.
|
|
||||||
* Every time the current route changes, the included view changes with it according to the
|
|
||||||
* configuration of the `$route` service.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
|
||||||
*
|
|
||||||
* @animations
|
|
||||||
* enter - animation is used to bring new content into the browser.
|
|
||||||
* leave - animation is used to animate existing content away.
|
|
||||||
*
|
|
||||||
* 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">
|
|
||||||
<div ng-controller="MainCntl as main">
|
|
||||||
Choose:
|
|
||||||
<a href="Book/Moby">Moby</a> |
|
|
||||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
|
||||||
<a href="Book/Gatsby">Gatsby</a> |
|
|
||||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
|
||||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
|
||||||
|
|
||||||
<div class="view-animate-container">
|
|
||||||
<div ng-view class="view-animate"></div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<pre>$location.path() = {{main.$location.path()}}</pre>
|
|
||||||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
|
|
||||||
<pre>$route.current.params = {{main.$route.current.params}}</pre>
|
|
||||||
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
|
|
||||||
<pre>$routeParams = {{main.$routeParams}}</pre>
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="book.html">
|
|
||||||
<div>
|
|
||||||
controller: {{book.name}}<br />
|
|
||||||
Book Id: {{book.params.bookId}}<br />
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="chapter.html">
|
|
||||||
<div>
|
|
||||||
controller: {{chapter.name}}<br />
|
|
||||||
Book Id: {{chapter.params.bookId}}<br />
|
|
||||||
Chapter Id: {{chapter.params.chapterId}}
|
|
||||||
</div>
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="animations.css">
|
|
||||||
.view-animate-container {
|
|
||||||
position:relative;
|
|
||||||
height:100px!important;
|
|
||||||
position:relative;
|
|
||||||
background:white;
|
|
||||||
border:1px solid black;
|
|
||||||
height:40px;
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate {
|
|
||||||
padding:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate.ng-enter, .view-animate.ng-leave {
|
|
||||||
-webkit-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;
|
|
||||||
width:100%;
|
|
||||||
border-left:1px solid black;
|
|
||||||
|
|
||||||
position:absolute;
|
|
||||||
top:0;
|
|
||||||
left:0;
|
|
||||||
right:0;
|
|
||||||
bottom:0;
|
|
||||||
padding:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-animate.ng-enter {
|
|
||||||
left:100%;
|
|
||||||
}
|
|
||||||
.view-animate.ng-enter.ng-enter-active {
|
|
||||||
left:0;
|
|
||||||
}
|
|
||||||
.view-animate.ng-leave.ng-leave-active {
|
|
||||||
left:-100%;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="script.js">
|
|
||||||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'],
|
|
||||||
function($routeProvider, $locationProvider) {
|
|
||||||
$routeProvider.when('/Book/:bookId', {
|
|
||||||
templateUrl: 'book.html',
|
|
||||||
controller: BookCntl,
|
|
||||||
controllerAs: 'book'
|
|
||||||
});
|
|
||||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', {
|
|
||||||
templateUrl: 'chapter.html',
|
|
||||||
controller: ChapterCntl,
|
|
||||||
controllerAs: 'chapter'
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure html5 to get links working on jsfiddle
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
function MainCntl($route, $routeParams, $location) {
|
|
||||||
this.$route = $route;
|
|
||||||
this.$location = $location;
|
|
||||||
this.$routeParams = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BookCntl($routeParams) {
|
|
||||||
this.name = "BookCntl";
|
|
||||||
this.params = $routeParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChapterCntl($routeParams) {
|
|
||||||
this.name = "ChapterCntl";
|
|
||||||
this.params = $routeParams;
|
|
||||||
}
|
|
||||||
</file>
|
|
||||||
|
|
||||||
<file name="protractorTest.js">
|
|
||||||
it('should load and compile correct template', function() {
|
|
||||||
element(by.linkText('Moby: Ch1')).click();
|
|
||||||
var content = element(by.css('.doc-example-live [ng-view]')).getText();
|
|
||||||
expect(content).toMatch(/controller\: ChapterCntl/);
|
|
||||||
expect(content).toMatch(/Book Id\: Moby/);
|
|
||||||
expect(content).toMatch(/Chapter Id\: 1/);
|
|
||||||
|
|
||||||
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/);
|
|
||||||
});
|
|
||||||
</file>
|
|
||||||
</example>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc event
|
|
||||||
* @name ngRoute.directive:ngView#$viewContentLoaded
|
|
||||||
* @eventOf ngRoute.directive:ngView
|
|
||||||
* @eventType emit on the current ngView scope
|
|
||||||
* @description
|
|
||||||
* Emitted every time the ngView content is reloaded.
|
|
||||||
*/
|
|
||||||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
|
|
||||||
function ngViewFactory( $route, $anchorScroll, $animate) {
|
|
||||||
return {
|
|
||||||
restrict: 'ECA',
|
|
||||||
terminal: true,
|
|
||||||
priority: 400,
|
|
||||||
transclude: 'element',
|
|
||||||
link: function(scope, $element, attr, ctrl, $transclude) {
|
|
||||||
var currentScope,
|
|
||||||
currentElement,
|
|
||||||
autoScrollExp = attr.autoscroll,
|
|
||||||
onloadExp = attr.onload || '';
|
|
||||||
|
|
||||||
scope.$on('$routeChangeSuccess', update);
|
|
||||||
update();
|
|
||||||
|
|
||||||
function cleanupLastView() {
|
|
||||||
if (currentScope) {
|
|
||||||
currentScope.$destroy();
|
|
||||||
currentScope = null;
|
|
||||||
}
|
|
||||||
if(currentElement) {
|
|
||||||
$animate.leave(currentElement);
|
|
||||||
currentElement = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var locals = $route.current && $route.current.locals,
|
|
||||||
template = locals && locals.$template;
|
|
||||||
|
|
||||||
if (angular.isDefined(template)) {
|
|
||||||
var newScope = scope.$new();
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
currentElement = clone;
|
|
||||||
currentScope = current.scope = newScope;
|
|
||||||
currentScope.$emit('$viewContentLoaded');
|
|
||||||
currentScope.$eval(onloadExp);
|
|
||||||
} else {
|
|
||||||
cleanupLastView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
$element.html(locals.$template);
|
|
||||||
|
|
||||||
var link = $compile($element.contents());
|
|
||||||
|
|
||||||
if (current.controller) {
|
|
||||||
locals.$scope = scope;
|
|
||||||
var controller = $controller(current.controller, locals);
|
|
||||||
if (current.controllerAs) {
|
|
||||||
scope[current.controllerAs] = controller;
|
|
||||||
}
|
|
||||||
$element.data('$ngControllerController', controller);
|
|
||||||
$element.children().data('$ngControllerController', controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
link(scope);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
|
@ -1,642 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.2.13
|
|
||||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {'use strict';
|
|
||||||
|
|
||||||
var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc overview
|
|
||||||
* @name ngSanitize
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* # ngSanitize
|
|
||||||
*
|
|
||||||
* The `ngSanitize` module provides functionality to sanitize HTML.
|
|
||||||
*
|
|
||||||
* {@installModule sanitize}
|
|
||||||
*
|
|
||||||
* <div doc-module-components="ngSanitize"></div>
|
|
||||||
*
|
|
||||||
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* HTML Parser By Misko Hevery (misko@hevery.com)
|
|
||||||
* based on: HTML Parser By John Resig (ejohn.org)
|
|
||||||
* Original code by Erik Arvidsson, Mozilla Public License
|
|
||||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
|
||||||
*
|
|
||||||
* // Use like so:
|
|
||||||
* htmlParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc service
|
|
||||||
* @name ngSanitize.$sanitize
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
|
||||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example module="ngSanitize">
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function Ctrl($scope, $sce) {
|
|
||||||
$scope.snippet =
|
|
||||||
'<p style="color:blue">an html\n' +
|
|
||||||
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
|
|
||||||
'snippet</p>';
|
|
||||||
$scope.deliberatelyTrustDangerousSnippet = function() {
|
|
||||||
return $sce.trustAsHtml($scope.snippet);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div ng-controller="Ctrl">
|
|
||||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Directive</td>
|
|
||||||
<td>How</td>
|
|
||||||
<td>Source</td>
|
|
||||||
<td>Rendered</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="bind-html-with-sanitize">
|
|
||||||
<td>ng-bind-html</td>
|
|
||||||
<td>Automatically uses $sanitize</td>
|
|
||||||
<td><pre><div ng-bind-html="snippet"><br/></div></pre></td>
|
|
||||||
<td><div ng-bind-html="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
<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()">
|
|
||||||
</div></pre>
|
|
||||||
</td>
|
|
||||||
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="bind-default">
|
|
||||||
<td>ng-bind</td>
|
|
||||||
<td>Automatically escapes</td>
|
|
||||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
|
||||||
<td><div ng-bind="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</doc:source>
|
|
||||||
<doc:protractor>
|
|
||||||
it('should sanitize the html snippet by default', function() {
|
|
||||||
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
|
|
||||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should inline raw snippet if bound to a trusted value', function() {
|
|
||||||
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
|
|
||||||
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(element(by.css('#bind-default div')).getInnerHtml()).
|
|
||||||
toBe("<p style=\"color:blue\">an html\n" +
|
|
||||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
|
||||||
"snippet</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update', function() {
|
|
||||||
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:protractor>
|
|
||||||
</doc:example>
|
|
||||||
*/
|
|
||||||
function $SanitizeProvider() {
|
|
||||||
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
|
|
||||||
return function(html) {
|
|
||||||
var 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*>/,
|
|
||||||
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,
|
|
||||||
// Match everything outside of normal chars and " (quote character)
|
|
||||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
|
|
||||||
|
|
||||||
|
|
||||||
// Good source of info about elements and attributes
|
|
||||||
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
|
||||||
// http://simon.html5.org/html-elements
|
|
||||||
|
|
||||||
// Safe Void Elements - HTML5
|
|
||||||
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
|
||||||
var voidElements = makeMap("area,br,col,hr,img,wbr");
|
|
||||||
|
|
||||||
// Elements that you can, intentionally, leave open (and which close themselves)
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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"));
|
|
||||||
|
|
||||||
// Inline Elements - HTML5
|
|
||||||
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
|
|
||||||
"bdi,bdo,big,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"));
|
|
||||||
|
|
||||||
|
|
||||||
// Special Elements (can contain anything)
|
|
||||||
var specialElements = makeMap("script,style");
|
|
||||||
|
|
||||||
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");
|
|
||||||
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,size,span,start,summary,target,title,type,'+
|
|
||||||
'valign,value,vspace,width'));
|
|
||||||
|
|
||||||
function makeMap(str) {
|
|
||||||
var obj = {}, items = str.split(','), i;
|
|
||||||
for (i = 0; i < items.length; i++) obj[items[i]] = true;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @example
|
|
||||||
* htmlParser(htmlString, {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {string} html string
|
|
||||||
* @param {object} handler
|
|
||||||
*/
|
|
||||||
function htmlParser( html, handler ) {
|
|
||||||
var index, chars, match, stack = [], last = html;
|
|
||||||
stack.last = function() { return stack[ stack.length - 1 ]; };
|
|
||||||
|
|
||||||
while ( html ) {
|
|
||||||
chars = true;
|
|
||||||
|
|
||||||
// Make sure we're not in a script or style element
|
|
||||||
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
|
||||||
|
|
||||||
// Comment
|
|
||||||
if ( html.indexOf("<!--") === 0 ) {
|
|
||||||
// comments containing -- are not allowed unless they terminate the comment
|
|
||||||
index = html.indexOf("--", 4);
|
|
||||||
|
|
||||||
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 );
|
|
||||||
|
|
||||||
if ( match ) {
|
|
||||||
html = html.substring( match[0].length );
|
|
||||||
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start tag
|
|
||||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
|
||||||
match = html.match( START_TAG_REGEXP );
|
|
||||||
|
|
||||||
if ( match ) {
|
|
||||||
html = html.substring( match[0].length );
|
|
||||||
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
|
||||||
chars = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( chars ) {
|
|
||||||
index = html.indexOf("<");
|
|
||||||
|
|
||||||
var text = index < 0 ? html : html.substring( 0, index );
|
|
||||||
html = index < 0 ? "" : html.substring( index );
|
|
||||||
|
|
||||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
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) );
|
|
||||||
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
|
|
||||||
parseEndTag( "", stack.last() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( html == last ) {
|
|
||||||
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
|
|
||||||
"of html: {0}", html);
|
|
||||||
}
|
|
||||||
last = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any remaining tags
|
|
||||||
parseEndTag();
|
|
||||||
|
|
||||||
function parseStartTag( tag, tagName, rest, unary ) {
|
|
||||||
tagName = angular.lowercase(tagName);
|
|
||||||
if ( blockElements[ tagName ] ) {
|
|
||||||
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
|
||||||
parseEndTag( "", stack.last() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
|
|
||||||
parseEndTag( "", tagName );
|
|
||||||
}
|
|
||||||
|
|
||||||
unary = voidElements[ tagName ] || !!unary;
|
|
||||||
|
|
||||||
if ( !unary )
|
|
||||||
stack.push( tagName );
|
|
||||||
|
|
||||||
var attrs = {};
|
|
||||||
|
|
||||||
rest.replace(ATTR_REGEXP,
|
|
||||||
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
|
|
||||||
var value = doubleQuotedValue
|
|
||||||
|| singleQuotedValue
|
|
||||||
|| unquotedValue
|
|
||||||
|| '';
|
|
||||||
|
|
||||||
attrs[name] = decodeEntities(value);
|
|
||||||
});
|
|
||||||
if (handler.start) handler.start( tagName, attrs, unary );
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEndTag( tag, tagName ) {
|
|
||||||
var pos = 0, i;
|
|
||||||
tagName = angular.lowercase(tagName);
|
|
||||||
if ( tagName )
|
|
||||||
// Find the closest opened tag of the same type
|
|
||||||
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
|
||||||
if ( stack[ pos ] == tagName )
|
|
||||||
break;
|
|
||||||
|
|
||||||
if ( pos >= 0 ) {
|
|
||||||
// Close all the open elements, up the stack
|
|
||||||
for ( i = stack.length - 1; i >= pos; i-- )
|
|
||||||
if (handler.end) handler.end( stack[ i ] );
|
|
||||||
|
|
||||||
// Remove the open elements from the stack
|
|
||||||
stack.length = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
function decodeEntities(value) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes all potentially dangerous characters, so that the
|
|
||||||
* resulting string can be safely inserted into attribute or
|
|
||||||
* element text.
|
|
||||||
* @param value
|
|
||||||
* @returns escaped text
|
|
||||||
*/
|
|
||||||
function encodeEntities(value) {
|
|
||||||
return value.
|
|
||||||
replace(/&/g, '&').
|
|
||||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
|
||||||
return '&#' + value.charCodeAt(0) + ';';
|
|
||||||
}).
|
|
||||||
replace(/</g, '<').
|
|
||||||
replace(/>/g, '>');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create an HTML/XML writer which writes to buffer
|
|
||||||
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
|
||||||
* @returns {object} in the form of {
|
|
||||||
* start: function(tag, attrs, unary) {},
|
|
||||||
* end: function(tag) {},
|
|
||||||
* chars: function(text) {},
|
|
||||||
* comment: function(text) {}
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
function htmlSanitizeWriter(buf, uriValidator){
|
|
||||||
var ignore = false;
|
|
||||||
var out = angular.bind(buf, buf.push);
|
|
||||||
return {
|
|
||||||
start: function(tag, attrs, unary){
|
|
||||||
tag = angular.lowercase(tag);
|
|
||||||
if (!ignore && specialElements[tag]) {
|
|
||||||
ignore = tag;
|
|
||||||
}
|
|
||||||
if (!ignore && validElements[tag] === true) {
|
|
||||||
out('<');
|
|
||||||
out(tag);
|
|
||||||
angular.forEach(attrs, function(value, key){
|
|
||||||
var lkey=angular.lowercase(key);
|
|
||||||
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
|
|
||||||
if (validAttrs[lkey] === true &&
|
|
||||||
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
|
|
||||||
out(' ');
|
|
||||||
out(key);
|
|
||||||
out('="');
|
|
||||||
out(encodeEntities(value));
|
|
||||||
out('"');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
out(unary ? '/>' : '>');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end: function(tag){
|
|
||||||
tag = angular.lowercase(tag);
|
|
||||||
if (!ignore && validElements[tag] === true) {
|
|
||||||
out('</');
|
|
||||||
out(tag);
|
|
||||||
out('>');
|
|
||||||
}
|
|
||||||
if (tag == ignore) {
|
|
||||||
ignore = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chars: function(chars){
|
|
||||||
if (!ignore) {
|
|
||||||
out(encodeEntities(chars));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// define ngSanitize module and register $sanitize service
|
|
||||||
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
||||||
|
|
||||||
/* global sanitizeText: false */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc filter
|
|
||||||
* @name ngSanitize.filter:linky
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
|
||||||
* plain email address links.
|
|
||||||
*
|
|
||||||
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
|
|
||||||
*
|
|
||||||
* @param {string} text Input text.
|
|
||||||
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
|
|
||||||
* @returns {string} Html-linkified text.
|
|
||||||
*
|
|
||||||
* @usage
|
|
||||||
<span ng-bind-html="linky_expression | linky"></span>
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
<doc:example module="ngSanitize">
|
|
||||||
<doc:source>
|
|
||||||
<script>
|
|
||||||
function Ctrl($scope) {
|
|
||||||
$scope.snippet =
|
|
||||||
'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/.';
|
|
||||||
$scope.snippetWithTarget = 'http://angularjs.org/';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div ng-controller="Ctrl">
|
|
||||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Filter</td>
|
|
||||||
<td>Source</td>
|
|
||||||
<td>Rendered</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="linky-filter">
|
|
||||||
<td>linky filter</td>
|
|
||||||
<td>
|
|
||||||
<pre><div ng-bind-html="snippet | linky"><br></div></pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div ng-bind-html="snippet | linky"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="linky-target">
|
|
||||||
<td>linky target</td>
|
|
||||||
<td>
|
|
||||||
<pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="escaped-html">
|
|
||||||
<td>no filter</td>
|
|
||||||
<td><pre><div ng-bind="snippet"><br></div></pre></td>
|
|
||||||
<td><div ng-bind="snippet"></div></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</doc:source>
|
|
||||||
<doc:protractor>
|
|
||||||
it('should linkify the snippet with urls', function() {
|
|
||||||
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(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() {
|
|
||||||
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(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:protractor>
|
|
||||||
</doc:example>
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
if (!text) return text;
|
|
||||||
var match;
|
|
||||||
var raw = text;
|
|
||||||
var html = [];
|
|
||||||
var url;
|
|
||||||
var i;
|
|
||||||
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;
|
|
||||||
addText(raw.substr(0, i));
|
|
||||||
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
|
|
||||||
raw = raw.substring(i + match[0].length);
|
|
||||||
}
|
|
||||||
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);
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for jstd scenario adapter
|
|
||||||
*/
|
|
||||||
var jstdScenarioAdapter = {
|
|
||||||
relativeUrlPrefix: '/build/docs/'
|
|
||||||
};
|
|
|
@ -1,202 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.5
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSTestDriver adapter for angular scenario tests
|
|
||||||
*
|
|
||||||
* Example of jsTestDriver.conf for running scenario tests with JSTD:
|
|
||||||
<pre>
|
|
||||||
server: http://localhost:9877
|
|
||||||
|
|
||||||
load:
|
|
||||||
- lib/angular-scenario.js
|
|
||||||
- lib/jstd-scenario-adapter-config.js
|
|
||||||
- lib/jstd-scenario-adapter.js
|
|
||||||
# your test files go here #
|
|
||||||
|
|
||||||
proxy:
|
|
||||||
- {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
|
|
||||||
* Note the order of files - it's important !
|
|
||||||
*
|
|
||||||
* Example of jstd-scenario-adapter-config.js
|
|
||||||
<pre>
|
|
||||||
var jstdScenarioAdapter = {
|
|
||||||
relativeUrlPrefix: '/your-prefix/'
|
|
||||||
};
|
|
||||||
</pre>
|
|
||||||
*
|
|
||||||
* Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
|
|
||||||
* You have to configure this to work together with JSTD proxy.
|
|
||||||
*
|
|
||||||
* Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
|
|
||||||
* Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
|
|
||||||
* That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom type of test case
|
|
||||||
*
|
|
||||||
* @const
|
|
||||||
* @see jstestdriver.TestCaseInfo
|
|
||||||
*/
|
|
||||||
var SCENARIO_TYPE = 'scenario';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin for JSTestDriver
|
|
||||||
* Connection point between scenario's jstd output and jstestdriver.
|
|
||||||
*
|
|
||||||
* @see jstestdriver.PluginRegistrar
|
|
||||||
*/
|
|
||||||
function JstdPlugin() {
|
|
||||||
var nop = function() {};
|
|
||||||
|
|
||||||
this.reportResult = nop;
|
|
||||||
this.reportEnd = nop;
|
|
||||||
this.runScenario = nop;
|
|
||||||
|
|
||||||
this.name = 'Angular Scenario Adapter';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called for each JSTD TestCase
|
|
||||||
*
|
|
||||||
* Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
|
|
||||||
* Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
|
|
||||||
*
|
|
||||||
* @param {jstestdriver.TestRunConfiguration} configuration
|
|
||||||
* @param {Function} onTestDone
|
|
||||||
* @param {Function} onAllTestsComplete
|
|
||||||
* @returns {boolean} True if this type of test is handled by this plugin, false otherwise
|
|
||||||
*/
|
|
||||||
this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
|
|
||||||
if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
|
|
||||||
|
|
||||||
this.reportResult = onTestDone;
|
|
||||||
this.reportEnd = onAllTestsComplete;
|
|
||||||
this.runScenario();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
|
|
||||||
testRunsConfiguration.push(
|
|
||||||
new jstestdriver.TestRunConfiguration(
|
|
||||||
new jstestdriver.TestCaseInfo(
|
|
||||||
'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton instance of the plugin
|
|
||||||
* Accessed using closure by:
|
|
||||||
* - jstd output (reports to this plugin)
|
|
||||||
* - initScenarioAdapter (register the plugin to jstd)
|
|
||||||
*/
|
|
||||||
var plugin = new JstdPlugin();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise scenario jstd-adapter
|
|
||||||
* (only if jstestdriver is defined)
|
|
||||||
*
|
|
||||||
* @param {Object} jstestdriver Undefined when run from browser (without jstd)
|
|
||||||
* @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
|
|
||||||
* @param {Object=} config Configuration object, supported properties:
|
|
||||||
* - relativeUrlPrefix: prefix for all relative links when navigateTo()
|
|
||||||
*/
|
|
||||||
function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
|
|
||||||
if (jstestdriver) {
|
|
||||||
// create and register ScenarioPlugin
|
|
||||||
jstestdriver.pluginRegistrar.register(plugin);
|
|
||||||
plugin.runScenario = initScenarioAndRun;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HACK (angular.scenario.Application.navigateTo)
|
|
||||||
*
|
|
||||||
* We need to navigate to relative urls when running from browser (without JSTD),
|
|
||||||
* because we want to allow running scenario tests without creating its own virtual host.
|
|
||||||
* For example: http://angular.local/build/docs/docs-scenario.html
|
|
||||||
*
|
|
||||||
* On the other hand, when running with JSTD, we need to navigate to absolute urls,
|
|
||||||
* because of JSTD proxy. (proxy, because of same domain policy)
|
|
||||||
*
|
|
||||||
* So this hack is applied only if running with JSTD and change all relative urls to absolute.
|
|
||||||
*/
|
|
||||||
var appProto = angular.scenario.Application.prototype,
|
|
||||||
navigateTo = appProto.navigateTo,
|
|
||||||
relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
|
|
||||||
|
|
||||||
appProto.navigateTo = function(url, loadFn, errorFn) {
|
|
||||||
if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
|
|
||||||
url != 'about:blank' && !url.match(/^https?/)) {
|
|
||||||
url = relativeUrlPrefix + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return navigateTo.call(this, url, loadFn, errorFn);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds proper TestResult object from given model spec
|
|
||||||
*
|
|
||||||
* TODO(vojta) report error details
|
|
||||||
*
|
|
||||||
* @param {angular.scenario.ObjectModel.Spec} spec
|
|
||||||
* @returns {jstestdriver.TestResult}
|
|
||||||
*/
|
|
||||||
function createTestResultFromSpec(spec) {
|
|
||||||
var map = {
|
|
||||||
success: 'PASSED',
|
|
||||||
error: 'ERROR',
|
|
||||||
failure: 'FAILED'
|
|
||||||
};
|
|
||||||
|
|
||||||
return new jstestdriver.TestResult(
|
|
||||||
spec.fullDefinitionName,
|
|
||||||
spec.name,
|
|
||||||
jstestdriver.TestResult.RESULT[map[spec.status]],
|
|
||||||
spec.error || '',
|
|
||||||
spec.line || '',
|
|
||||||
spec.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates JSTD output (jstestdriver.TestResult)
|
|
||||||
*/
|
|
||||||
angular.scenario.output('jstd', function(context, runner, model) {
|
|
||||||
model.on('SpecEnd', function(spec) {
|
|
||||||
plugin.reportResult(createTestResultFromSpec(spec));
|
|
||||||
});
|
|
||||||
|
|
||||||
model.on('RunnerEnd', function() {
|
|
||||||
plugin.reportEnd();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
|
|
||||||
})(window);
|
|
|
@ -1,239 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhanced Select2 Dropmenus
|
|
||||||
*
|
|
||||||
* @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
|
|
||||||
* This change is so that you do not have to do an additional query yourself on top of Select2's own query
|
|
||||||
* @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
|
|
||||||
*/
|
|
||||||
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
|
|
||||||
var options = {};
|
|
||||||
if (uiSelect2Config) {
|
|
||||||
angular.extend(options, uiSelect2Config);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
require: 'ngModel',
|
|
||||||
priority: 1,
|
|
||||||
compile: function (tElm, tAttrs) {
|
|
||||||
var watch,
|
|
||||||
repeatOption,
|
|
||||||
repeatAttr,
|
|
||||||
isSelect = tElm.is('select'),
|
|
||||||
isMultiple = angular.isDefined(tAttrs.multiple);
|
|
||||||
|
|
||||||
// Enable watching of the options dataset if in use
|
|
||||||
if (tElm.is('select')) {
|
|
||||||
repeatOption = tElm.find( 'optgroup[ng-repeat], optgroup[data-ng-repeat], option[ng-repeat], option[data-ng-repeat]');
|
|
||||||
|
|
||||||
if (repeatOption.length) {
|
|
||||||
repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
|
|
||||||
watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function (scope, elm, attrs, controller) {
|
|
||||||
// instance-specific options
|
|
||||||
var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert from Select2 view-model to Angular view-model.
|
|
||||||
*/
|
|
||||||
var convertToAngularModel = function(select2_data) {
|
|
||||||
var model;
|
|
||||||
if (opts.simple_tags) {
|
|
||||||
model = [];
|
|
||||||
angular.forEach(select2_data, function(value, index) {
|
|
||||||
model.push(value.id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
model = select2_data;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert from Angular view-model to Select2 view-model.
|
|
||||||
*/
|
|
||||||
var convertToSelect2Model = function(angular_data) {
|
|
||||||
var model = [];
|
|
||||||
if (!angular_data) {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.simple_tags) {
|
|
||||||
model = [];
|
|
||||||
angular.forEach(
|
|
||||||
angular_data,
|
|
||||||
function(value, index) {
|
|
||||||
model.push({'id': value, 'text': value});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
model = angular_data;
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isSelect) {
|
|
||||||
// Use <select multiple> instead
|
|
||||||
delete opts.multiple;
|
|
||||||
delete opts.initSelection;
|
|
||||||
} else if (isMultiple) {
|
|
||||||
opts.multiple = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller) {
|
|
||||||
// Watch the model for programmatic changes
|
|
||||||
scope.$watch(tAttrs.ngModel, function(current, old) {
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (current === old) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
controller.$render();
|
|
||||||
}, true);
|
|
||||||
controller.$render = function () {
|
|
||||||
if (isSelect) {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
} else {
|
|
||||||
if (opts.multiple) {
|
|
||||||
var viewValue = controller.$viewValue;
|
|
||||||
if (angular.isString(viewValue)) {
|
|
||||||
viewValue = viewValue.split(',');
|
|
||||||
}
|
|
||||||
elm.select2(
|
|
||||||
'data', convertToSelect2Model(viewValue));
|
|
||||||
} else {
|
|
||||||
if (angular.isObject(controller.$viewValue)) {
|
|
||||||
elm.select2('data', controller.$viewValue);
|
|
||||||
} else if (!controller.$viewValue) {
|
|
||||||
elm.select2('data', null);
|
|
||||||
} else {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Watch the options dataset for changes
|
|
||||||
if (watch) {
|
|
||||||
scope.$watch(watch, function (newVal, oldVal, scope) {
|
|
||||||
if (angular.equals(newVal, oldVal)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Delayed so that the options have time to be rendered
|
|
||||||
$timeout(function () {
|
|
||||||
elm.select2('val', controller.$viewValue);
|
|
||||||
// Refresh angular to remove the superfluous option
|
|
||||||
elm.trigger('change');
|
|
||||||
if(newVal && !oldVal && controller.$setPristine) {
|
|
||||||
controller.$setPristine(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update valid and dirty statuses
|
|
||||||
controller.$parsers.push(function (value) {
|
|
||||||
var div = elm.prev();
|
|
||||||
div
|
|
||||||
.toggleClass('ng-invalid', !controller.$valid)
|
|
||||||
.toggleClass('ng-valid', controller.$valid)
|
|
||||||
.toggleClass('ng-invalid-required', !controller.$valid)
|
|
||||||
.toggleClass('ng-valid-required', controller.$valid)
|
|
||||||
.toggleClass('ng-dirty', controller.$dirty)
|
|
||||||
.toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isSelect) {
|
|
||||||
// Set the view and model value and update the angular template manually for the ajax/multiple select2.
|
|
||||||
elm.bind("change", function (e) {
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
|
|
||||||
if (scope.$$phase || scope.$root.$$phase) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
scope.$apply(function () {
|
|
||||||
controller.$setViewValue(
|
|
||||||
convertToAngularModel(elm.select2('data')));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.initSelection) {
|
|
||||||
var initSelection = opts.initSelection;
|
|
||||||
opts.initSelection = function (element, callback) {
|
|
||||||
initSelection(element, function (value) {
|
|
||||||
var isPristine = controller.$pristine;
|
|
||||||
controller.$setViewValue(convertToAngularModel(value));
|
|
||||||
callback(value);
|
|
||||||
if (isPristine) {
|
|
||||||
controller.$setPristine();
|
|
||||||
}
|
|
||||||
elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elm.bind("$destroy", function() {
|
|
||||||
elm.select2("destroy");
|
|
||||||
});
|
|
||||||
|
|
||||||
attrs.$observe('disabled', function (value) {
|
|
||||||
elm.select2('enable', !value);
|
|
||||||
});
|
|
||||||
|
|
||||||
attrs.$observe('readonly', function (value) {
|
|
||||||
elm.select2('readonly', !!value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (attrs.ngMultiple) {
|
|
||||||
scope.$watch(attrs.ngMultiple, function(newVal) {
|
|
||||||
attrs.$set('multiple', !!newVal);
|
|
||||||
elm.select2(opts);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the plugin late so that the injected DOM does not disrupt the template compiler
|
|
||||||
$timeout(function () {
|
|
||||||
elm.select2(opts);
|
|
||||||
|
|
||||||
// Set initial value - I'm not sure about this but it seems to need to be there
|
|
||||||
elm.select2('data', controller.$modelValue);
|
|
||||||
// important!
|
|
||||||
controller.$render();
|
|
||||||
|
|
||||||
// Not sure if I should just check for !isSelect OR if I should check for 'tags' key
|
|
||||||
if (!opts.initSelection && !isSelect) {
|
|
||||||
var isPristine = controller.$pristine;
|
|
||||||
controller.$setViewValue(
|
|
||||||
convertToAngularModel(elm.select2('data'))
|
|
||||||
);
|
|
||||||
if (isPristine) {
|
|
||||||
controller.$setPristine();
|
|
||||||
}
|
|
||||||
elm.prev().toggleClass('ng-pristine', controller.$pristine);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
||||||
{"full":"1.0.7","major":"1","minor":"0","dot":"7","codename":"monochromatic-rainbow","cdn":"1.0.6"}
|
|
|
@ -1 +0,0 @@
|
||||||
1.0.7
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"realm" : "twitter-identity-provider-realm",
|
|
||||||
"resource" : "twitter-authentication",
|
|
||||||
"auth-server-url": "/auth",
|
|
||||||
"ssl-required" : "external",
|
|
||||||
"public-client" : true
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "twitter-identity-provider-realm",
|
|
||||||
"enabled": true,
|
|
||||||
"sslRequired": "external",
|
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
|
||||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
|
||||||
"defaultRoles": [ "user" ],
|
|
||||||
"users" : [
|
|
||||||
{
|
|
||||||
"username" : "admin",
|
|
||||||
"enabled": true,
|
|
||||||
"email" : "admin@admin.com",
|
|
||||||
"firstName": "Admin",
|
|
||||||
"lastName": "Burke",
|
|
||||||
"credentials" : [
|
|
||||||
{ "type" : "password",
|
|
||||||
"value" : "password" }
|
|
||||||
],
|
|
||||||
"realmRoles": [ "user","admin" ],
|
|
||||||
"clientRoles": {
|
|
||||||
"realm-management": [ "realm-admin" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles" : {
|
|
||||||
"realm" : [
|
|
||||||
{
|
|
||||||
"name": "user",
|
|
||||||
"description": "User privileges"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"clients": [
|
|
||||||
{
|
|
||||||
"clientId": "twitter-authentication",
|
|
||||||
"enabled": true,
|
|
||||||
"publicClient" : true,
|
|
||||||
"adminUrl": "/twitter-authentication",
|
|
||||||
"baseUrl": "/twitter-authentication",
|
|
||||||
"redirectUris": [
|
|
||||||
"/twitter-authentication/*"
|
|
||||||
],
|
|
||||||
"webOrigins": [
|
|
||||||
"http://localhost:8080"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"clientId": "admin-client",
|
|
||||||
"enabled": true,
|
|
||||||
"fullScopeAllowed": true,
|
|
||||||
"baseUrl": "/admin-client",
|
|
||||||
"directAccessGrantsEnabled": true,
|
|
||||||
"redirectUris": [
|
|
||||||
"/admin-client/*"
|
|
||||||
],
|
|
||||||
"secret": "password"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"identityProviders": [
|
|
||||||
{
|
|
||||||
"alias" : "twitter",
|
|
||||||
"providerId" : "twitter",
|
|
||||||
"enabled": true,
|
|
||||||
"updateProfileFirstLogin" : "true",
|
|
||||||
"storeToken" : "true",
|
|
||||||
"addReadTokenRoleOnCreate" : true,
|
|
||||||
"config": {
|
|
||||||
"clientId": "CHANGE_CLIENT_ID",
|
|
||||||
"clientSecret": "CHANGE_CLIENT_SECRET"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
Keycloak CORS support
|
|
||||||
===================================
|
|
||||||
The following examples requires Wildfly 8.0.0, JBoss EAP 6.x, or JBoss AS 7.1.1. This example simulates Browser CORS
|
|
||||||
requests. While the examples will run on one machine, the servers/applications are configured to point to different domains:
|
|
||||||
* **localhost-auth** is where the Keycloak auth server lives
|
|
||||||
* **localhost-db** is where a database REST service lives
|
|
||||||
* **localhost** is where the Javascript application lives
|
|
||||||
|
|
||||||
In the demo you will visit the Javascript application and be redirected to Keycloak to login. As part of the login process,
|
|
||||||
the javascript application will have to make a CORS request to the auth server (localhost-auth) to obtain a token. After it logs in, the
|
|
||||||
application will make another CORS request to the REST database service (localhost-db).
|
|
||||||
|
|
||||||
Here are some of the configuration additions to this example to enable CORS:
|
|
||||||
1. The **angular-product** application in realm configuration has a Web Origin of **http://localhost:8080**. When you log into
|
|
||||||
the angular-product application, Keycloak will add the Web Origins for that application to the token. Any CORS request made
|
|
||||||
will check these allowed origins to make sure they match up with the Origin header the browser is sending
|
|
||||||
2. The **angular-product** application config (keycloak.json) points the auth-server at **http://localhost-auth:8080/auth**
|
|
||||||
3. The **database-service** config (keycloak.json) has an additional flag set **enable-cors**
|
|
||||||
|
|
||||||
Step 1: Edit your hosts file
|
|
||||||
--------------------------------------
|
|
||||||
The demo expects additional host mappings for localhost. So, you need to edit your machine's host file (/etc/hosts or
|
|
||||||
C:\Windows\System32\drivers\etc\hosts) and add the following entries:
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
127.0.0.1 localhost-auth
|
|
||||||
127.0.0.1 localhost-db
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Step 2: Make sure you've set up the Keycloak Server and have it running
|
|
||||||
--------------------------------------
|
|
||||||
You will run this demo on the same server as the keycloak server. It is best to use the appliance as everything is all set up.
|
|
||||||
See documentation on how to set this up.
|
|
||||||
|
|
||||||
Step 3: Import the Test Realm
|
|
||||||
---------------------------------------
|
|
||||||
Next thing you have to do is import the test realm for the demo. Clicking on the below link will bring you to the
|
|
||||||
create realm page in the Admin UI. The username/password is admin/admin to login in. Keycloak will ask you to
|
|
||||||
create a new admin password before you can go to the create realm page.
|
|
||||||
|
|
||||||
[http://localhost-auth:8080/auth/admin/index.html#/create/realm](http://localhost-auth:8080/auth/admin/index.html#/create/realm)
|
|
||||||
|
|
||||||
Import the cors-realm.json file that is in the cors/ example directory. Feel free to browse the setup of the realm,
|
|
||||||
particularly the angular-product application.
|
|
||||||
|
|
||||||
|
|
||||||
Step 4: Build and deploy
|
|
||||||
---------------------------------------
|
|
||||||
next you must build and deploy
|
|
||||||
|
|
||||||
```
|
|
||||||
cd cors
|
|
||||||
mvn clean install wildfly:deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
Step 5: Login and Observe Apps
|
|
||||||
---------------------------------------
|
|
||||||
Try going to the customer app and view customer data:
|
|
||||||
|
|
||||||
[http://localhost:8080/angular-cors-product/index.html](http://localhost:8080/angular-cors-product/index.html)
|
|
||||||
|
|
||||||
This should take you to the auth-server login screen. Enter username: bburke@redhat.com and password: password. You
|
|
||||||
should be brought back to a simple and boring HTML page. Click the Reload button to show the product listing. Reload
|
|
||||||
causes an HTTP request to a different domain, this will trigger the browser's CORS protocol.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
1
examples/cors/angular-product-app/.gitignore
vendored
1
examples/cors/angular-product-app/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
/.externalToolBuilders/*
|
|
|
@ -1,53 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-examples-cors-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>org.keycloak.example.demo</groupId>
|
|
||||||
<artifactId>cors-angular-product-example</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
<name>Angular Product Portal JS</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>angular-cors-product</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jboss.as.plugins</groupId>
|
|
||||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.wildfly.plugins</groupId>
|
|
||||||
<artifactId>wildfly-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>false</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,124 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Angular Product Portal</title>
|
|
||||||
|
|
||||||
<script src="lib/angular/angular.js"></script>
|
|
||||||
<script src="lib/angular/angular-resource.js"></script>
|
|
||||||
<script src="lib/angular/angular-route.js"></script>
|
|
||||||
<script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
|
|
||||||
|
|
||||||
<script src="/auth/js/keycloak.js"></script>
|
|
||||||
<script src="js/app.js" type="text/javascript"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body data-ng-controller="GlobalCtrl">
|
|
||||||
|
|
||||||
<div id="content-area" class="col-md-9" role="main">
|
|
||||||
<div id="content">
|
|
||||||
<h2><span>Products</span></h2>
|
|
||||||
<a href="" ng-click="logout()">Sign Out</a>
|
|
||||||
<button type="submit" data-ng-click="reloadData()">Reload</button>
|
|
||||||
<table class="table" data-ng-show="products.length > 0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Product Listing</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr data-ng-repeat="p in products">
|
|
||||||
<td>{{p}}</a></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div>
|
|
||||||
<h2><span>Realm Roles</span></h2>
|
|
||||||
<button type="submit" data-ng-click="loadRoles()">load Roles</button>
|
|
||||||
<button type="submit" data-ng-click="addRole()">Add Role</button>
|
|
||||||
<button type="submit" data-ng-click="deleteRole()">Delete Role</button>
|
|
||||||
<table class="table" data-ng-show="roles.length > 0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Role Listing</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr data-ng-repeat="r in roles">
|
|
||||||
<td>{{r.name}}</a></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div>
|
|
||||||
<h2><span>Social providers</span></h2>
|
|
||||||
<button type="submit" data-ng-click="loadServerInfo()">load available social providers</button>
|
|
||||||
<table class="table" data-ng-show="serverInfo.socialProviders.length > 0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Available social providers</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr data-ng-repeat="sp in serverInfo.socialProviders">
|
|
||||||
<td>{{sp}}</a></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div>
|
|
||||||
<h2><span>Realm info</span></h2>
|
|
||||||
<button type="submit" data-ng-click="loadPublicRealmInfo()">Load public realm info</button>
|
|
||||||
|
|
||||||
<div data-ng-show="publicKeys">
|
|
||||||
<b>Realm issuer</b>: {{realmOIDCInfo.issuer}} <br/>
|
|
||||||
<table class="table" data-ng-show="publicKeys.keys.length > 0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Public Key KIDs</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr data-ng-repeat="pk in publicKeys.keys">
|
|
||||||
<td>{{pk.kid}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div>
|
|
||||||
<h2><span>Server version</span></h2>
|
|
||||||
<button type="submit" data-ng-click="loadVersion()">Load version</button>
|
|
||||||
|
|
||||||
<div data-ng-show="version">
|
|
||||||
Keycloak version: {{version.version}} <br/>
|
|
||||||
Keycloak build time: {{version['build-time'] | date:'yyyy-MM-dd HH:mm:ss'}} <br/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,160 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var module = angular.module('product', []);
|
|
||||||
|
|
||||||
var auth = {};
|
|
||||||
var logout = function(){
|
|
||||||
console.log('*** LOGOUT');
|
|
||||||
auth.loggedIn = false;
|
|
||||||
auth.authz = null;
|
|
||||||
window.location = auth.logoutUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
angular.element(document).ready(function ($http) {
|
|
||||||
console.log("*** here");
|
|
||||||
var keycloakAuth = new Keycloak('keycloak.json');
|
|
||||||
auth.loggedIn = false;
|
|
||||||
|
|
||||||
keycloakAuth.init({ onLoad: 'login-required' }).then(function () {
|
|
||||||
console.log('here login');
|
|
||||||
auth.loggedIn = true;
|
|
||||||
auth.authz = keycloakAuth;
|
|
||||||
auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/" + keycloakAuth.realm + "/protocol/openid-connect/logout?redirect_uri=http://localhost:8080/angular-cors-product/index.html"
|
|
||||||
module.factory('Auth', function() {
|
|
||||||
return auth;
|
|
||||||
});
|
|
||||||
angular.bootstrap(document, ["product"]);
|
|
||||||
}).catch(function () {
|
|
||||||
alert("failed to login");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('GlobalCtrl', function($scope, $http) {
|
|
||||||
$scope.products = [];
|
|
||||||
$scope.roles = [];
|
|
||||||
$scope.serverInfo = [];
|
|
||||||
$scope.realm = [];
|
|
||||||
$scope.version = [];
|
|
||||||
$scope.reloadData = function() {
|
|
||||||
$http.get("http://localhost-db:8080/cors-database/products").success(function(data) {
|
|
||||||
$scope.products = angular.fromJson(data);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
$scope.loadRoles = function() {
|
|
||||||
$http.get("http://localhost-auth:8080/auth/admin/realms/" + auth.authz.realm + "/roles").success(function(data) {
|
|
||||||
$scope.roles = angular.fromJson(data);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
$scope.addRole = function() {
|
|
||||||
$http.post("http://localhost-auth:8080/auth/admin/realms/" + auth.authz.realm + "/roles", {name: 'stuff'}).success(function() {
|
|
||||||
$scope.loadRoles();
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
$scope.deleteRole = function() {
|
|
||||||
$http.delete("http://localhost-auth:8080/auth/admin/realms/" + auth.authz.realm + "/roles/stuff").success(function() {
|
|
||||||
$scope.loadRoles();
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.loadServerInfo = function() {
|
|
||||||
$http.get("http://localhost-auth:8080/auth/admin/serverinfo").success(function(data) {
|
|
||||||
$scope.serverInfo = angular.fromJson(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.loadPublicRealmInfo = function() {
|
|
||||||
$http.get("http://localhost-auth:8080/auth/realms/cors/.well-known/openid-configuration").success(function(data) {
|
|
||||||
$scope.realmOIDCInfo = angular.fromJson(data);
|
|
||||||
|
|
||||||
var jwksUri = $scope.realmOIDCInfo.jwks_uri;
|
|
||||||
$http.get(jwksUri).success(function(data) {
|
|
||||||
$scope.publicKeys = angular.fromJson(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.loadVersion = function() {
|
|
||||||
$http.get("http://localhost-auth:8080/auth/version").success(function(data) {
|
|
||||||
$scope.version = angular.fromJson(data);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.logout = logout;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.factory('authInterceptor', function($q, Auth) {
|
|
||||||
return {
|
|
||||||
request: function (config) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
if (Auth.authz.token) {
|
|
||||||
Auth.authz.updateToken(5).then(function() {
|
|
||||||
config.headers = config.headers || {};
|
|
||||||
config.headers.Authorization = 'Bearer ' + Auth.authz.token;
|
|
||||||
|
|
||||||
deferred.resolve(config);
|
|
||||||
}).catch(function() {
|
|
||||||
deferred.reject('Failed to refresh token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.config(function($httpProvider) {
|
|
||||||
$httpProvider.responseInterceptors.push('errorInterceptor');
|
|
||||||
$httpProvider.interceptors.push('authInterceptor');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('errorInterceptor', function($q) {
|
|
||||||
return function(promise) {
|
|
||||||
return promise.then(function(response) {
|
|
||||||
return response;
|
|
||||||
}, function(response) {
|
|
||||||
if (response.status == 401) {
|
|
||||||
console.log('session timeout?');
|
|
||||||
logout();
|
|
||||||
} else if (response.status == 403) {
|
|
||||||
alert("Forbidden");
|
|
||||||
} else if (response.status == 404) {
|
|
||||||
alert("Not found");
|
|
||||||
} else if (response.status) {
|
|
||||||
if (response.data && response.data.errorMessage) {
|
|
||||||
alert(response.data.errorMessage);
|
|
||||||
} else {
|
|
||||||
alert("An unexpected server error has occurred");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $q.reject(response);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"realm" : "cors",
|
|
||||||
"auth-server-url" : "http://localhost-auth:8080/auth",
|
|
||||||
"ssl-required" : "external",
|
|
||||||
"resource" : "angular-cors-product",
|
|
||||||
"public-client" : true
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue