keycloak-memoizeit
Changes
core/src/main/java/org/keycloak/representations/idm/RequiredCredentialRepresentation.java 39(+0 -39)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.jquery.json 36(+36 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ar.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ca.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_cs.js 49(+49 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_da.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_de.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_el.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_en.js.template 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_es.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_et.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_eu.js 43(+43 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_fi.js 28(+28 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_fr.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_gl.js 43(+43 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_he.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_hr.js 42(+42 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_hu.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_id.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_is.js 16(+16 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_it.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ja.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ko.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_lt.js 29(+29 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_lv.js 16(+16 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_mk.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_nl.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_no.js 18(+18 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pl.js 37(+37 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pt-BR.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pt-PT.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ro.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ru.js 15(+15 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_sk.js 48(+48 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_sv.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_tr.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ua.js 17(+17 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_vi.js 18(+18 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_zh-CN.js 14(+14 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_zh-TW.js 14(+14 -0)
examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2-spinner.gif 0(+0 -0)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java 2(+1 -1)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java 4(+2 -2)
services/src/main/java/org/keycloak/services/models/picketlink/mappings/ApplicationData.java 6(+3 -3)
services/src/main/java/org/keycloak/services/models/picketlink/mappings/ApplicationEntity.java 4(+2 -2)
services/src/main/java/org/keycloak/services/models/picketlink/relationships/RequiredApplicationCredentialRelationship.java 2(+1 -1)
services/src/main/java/org/keycloak/services/models/picketlink/relationships/RequiredCredentialRelationship.java 10(+10 -0)
services/src/test/resources/testrealm.json 214(+106 -108)
Details
diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
index 955f9df..592ddab 100755
--- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java
+++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
@@ -111,7 +111,7 @@ public class AbstractOAuthClient {
.param("grant_type", "authorization_code")
.param("code", code)
.param("client_id", clientId)
- .param("Password", password)
+ .param("password", password)
.param("redirect_uri", redirectUri);
Response res = client.target(codeUrl).request().header(HttpHeaders.AUTHORIZATION, authHeader).post(Entity.form(codeForm));
try {
diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
index 2259317..af9b292 100755
--- a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java
@@ -5,9 +5,13 @@ package org.keycloak.representations.idm;
* @version $Revision: 1 $
*/
public class CredentialRepresentation {
+ public static final String PASSWORD = "password";
+ public static final String TOTP = "totp";
+ public static final String CLIENT_CERT = "cert";
+
protected String type;
protected String value;
- protected boolean hashed;
+ protected String device;
public String getType() {
return type;
@@ -25,11 +29,11 @@ public class CredentialRepresentation {
this.value = value;
}
- public boolean isHashed() {
- return hashed;
+ public String getDevice() {
+ return device;
}
- public void setHashed(boolean hashed) {
- this.hashed = hashed;
+ public void setDevice(String device) {
+ this.device = device;
}
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 896c1c9..8ef7c4a 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -17,16 +17,18 @@ public class RealmRepresentation {
protected boolean enabled;
protected boolean sslNotRequired;
protected boolean cookieLoginAllowed;
+ protected boolean registrationAllowed;
+ protected boolean social;
protected String privateKey;
protected String publicKey;
protected List<RoleRepresentation> roles;
- protected List<RequiredCredentialRepresentation> requiredCredentials;
- protected List<RequiredCredentialRepresentation> requiredResourceCredentials;
- protected List<RequiredCredentialRepresentation> requiredOAuthClientCredentials;
+ protected Set<String> requiredCredentials;
+ protected Set<String> requiredApplicationCredentials;
+ protected Set<String> requiredOAuthClientCredentials;
protected List<UserRepresentation> users;
protected List<RoleMappingRepresentation> roleMappings;
protected List<ScopeMappingRepresentation> scopeMappings;
- protected List<ResourceRepresentation> resources;
+ protected List<ApplicationRepresentation> applications;
public String getSelf() {
@@ -57,14 +59,14 @@ public class RealmRepresentation {
return users;
}
- public List<ResourceRepresentation> getResources() {
- return resources;
+ public List<ApplicationRepresentation> getApplications() {
+ return applications;
}
- public ResourceRepresentation resource(String name) {
- ResourceRepresentation resource = new ResourceRepresentation();
- if (resources == null) resources = new ArrayList<ResourceRepresentation>();
- resources.add(resource);
+ public ApplicationRepresentation resource(String name) {
+ ApplicationRepresentation resource = new ApplicationRepresentation();
+ if (applications == null) applications = new ArrayList<ApplicationRepresentation>();
+ applications.add(resource);
resource.setName(name);
return resource;
}
@@ -81,8 +83,8 @@ public class RealmRepresentation {
return user;
}
- public void setResources(List<ResourceRepresentation> resources) {
- this.resources = resources;
+ public void setApplications(List<ApplicationRepresentation> applications) {
+ this.applications = applications;
}
public boolean isEnabled() {
@@ -141,27 +143,27 @@ public class RealmRepresentation {
return mapping;
}
- public List<RequiredCredentialRepresentation> getRequiredCredentials() {
+ public Set<String> getRequiredCredentials() {
return requiredCredentials;
}
- public void setRequiredCredentials(List<RequiredCredentialRepresentation> requiredCredentials) {
+ public void setRequiredCredentials(Set<String> requiredCredentials) {
this.requiredCredentials = requiredCredentials;
}
- public List<RequiredCredentialRepresentation> getRequiredResourceCredentials() {
- return requiredResourceCredentials;
+ public Set<String> getRequiredApplicationCredentials() {
+ return requiredApplicationCredentials;
}
- public void setRequiredResourceCredentials(List<RequiredCredentialRepresentation> requiredResourceCredentials) {
- this.requiredResourceCredentials = requiredResourceCredentials;
+ public void setRequiredApplicationCredentials(Set<String> requiredApplicationCredentials) {
+ this.requiredApplicationCredentials = requiredApplicationCredentials;
}
- public List<RequiredCredentialRepresentation> getRequiredOAuthClientCredentials() {
+ public Set<String> getRequiredOAuthClientCredentials() {
return requiredOAuthClientCredentials;
}
- public void setRequiredOAuthClientCredentials(List<RequiredCredentialRepresentation> requiredOAuthClientCredentials) {
+ public void setRequiredOAuthClientCredentials(Set<String> requiredOAuthClientCredentials) {
this.requiredOAuthClientCredentials = requiredOAuthClientCredentials;
}
@@ -196,4 +198,20 @@ public class RealmRepresentation {
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
+
+ public boolean isRegistrationAllowed() {
+ return registrationAllowed;
+ }
+
+ public void setRegistrationAllowed(boolean registrationAllowed) {
+ this.registrationAllowed = registrationAllowed;
+ }
+
+ public boolean isSocial() {
+ return social;
+ }
+
+ public void setSocial(boolean social) {
+ this.social = social;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index cbbff89..2b3d02c 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -14,6 +14,9 @@ public class UserRepresentation {
protected String self; // link
protected String username;
protected boolean enabled;
+ protected String firstName;
+ protected String lastName;
+ protected String email;
protected Map<String, String> attributes;
protected List<CredentialRepresentation> credentials;
@@ -25,6 +28,30 @@ public class UserRepresentation {
this.self = self;
}
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
public String getUsername() {
return username;
}
@@ -55,12 +82,11 @@ public class UserRepresentation {
this.credentials = credentials;
}
- public UserRepresentation credential(String type, String value, boolean hashed) {
+ public UserRepresentation credential(String type, String value) {
if (this.credentials == null) credentials = new ArrayList<CredentialRepresentation>();
CredentialRepresentation cred = new CredentialRepresentation();
cred.setType(type);
cred.setValue(value);
- cred.setHashed(hashed);
credentials.add(cred);
return this;
}
diff --git a/examples/as7-eap-demo/server/src/main/java/org/keycloak/example/demo/DemoApplication.java b/examples/as7-eap-demo/server/src/main/java/org/keycloak/example/demo/DemoApplication.java
index eab7b1e..faf899e 100755
--- a/examples/as7-eap-demo/server/src/main/java/org/keycloak/example/demo/DemoApplication.java
+++ b/examples/as7-eap-demo/server/src/main/java/org/keycloak/example/demo/DemoApplication.java
@@ -1,6 +1,7 @@
package org.keycloak.example.demo;
import org.jboss.resteasy.jwt.JsonSerialization;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.KeycloakSession;
@@ -40,7 +41,7 @@ public class DemoApplication extends KeycloakApplication {
defaultRealm.setCookieLoginAllowed(true);
defaultRealm.setRegistrationAllowed(true);
manager.generateRealmKeys(defaultRealm);
- defaultRealm.addRequiredCredential(RequiredCredentialModel.PASSWORD);
+ defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
RealmRepresentation rep = loadJson("META-INF/testrealm.json");
diff --git a/examples/as7-eap-demo/server/src/main/resources/META-INF/persistence.xml b/examples/as7-eap-demo/server/src/main/resources/META-INF/persistence.xml
index e0dc722..ad40046 100755
--- a/examples/as7-eap-demo/server/src/main/resources/META-INF/persistence.xml
+++ b/examples/as7-eap-demo/server/src/main/resources/META-INF/persistence.xml
@@ -19,7 +19,7 @@
<class>org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>
<class>org.keycloak.services.models.picketlink.mappings.RealmEntity</class>
- <class>org.keycloak.services.models.picketlink.mappings.ResourceEntity</class>
+ <class>org.keycloak.services.models.picketlink.mappings.ApplicationEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
diff --git a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
index b333aab..2fa8a91 100755
--- a/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
+++ b/examples/as7-eap-demo/server/src/main/resources/META-INF/testrealm.json
@@ -1,94 +1,86 @@
{
- "realm" : "demo",
- "enabled" : true,
- "tokenLifespan" : 10,
- "accessCodeLifespan" : 10,
- "sslNotRequired" : true,
- "cookieLoginAllowed" : true,
- "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",
- "requiredCredentials" : [
+ "realm": "demo",
+ "enabled": true,
+ "tokenLifespan": 300,
+ "accessCodeLifespan": 10,
+ "sslNotRequired": true,
+ "cookieLoginAllowed": true,
+ "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",
+ "requiredCredentials": [ "password" ],
+ "requiredApplicationCredentials": [ "password" ],
+ "requiredOAuthClientCredentials": [ "password" ],
+ "users" : [
{
- "type" : "Password",
- "input" : true,
- "secret" : true
- }
- ],
- "requiredResourceCredentials" : [
- {
- "type" : "Password",
- "input" : true,
- "secret" : true
- }
- ],
- "requiredOAuthClientCredentials" : [
- {
- "type" : "Password",
- "input" : true,
- "secret" : true
- }
- ],
- "users" : [
- {
"username" : "bburke@redhat.com",
"enabled" : true,
"attributes" : {
"email" : "bburke@redhat.com"
},
"credentials" : [
- { "type" : "Password",
- "value" : "password" }
+ { "type" : "password",
+ "value" : "password" }
]
- },
- {
+ },
+ {
"username" : "third-party",
"enabled" : true,
"credentials" : [
- { "type" : "Password",
- "value" : "password" }
+ { "type" : "password",
+ "value" : "password" }
+ ]
+ }
+ ],
+ "roles": [
+ {
+ "name": "user",
+ "description": "Have User privileges"
+ },
+ {
+ "name": "admin",
+ "description": "Have Administrator privileges"
+ }
+ ],
+ "roleMappings": [
+ {
+ "username": "bburke@redhat.com",
+ "roles": ["user"]
+ },
+ {
+ "username": "third-party",
+ "roles": ["KEYCLOAK_IDENTITY_REQUESTER"]
+ }
+ ],
+ "scopeMappings": [
+ {
+ "username": "third-party",
+ "roles": ["user"]
+ }
+ ],
+ "applications": [
+ {
+ "name": "customer-portal",
+ "enabled": true,
+ "adminUrl": "http://localhost:8080/customer-portal/j_admin_request",
+ "useRealmMappings": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "password"
+ }
]
- }
- ],
- "roles" : [
- { "name" : "user", "description" : "Have User privileges" },
- { "name" : "admin", "description" : "Have Administrator privileges" }
- ],
- "roleMappings" : [
- {
- "username" : "bburke@redhat.com",
- "roles" : ["user"]
- },
- {
- "username" : "third-party",
- "roles" : ["KEYCLOAK_IDENTITY_REQUESTER"]
- }
- ],
- "scopeMappings" : [
- {
- "username" : "third-party",
- "roles" : ["user"]
- }
- ],
- "resources" : [
- {
- "name" : "customer-portal",
- "enabled" : true,
- "adminUrl" : "http://localhost:8080/customer-portal/j_admin_request",
- "useRealmMappings" : true,
- "credentials" : [
- { "type" : "Password",
- "value" : "password" }
- ]
- },
- {
- "name" : "product-portal",
- "enabled" : true,
- "adminUrl" : "http://localhost:8080/product-portal/j_admin_request",
- "useRealmMappings" : true,
- "credentials" : [
- { "type" : "Password",
- "value" : "password" }
- ]
- }
- ]
+ },
+ {
+ "name": "product-portal",
+ "enabled": true,
+ "adminUrl": "http://localhost:8080/product-portal/j_admin_request",
+ "useRealmMappings": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "password"
+ }
+ ]
+ }
+ ]
}
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/index.html b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/index.html
index d792b30..ad1a87e 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/index.html
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/index.html
@@ -15,13 +15,20 @@
<link href="lib/bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
<link href="css/admin.css" rel="stylesheet">
<link href="css/admin-responsive.css" rel="stylesheet">
+ <link href="lib/select2-3.4.1/select2.css" rel="stylesheet">
<link href="css/styles.css" rel="stylesheet">
+ <script src="lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
+ <script src="lib/select2-3.4.1/select2.js" type="text/javascript"></script>
+
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-resource.js"></script>
<script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
+ <script src="lib/jquery/jquery.idletimer.js" type="text/javascript"></script>
+ <script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
+ <script src="lib/angular/select2.js" type="text/javascript"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/loaders.js"></script>
@@ -57,9 +64,6 @@
</div>
</div>
-<script src="lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
-<script src="lib/jquery/jquery.idletimer.js" type="text/javascript"></script>
-<script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
<script type="text/javascript">
$.idleTimeout('#idletimeout', '#idletimeout a', {
idleAfter: 300,
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/app.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/app.js
index 9fe75a5..6afcfed 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/app.js
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/app.js
@@ -1,6 +1,6 @@
'use strict';
-var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'keycloak.controllers', 'ui.bootstrap' ]);
+var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'keycloak.controllers', 'ui.bootstrap', 'ui.select2' ]);
var resourceRequests = 0;
module.config([ '$routeProvider', function($routeProvider) {
@@ -197,13 +197,12 @@ module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location
};
});
-/*
module.directive('kcInput', function() {
var d = {
scope : true,
replace : false,
link : function(scope, element, attrs) {
- var form = element.closest('form');
+ var form = element.children('form');
var label = element.children('label');
var input = element.children('input');
@@ -242,7 +241,7 @@ module.directive('kcEnter', function() {
});
};
});
-*/
+
module.filter('remove', function() {
return function(input, remove, attribute) {
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/controllers.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/controllers.js
index ef872b3..8680990 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/controllers.js
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/controllers.js
@@ -27,25 +27,9 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
$http.get('/auth-server/rest/saas/admin/realms').success(function(data) {
Current.realms = data;
- var count = 0;
- var showrealm = false;
- var id = null;
- for (var key in data) {
- if (count > 0) {
- showrealm = false;
- break;
- }
- id = key;
- showrealm = true;
- count++;
- }
-
- if (showrealm) {
- console.log('default redirect to realm: ' + id);
- Current.realm = Current.realms[id];
- $location.url("/realms/" + id);
- } else {
- //console.log('not redirecting');
+ if (data.length > 0) {
+ Current.realm = data[0];
+ $location.url("/realms/" + Current.realm.id);
}
});
});
@@ -58,137 +42,115 @@ module.controller('RealmListCtrl', function($scope, Realm, Current) {
module.controller('RealmDropdownCtrl', function($scope, Realm, Current, Auth, $location) {
// Current.realms = Realm.get();
$scope.current = Current;
+ if (Current.realms.length > 0) {
+ console.log('[0]: ' + current.realms[0].realm);
+ }
$scope.changeRealm = function() {
- for (var id in Current.realms) {
- var val = Current.realms[id];
- if (val == Current.realm) {
- $location.url("/realms/" + id);
- break;
- }
- }
+ $location.url("/realms/" + $scope.current.realm.id);
};
$scope.showNav = function() {
- var show = false;
- for (var key in Current.realms) {
- if (typeof Current.realms[key] != "function") {
- if (Current.realms[key] == Current.realm) {
- $scope.currentRealmId = key;
- }
- show = true;
- }
- }
+ var show = Current.realms.length > 0;
+ console.log('Show dropdown? ' + show);
return Auth.loggedIn && show;
}
});
-module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $location, Dialog, Notifications) {
- $scope.realm = angular.copy(realm);
+module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $http, $location, Dialog, Notifications) {
$scope.createRealm = !realm.id;
+ console.log('RealmDetailCtrl');
+
if ($scope.createRealm) {
- $scope.realm.enabled = true;
- $scope.realm.requireSsl = true;
- $scope.realm.cookieLoginAllowed = true;
- $scope.realm.tokenLifespan = 300;
- $scope.realm.tokenLifespanUnit = 'SECONDS';
- $scope.realm.accessCodeLifespan = 300;
- $scope.realm.accessCodeLifespanUnit = 'SECONDS';
+ $scope.realm = {
+ enabled: true,
+ requireSsl: true,
+ cookieLoginAllowed: true,
+ tokenLifespan: 300,
+ tokenLifespanUnit: 'SECONDS',
+ accessCodeLifespan: 300,
+ accessCodeLifespanUnit: 'SECONDS',
+ requiredCredentials: ['password'],
+ requiredOAuthClientCredentials: ['password'],
+ requiredApplicationCredentials: ['password']
+
+ };
} else {
- $scope.realm.name = realm.realm;
- $scope.realm.requireSsl = !$scope.realm.sslNotRequired;
+ if (Current.realm == null || Current.realm.id != realm.id) {
+ for (var i = 0; i < Current.realms.length; i++) {
+ if (realm.id == Current.realms[i].id) {
+ Current.realm = Current.realms[i];
+ break;
+ }
+ }
+ }
+ if (Current.realm == null || Current.realm.id != realm.id) {
+ console.log('should be unreachable');
+ console.log('Why? ' + Current.realms.length + ' ' + Current.realm);
+ return;
+ }
+ $scope.realm = angular.copy(realm);
+ $scope.realm.requireSsl = !realm.sslNotRequired;
$scope.realm.tokenLifespanUnit = 'SECONDS';
- $scope.realm.acessCodeLifespanUnit = 'SECONDS';
- }
-
- $scope.changed = $scope.create;
-
- $scope.$watch('realm', function() {
- if (!angular.equals($scope.realm, realm)) {
- $scope.changed = true;
- }
- }, true);
+ $scope.realm.accessCodeLifespanUnit = 'SECONDS';
- $scope.addRole = function() {
- if ($scope.newRole) {
- if ($scope.realm.roles) {
- for ( var i = 0; i < $scope.realm.roles.length; i++) {
- if ($scope.realm.roles[i] == $scope.newRole) {
- Notifications.warn("Role already exists");
- $scope.newRole = null;
- return;
- }
- }
- }
+ }
- if (!$scope.realm.roles) {
- $scope.realm.roles = [];
- }
+ var oldCopy = angular.copy($scope.realm);
- $scope.realm.roles.push($scope.newRole);
- $scope.newRole = null;
- }
- }
- $scope.removeRole = function(role) {
- Dialog.confirmDelete(role, 'role', function() {
- var i = $scope.realm.roles.indexOf(role);
- if (i > -1) {
- $scope.realm.roles.splice(i, 1);
- }
-
- if ($scope.realm.initialRoles) {
- $scope.removeInitialRole(role);
- }
- });
- };
- $scope.addInitialRole = function() {
- if ($scope.newInitialRole) {
- if (!$scope.realm.initialRoles) {
- $scope.realm.initialRoles = [];
- }
+ $scope.userCredentialOptions = {
+ 'multiple' : true,
+ 'simple_tags' : true,
+ 'tags' : ['password', 'totp', 'cert']
+ };
- $scope.realm.initialRoles.push($scope.newInitialRole);
- $scope.newInitialRole = null;
- }
- }
+ $scope.changed = $scope.create;
- $scope.removeInitialRole = function(role) {
- var i = $scope.realm.initialRoles.indexOf(role);
- if (i > -1) {
- $scope.realm.initialRoles.splice(i, 1);
+ $scope.$watch('realm', function() {
+ if (!angular.equals($scope.realm, oldCopy)) {
+ $scope.changed = true;
}
- };
+ }, true);
$scope.save = function() {
if ($scope.realmForm.$valid) {
- var realmCopy = {
- realm: $scope.realm.name,
- enabled: $scope.realm.enabled,
- cookieLoginAllowed: $scope.realm.cookieLoginAllowed,
- sslNotRequired: !$scope.realm.requireSsl,
- tokenLifespan: $scope.realm.tokenLifespan,
- accessCodeLifespan: $scope.realm.accessCodeLifespan
-
- };
-
+ var realmCopy = angular.copy($scope.realm);
+ realmCopy.sslNotRequired = !realmCopy.requireSsl;
+ delete realmCopy["requireSsl"];
+ delete realmCopy["tokenLifespanUnit"];
+ delete realmCopy["accessCodeLifespanUnit"];
if ($scope.createRealm) {
Realm.save(realmCopy, function(data, headers) {
+ console.log('creating new realm');
var l = headers().location;
var id = l.substring(l.lastIndexOf("/") + 1);
-
- var data = Realm.get(function() {
+ var data = Realm.query(function() {
Current.realms = data;
- Current.realm = Current.realms[id];
+ for (var i = 0; i < Current.realms.length; i++) {
+ if (Current.realms[i].id == id) {
+ Current.realm = Current.realms[i];
+ }
+ }
});
$location.url("/realms/" + id);
Notifications.success("Created realm");
});
} else {
+ console.log('updating realm...');
+ $scope.changed = false;
Realm.update(realmCopy, function() {
- Current.realms = Realm.get();
- $scope.changed = false;
- realm = angular.copy($scope.realm);
+ var id = realmCopy.id;
+ var data = Realm.query(function() {
+ Current.realms = data;
+ for (var i = 0; i < Current.realms.length; i++) {
+ if (Current.realms[i].id == id) {
+ Current.realm = Current.realms[i];
+ oldCopy = angular.copy($scope.realm);
+ }
+ }
+ });
+ $location.url("/realms/" + id);
Notifications.success("Saved changes to realm");
});
}
@@ -198,7 +160,7 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, $lo
};
$scope.reset = function() {
- $scope.realm = angular.copy(realm);
+ $scope.realm = angular.copy(oldCopy);
$scope.changed = false;
$scope.realmForm.showErrors = false;
};
@@ -385,7 +347,7 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, applications,
application : null
};
- selection.applications = angular.copy(applications);
+ selection.applications = applications;
for (var i=0;i < selection.applications.length; i++) {
if (selection.applications[i].name == application.name) {
@@ -396,10 +358,11 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, applications,
}
$scope.selection = selection;
-
- $scope.application = angular.copy(application);
-
-
+ if (!$scope.create) {
+ $scope.application= selection.application;
+ } else {
+ $scope.application = {};
+ }
$scope.changeApplication = function() {
console.log('ApplicationDetailCtrl.changeApplication() - ' + $scope.selection.application.name);
@@ -407,7 +370,7 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, applications,
};
$scope.$watch('application', function() {
- if (!angular.equals($scope.application, application)) {
+ if (!angular.equals($scope.selection.application, application)) {
$scope.changed = true;
}
}, true);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/services.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/services.js
index 4d54235..b3cd88f 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/services.js
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/js/services.js
@@ -132,7 +132,7 @@ module.factory('Role', function($resource) {
});
module.factory('Application', function($resource) {
- return $resource('/auth-server/rest/saas/admin/realms/:realm/resources/:id', {
+ return $resource('/auth-server/rest/saas/admin/realms/:realm/applications/:id', {
realm : '@realm',
id : '@id'
}, {
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/angular.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/angular.js
index a860c85..ccebef3 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/angular.js
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/angular.js
@@ -13168,7 +13168,7 @@ var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
* @description
* Fetches, compiles and includes an external HTML fragment.
*
- * Keep in mind that Same Origin Policy applies to included resources
+ * Keep in mind that Same Origin Policy applies to included applications
* (e.g. ngInclude won't work for cross-domain requests on all browsers and for
* file:// access on some browsers).
*
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/angular-scenario.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/angular-scenario.js
index f0e5c7d..65e1a3e 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/angular-scenario.js
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/angular-scenario.js
@@ -22574,7 +22574,7 @@ var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
* @description
* Fetches, compiles and includes an external HTML fragment.
*
- * Keep in mind that Same Origin Policy applies to included resources
+ * Keep in mind that Same Origin Policy applies to included applications
* (e.g. ngInclude won't work for cross-domain requests on all browsers and for
* file:// access on some browsers).
*
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/select2.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/select2.js
new file mode 100755
index 0000000..7a51790
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/angular/select2.js
@@ -0,0 +1,195 @@
+/**
+ * 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',
+ compile: function (tElm, tAttrs) {
+ var watch,
+ repeatOption,
+ repeatAttr,
+ isSelect = tElm.is('select'),
+ isMultiple = (tAttrs.multiple !== undefined);
+
+ // Enable watching of the options dataset if in use
+ if (tElm.is('select')) {
+ repeatOption = tElm.find('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) {
+ elm.select2(
+ 'data', convertToSelect2Model(controller.$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 (!newVal) 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');
+ });
+ });
+ }
+
+ // 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 () {
+ if (scope.$$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) {
+ controller.$setViewValue(convertToAngularModel(value));
+ callback(value);
+ });
+ };
+ }
+ }
+ }
+
+ 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) {
+ 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.val(controller.$viewValue);
+ // important!
+ controller.$render();
+
+ // Not sure if I should just check for !isSelect OR if I should check for 'tags' key
+ if (!opts.initSelection && !isSelect)
+ controller.$setViewValue(
+ convertToAngularModel(elm.select2('data')));
+ });
+ };
+ }
+ };
+}]);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/component.json b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/component.json
new file mode 100644
index 0000000..ec47566
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/component.json
@@ -0,0 +1,8 @@
+{
+ "name": "select2",
+ "version": "3.4.1",
+ "main": ["select2.js", "select2.css", "select2.png", "select2x2.png", "select2-spinner.gif"],
+ "dependencies": {
+ "jquery": ">= 1.7.1"
+ }
+}
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/LICENSE b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/LICENSE
new file mode 100644
index 0000000..3c98f3d
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/LICENSE
@@ -0,0 +1,18 @@
+Copyright 2012 Igor Vaynberg
+
+Version: @@ver@@ Timestamp: @@timestamp@@
+
+This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
+General Public License version 2 (the "GPL License"). You may choose either license to govern your
+use of this software only upon the condition that you accept all of the terms of either the Apache
+License or the GPL License.
+
+You may obtain a copy of the Apache License and the GPL License at:
+
+http://www.apache.org/licenses/LICENSE-2.0
+http://www.gnu.org/licenses/gpl-2.0.html
+
+Unless required by applicable law or agreed to in writing, software distributed under the Apache License
+or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+either express or implied. See the Apache License and the GPL License for the specific language governing
+permissions and limitations under the Apache License and the GPL License.
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/README.md b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/README.md
new file mode 100644
index 0000000..cb0be3c
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/README.md
@@ -0,0 +1,83 @@
+Select2
+=======
+
+Select2 is a jQuery-based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.
+
+To get started, checkout examples and documentation at http://ivaynberg.github.com/select2
+
+Use cases
+---------
+
+* Enhancing native selects with search.
+* Enhancing native selects with a better multi-select interface.
+* Loading data from JavaScript: easily load items via ajax and have them searchable.
+* Nesting optgroups: native selects only support one level of nested. Select2 does not have this restriction.
+* Tagging: ability to add new items on the fly.
+* Working with large, remote datasets: ability to partially load a dataset based on the search term.
+* Paging of large datasets: easy support for loading more pages when the results are scrolled to the end.
+* Templating: support for custom rendering of results and selections.
+
+Browser compatibility
+---------------------
+* IE 8+
+* Chrome 8+
+* Firefox 10+
+* Safari 3+
+* Opera 10.6+
+
+Integrations
+------------
+
+* [Wicket-Select2](https://github.com/ivaynberg/wicket-select2) (Java / [Apache Wicket](http://wicket.apache.org))
+* [select2-rails](https://github.com/argerim/select2-rails) (Ruby on Rails)
+* [AngularUI](http://angular-ui.github.com/#directives-select2) ([AngularJS](angularjs.org))
+* [Django](https://github.com/applegrew/django-select2)
+* [Symfony](https://github.com/19Gerhard85/sfSelect2WidgetsPlugin)
+* [Bootstrap](https://github.com/t0m/select2-bootstrap-css) (CSS skin)
+* [Yii](https://github.com/tonybolzan/yii-select2)
+
+Internationalization (i18n)
+---------------------------
+
+Select2 supports multiple languages by simply including the right
+language JS file (`select2_locale_it.js`, `select2_locale_nl.js`, etc.).
+
+Missing a language? Just copy `select2_locale_en.js.template`, translate
+it, and make a pull request back to Select2 here on GitHub.
+
+Bug tracker
+-----------
+
+Have a bug? Please create an issue here on GitHub!
+
+https://github.com/ivaynberg/select2/issues
+
+Mailing list
+------------
+
+Have a question? Ask on our mailing list!
+
+select2@googlegroups.com
+
+https://groups.google.com/d/forum/select2
+
+
+Copyright and license
+---------------------
+
+Copyright 2012 Igor Vaynberg
+
+This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
+General Public License version 2 (the "GPL License"). You may choose either license to govern your
+use of this software only upon the condition that you accept all of the terms of either the Apache
+License or the GPL License.
+
+You may obtain a copy of the Apache License and the GPL License in the LICENSE file, or at:
+
+http://www.apache.org/licenses/LICENSE-2.0
+http://www.gnu.org/licenses/gpl-2.0.html
+
+Unless required by applicable law or agreed to in writing, software distributed under the Apache License
+or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+either express or implied. See the Apache License and the GPL License for the specific language governing
+permissions and limitations under the Apache License and the GPL License.
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/release.sh b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/release.sh
new file mode 100755
index 0000000..0a315ab
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/release.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+set -e
+
+echo -n "Enter the version for this release: "
+
+read ver
+
+if [ ! $ver ]; then
+ echo "Invalid version."
+ exit
+fi
+
+name="select2"
+js="$name.js"
+mini="$name.min.js"
+css="$name.css"
+release="$name-$ver"
+tag="$ver"
+branch="build-$ver"
+curbranch=`git branch | grep "*" | sed "s/* //"`
+timestamp=$(date)
+tokens="s/@@ver@@/$ver/g;s/\@@timestamp@@/$timestamp/g"
+remote="github"
+
+echo "Updating Version Identifiers"
+
+sed -E -e "s/\"version\": \"([0-9\.]+)\",/\"version\": \"$ver\",/g" -i "" component.json select2.jquery.json
+git add component.json
+git add select2.jquery.json
+git commit -m "modified version identifiers in descriptors for release $ver"
+git push
+
+git branch "$branch"
+git checkout "$branch"
+
+echo "Tokenizing..."
+
+find . -name "$js" | xargs -I{} sed -e "$tokens" -i "" {}
+find . -name "$css" | xargs -I{} sed -e "$tokens" -i "" {}
+sed -e "s/latest/$ver/g" -i "" component.json
+
+git add "$js"
+git add "$css"
+
+echo "Minifying..."
+
+echo "/*" > "$mini"
+cat LICENSE | sed "$tokens" >> "$mini"
+echo "*/" >> "$mini"
+
+curl -s \
+ --data-urlencode "js_code@$js" \
+ http://marijnhaverbeke.nl/uglifyjs \
+ >> "$mini"
+
+git add "$mini"
+
+git commit -m "release $ver"
+
+echo "Tagging..."
+git tag -a "$tag" -m "tagged version $ver"
+git push "$remote" --tags
+
+echo "Cleaning Up..."
+
+git checkout "$curbranch"
+git branch -D "$branch"
+
+echo "Done"
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.css b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.css
new file mode 100644
index 0000000..48c85b4
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.css
@@ -0,0 +1,680 @@
+/*
+Version: 3.4.1 Timestamp: Thu Jun 27 18:02:10 PDT 2013
+*/
+.select2-container {
+ margin: 0;
+ position: relative;
+ display: inline-block;
+ /* inline-block for ie7 */
+ zoom: 1;
+ *display: inline;
+ vertical-align: middle;
+}
+
+.select2-container,
+.select2-drop,
+.select2-search,
+.select2-search input{
+ /*
+ Force border-box so that % widths fit the parent
+ container without overlap because of margin/padding.
+
+ More Info : http://www.quirksmode.org/css/box.html
+ */
+ -webkit-box-sizing: border-box; /* webkit */
+ -khtml-box-sizing: border-box; /* konqueror */
+ -moz-box-sizing: border-box; /* firefox */
+ -ms-box-sizing: border-box; /* ie */
+ box-sizing: border-box; /* css3 */
+}
+
+.select2-container .select2-choice {
+ display: block;
+ height: 26px;
+ padding: 0 0 0 8px;
+ overflow: hidden;
+ position: relative;
+
+ border: 1px solid #aaa;
+ white-space: nowrap;
+ line-height: 26px;
+ color: #444;
+ text-decoration: none;
+
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ background-color: #fff;
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white));
+ background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%);
+ background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%);
+ background-image: -o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%);
+ background-image: -ms-linear-gradient(top, #ffffff 0%, #eeeeee 50%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
+ background-image: linear-gradient(top, #ffffff 0%, #eeeeee 50%);
+}
+
+.select2-container.select2-drop-above .select2-choice {
+ border-bottom-color: #aaa;
+
+ -webkit-border-radius:0 0 4px 4px;
+ -moz-border-radius:0 0 4px 4px;
+ border-radius:0 0 4px 4px;
+
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, white));
+ background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 90%);
+ background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 90%);
+ background-image: -o-linear-gradient(bottom, #eeeeee 0%, white 90%);
+ background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 90%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
+ background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%);
+}
+
+.select2-container.select2-allowclear .select2-choice .select2-chosen {
+ margin-right: 42px;
+}
+
+.select2-container .select2-choice > .select2-chosen {
+ margin-right: 26px;
+ display: block;
+ overflow: hidden;
+
+ white-space: nowrap;
+
+ -ms-text-overflow: ellipsis;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+}
+
+.select2-container .select2-choice abbr {
+ display: none;
+ width: 12px;
+ height: 12px;
+ position: absolute;
+ right: 24px;
+ top: 8px;
+
+ font-size: 1px;
+ text-decoration: none;
+
+ border: 0;
+ background: url('select2.png') right top no-repeat;
+ cursor: pointer;
+ outline: 0;
+}
+
+.select2-container.select2-allowclear .select2-choice abbr {
+ display: inline-block;
+}
+
+.select2-container .select2-choice abbr:hover {
+ background-position: right -11px;
+ cursor: pointer;
+}
+
+.select2-drop-undermask {
+ border: 0;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: 9998;
+ background-color: transparent;
+ filter: alpha(opacity=0);
+}
+
+.select2-drop-mask {
+ border: 0;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: 9998;
+ /* styles required for IE to work */
+ background-color: #fff;
+ opacity: 0;
+ filter: alpha(opacity=0);
+}
+
+.select2-drop {
+ width: 100%;
+ margin-top: -1px;
+ position: absolute;
+ z-index: 9999;
+ top: 100%;
+
+ background: #fff;
+ color: #000;
+ border: 1px solid #aaa;
+ border-top: 0;
+
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+
+ -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+ -moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+ box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+}
+
+.select2-drop-auto-width {
+ border-top: 1px solid #aaa;
+ width: auto;
+}
+
+.select2-drop-auto-width .select2-search {
+ padding-top: 4px;
+}
+
+.select2-drop.select2-drop-above {
+ margin-top: 1px;
+ border-top: 1px solid #aaa;
+ border-bottom: 0;
+
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+
+ -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
+ -moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
+ box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
+}
+
+.select2-drop-active {
+ border: 1px solid #5897fb;
+ border-top: none;
+}
+
+.select2-drop.select2-drop-above.select2-drop-active {
+ border-top: 1px solid #5897fb;
+}
+
+.select2-container .select2-choice .select2-arrow {
+ display: inline-block;
+ width: 18px;
+ height: 100%;
+ position: absolute;
+ right: 0;
+ top: 0;
+
+ border-left: 1px solid #aaa;
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+
+ background: #ccc;
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
+ background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
+ background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
+ background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%);
+ background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
+ background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%);
+}
+
+.select2-container .select2-choice .select2-arrow b {
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: url('select2.png') no-repeat 0 1px;
+}
+
+.select2-search {
+ display: inline-block;
+ width: 100%;
+ min-height: 26px;
+ margin: 0;
+ padding-left: 4px;
+ padding-right: 4px;
+
+ position: relative;
+ z-index: 10000;
+
+ white-space: nowrap;
+}
+
+.select2-search input {
+ width: 100%;
+ height: auto !important;
+ min-height: 26px;
+ padding: 4px 20px 4px 5px;
+ margin: 0;
+
+ outline: 0;
+ font-family: sans-serif;
+ font-size: 1em;
+
+ border: 1px solid #aaa;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+
+ background: #fff url('select2.png') no-repeat 100% -22px;
+ background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
+ background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+ background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+ background: url('select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
+ background: url('select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+ background: url('select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+}
+
+.select2-drop.select2-drop-above .select2-search input {
+ margin-top: 4px;
+}
+
+.select2-search input.select2-active {
+ background: #fff url('select2-spinner.gif') no-repeat 100%;
+ background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
+ background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+ background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+ background: url('select2-spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
+ background: url('select2-spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+ background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+}
+
+.select2-container-active .select2-choice,
+.select2-container-active .select2-choices {
+ border: 1px solid #5897fb;
+ outline: none;
+
+ -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
+ -moz-box-shadow: 0 0 5px rgba(0,0,0,.3);
+ box-shadow: 0 0 5px rgba(0,0,0,.3);
+}
+
+.select2-dropdown-open .select2-choice {
+ border-bottom-color: transparent;
+ -webkit-box-shadow: 0 1px 0 #fff inset;
+ -moz-box-shadow: 0 1px 0 #fff inset;
+ box-shadow: 0 1px 0 #fff inset;
+
+ -webkit-border-bottom-left-radius: 0;
+ -moz-border-radius-bottomleft: 0;
+ border-bottom-left-radius: 0;
+
+ -webkit-border-bottom-right-radius: 0;
+ -moz-border-radius-bottomright: 0;
+ border-bottom-right-radius: 0;
+
+ background-color: #eee;
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee));
+ background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%);
+ background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%);
+ background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%);
+ background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
+ background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
+}
+
+.select2-dropdown-open.select2-drop-above .select2-choice,
+.select2-dropdown-open.select2-drop-above .select2-choices {
+ border: 1px solid #5897fb;
+ border-top-color: transparent;
+
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, white), color-stop(0.5, #eeeeee));
+ background-image: -webkit-linear-gradient(center top, white 0%, #eeeeee 50%);
+ background-image: -moz-linear-gradient(center top, white 0%, #eeeeee 50%);
+ background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
+ background-image: -ms-linear-gradient(bottom, #ffffff 0%,#eeeeee 50%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
+ background-image: linear-gradient(bottom, #ffffff 0%,#eeeeee 50%);
+}
+
+.select2-dropdown-open .select2-choice .select2-arrow {
+ background: transparent;
+ border-left: none;
+ filter: none;
+}
+.select2-dropdown-open .select2-choice .select2-arrow b {
+ background-position: -18px 1px;
+}
+
+/* results */
+.select2-results {
+ max-height: 200px;
+ padding: 0 0 0 4px;
+ margin: 4px 4px 4px 0;
+ position: relative;
+ overflow-x: hidden;
+ overflow-y: auto;
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
+}
+
+.select2-results ul.select2-result-sub {
+ margin: 0;
+ padding-left: 0;
+}
+
+.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px }
+.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px }
+.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px }
+
+.select2-results li {
+ list-style: none;
+ display: list-item;
+ background-image: none;
+}
+
+.select2-results li.select2-result-with-children > .select2-result-label {
+ font-weight: bold;
+}
+
+.select2-results .select2-result-label {
+ padding: 3px 7px 4px;
+ margin: 0;
+ cursor: pointer;
+
+ min-height: 1em;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.select2-results .select2-highlighted {
+ background: #3875d7;
+ color: #fff;
+}
+
+.select2-results li em {
+ background: #feffde;
+ font-style: normal;
+}
+
+.select2-results .select2-highlighted em {
+ background: transparent;
+}
+
+.select2-results .select2-highlighted ul {
+ background: white;
+ color: #000;
+}
+
+
+.select2-results .select2-no-results,
+.select2-results .select2-searching,
+.select2-results .select2-selection-limit {
+ background: #f4f4f4;
+ display: list-item;
+}
+
+/*
+disabled look for disabled choices in the results dropdown
+*/
+.select2-results .select2-disabled.select2-highlighted {
+ color: #666;
+ background: #f4f4f4;
+ display: list-item;
+ cursor: default;
+}
+.select2-results .select2-disabled {
+ background: #f4f4f4;
+ display: list-item;
+ cursor: default;
+}
+
+.select2-results .select2-selected {
+ display: none;
+}
+
+.select2-more-results.select2-active {
+ background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
+}
+
+.select2-more-results {
+ background: #f4f4f4;
+ display: list-item;
+}
+
+/* disabled styles */
+
+.select2-container.select2-container-disabled .select2-choice {
+ background-color: #f4f4f4;
+ background-image: none;
+ border: 1px solid #ddd;
+ cursor: default;
+}
+
+.select2-container.select2-container-disabled .select2-choice .select2-arrow {
+ background-color: #f4f4f4;
+ background-image: none;
+ border-left: 0;
+}
+
+.select2-container.select2-container-disabled .select2-choice abbr {
+ display: none;
+}
+
+
+/* multiselect */
+
+.select2-container-multi .select2-choices {
+ height: auto !important;
+ height: 1%;
+ margin: 0;
+ padding: 0;
+ position: relative;
+
+ border: 1px solid #aaa;
+ cursor: text;
+ overflow: hidden;
+
+ background-color: #fff;
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
+ background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+ background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
+}
+
+.select2-locked {
+ padding: 3px 5px 3px 5px !important;
+}
+
+.select2-container-multi .select2-choices {
+ min-height: 26px;
+}
+
+.select2-container-multi.select2-container-active .select2-choices {
+ border: 1px solid #5897fb;
+ outline: none;
+
+ -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
+ -moz-box-shadow: 0 0 5px rgba(0,0,0,.3);
+ box-shadow: 0 0 5px rgba(0,0,0,.3);
+}
+.select2-container-multi .select2-choices li {
+ float: left;
+ list-style: none;
+}
+.select2-container-multi .select2-choices .select2-search-field {
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
+.select2-container-multi .select2-choices .select2-search-field input {
+ padding: 5px;
+ margin: 1px 0;
+
+ font-family: sans-serif;
+ font-size: 100%;
+ color: #666;
+ outline: 0;
+ border: 0;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ background: transparent !important;
+}
+
+.select2-container-multi .select2-choices .select2-search-field input.select2-active {
+ background: #fff url('select2-spinner.gif') no-repeat 100% !important;
+}
+
+.select2-default {
+ color: #999 !important;
+}
+
+.select2-container-multi .select2-choices .select2-search-choice {
+ padding: 3px 5px 3px 18px;
+ margin: 3px 0 3px 5px;
+ position: relative;
+
+ line-height: 13px;
+ color: #333;
+ cursor: default;
+ border: 1px solid #aaaaaa;
+
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+
+ -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
+ -moz-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
+ box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
+
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ background-color: #e4e4e4;
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0 );
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
+ background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+ background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+}
+.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
+ cursor: default;
+}
+.select2-container-multi .select2-choices .select2-search-choice-focus {
+ background: #d4d4d4;
+}
+
+.select2-search-choice-close {
+ display: block;
+ width: 12px;
+ height: 13px;
+ position: absolute;
+ right: 3px;
+ top: 4px;
+
+ font-size: 1px;
+ outline: none;
+ background: url('select2.png') right top no-repeat;
+}
+
+.select2-container-multi .select2-search-choice-close {
+ left: 3px;
+}
+
+.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
+ background-position: right -11px;
+}
+.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
+ background-position: right -11px;
+}
+
+/* disabled styles */
+.select2-container-multi.select2-container-disabled .select2-choices{
+ background-color: #f4f4f4;
+ background-image: none;
+ border: 1px solid #ddd;
+ cursor: default;
+}
+
+.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
+ padding: 3px 5px 3px 5px;
+ border: 1px solid #ddd;
+ background-image: none;
+ background-color: #f4f4f4;
+}
+
+.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
+ background:none;
+}
+/* end multiselect */
+
+
+.select2-result-selectable .select2-match,
+.select2-result-unselectable .select2-match {
+ text-decoration: underline;
+}
+
+.select2-offscreen, .select2-offscreen:focus {
+ clip: rect(0 0 0 0);
+ width: 1px;
+ height: 1px;
+ border: 0;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ position: absolute;
+ outline: 0;
+ left: 0px;
+}
+
+.select2-display-none {
+ display: none;
+}
+
+.select2-measure-scrollbar {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+}
+/* Retina-ize icons */
+
+@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) {
+ .select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice .select2-arrow b {
+ background-image: url('select2x2.png') !important;
+ background-repeat: no-repeat !important;
+ background-size: 60px 40px !important;
+ }
+ .select2-search input {
+ background-position: 100% -21px !important;
+ }
+}
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.jquery.json b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.jquery.json
new file mode 100644
index 0000000..af9cab7
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.jquery.json
@@ -0,0 +1,36 @@
+{
+ "name": "select2",
+ "title": "Select2",
+ "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
+ "keywords": [
+ "select",
+ "autocomplete",
+ "typeahead",
+ "dropdown",
+ "multiselect",
+ "tag",
+ "tagging"
+ ],
+ "version": "3.4.1",
+ "author": {
+ "name": "Igor Vaynberg",
+ "url": "https://github.com/ivaynberg"
+ },
+ "licenses": [
+ {
+ "type": "Apache",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
+ },
+ {
+ "type": "GPL v2",
+ "url": "http://www.gnu.org/licenses/gpl-2.0.html"
+ }
+ ],
+ "bugs": "https://github.com/ivaynberg/select2/issues",
+ "homepage": "http://ivaynberg.github.com/select2",
+ "docs": "http://ivaynberg.github.com/select2/",
+ "download": "https://github.com/ivaynberg/select2/tags",
+ "dependencies": {
+ "jquery": ">=1.7.1"
+ }
+}
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.js
new file mode 100644
index 0000000..a496071
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.js
@@ -0,0 +1,3137 @@
+/*
+Copyright 2012 Igor Vaynberg
+
+Version: 3.4.1 Timestamp: Thu Jun 27 18:02:10 PDT 2013
+
+This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
+General Public License version 2 (the "GPL License"). You may choose either license to govern your
+use of this software only upon the condition that you accept all of the terms of either the Apache
+License or the GPL License.
+
+You may obtain a copy of the Apache License and the GPL License at:
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ http://www.gnu.org/licenses/gpl-2.0.html
+
+Unless required by applicable law or agreed to in writing, software distributed under the
+Apache License or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for
+the specific language governing permissions and limitations under the Apache License and the GPL License.
+*/
+(function ($) {
+ if(typeof $.fn.each2 == "undefined") {
+ $.fn.extend({
+ /*
+ * 4-10 times faster .each replacement
+ * use it carefully, as it overrides jQuery context of element on each iteration
+ */
+ each2 : function (c) {
+ var j = $([0]), i = -1, l = this.length;
+ while (
+ ++i < l
+ && (j.context = j[0] = this[i])
+ && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
+ );
+ return this;
+ }
+ });
+ }
+})(jQuery);
+
+(function ($, undefined) {
+ "use strict";
+ /*global document, window, jQuery, console */
+
+ if (window.Select2 !== undefined) {
+ return;
+ }
+
+ var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
+ lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
+
+ KEY = {
+ TAB: 9,
+ ENTER: 13,
+ ESC: 27,
+ SPACE: 32,
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40,
+ SHIFT: 16,
+ CTRL: 17,
+ ALT: 18,
+ PAGE_UP: 33,
+ PAGE_DOWN: 34,
+ HOME: 36,
+ END: 35,
+ BACKSPACE: 8,
+ DELETE: 46,
+ isArrow: function (k) {
+ k = k.which ? k.which : k;
+ switch (k) {
+ case KEY.LEFT:
+ case KEY.RIGHT:
+ case KEY.UP:
+ case KEY.DOWN:
+ return true;
+ }
+ return false;
+ },
+ isControl: function (e) {
+ var k = e.which;
+ switch (k) {
+ case KEY.SHIFT:
+ case KEY.CTRL:
+ case KEY.ALT:
+ return true;
+ }
+
+ if (e.metaKey) return true;
+
+ return false;
+ },
+ isFunctionKey: function (k) {
+ k = k.which ? k.which : k;
+ return k >= 112 && k <= 123;
+ }
+ },
+ MEASURE_SCROLLBAR_TEMPLATE = "<div class='select2-measure-scrollbar'></div>";
+
+ $document = $(document);
+
+ nextUid=(function() { var counter=1; return function() { return counter++; }; }());
+
+ function indexOf(value, array) {
+ var i = 0, l = array.length;
+ for (; i < l; i = i + 1) {
+ if (equal(value, array[i])) return i;
+ }
+ return -1;
+ }
+
+ function measureScrollbar () {
+ var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
+ $template.appendTo('body');
+
+ var dim = {
+ width: $template.width() - $template[0].clientWidth,
+ height: $template.height() - $template[0].clientHeight
+ };
+ $template.remove();
+
+ return dim;
+ }
+
+ /**
+ * Compares equality of a and b
+ * @param a
+ * @param b
+ */
+ function equal(a, b) {
+ if (a === b) return true;
+ if (a === undefined || b === undefined) return false;
+ if (a === null || b === null) return false;
+ // Check whether 'a' or 'b' is a string (primitive or object).
+ // The concatenation of an empty string (+'') converts its argument to a string's primitive.
+ if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
+ if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
+ return false;
+ }
+
+ /**
+ * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
+ * strings
+ * @param string
+ * @param separator
+ */
+ function splitVal(string, separator) {
+ var val, i, l;
+ if (string === null || string.length < 1) return [];
+ val = string.split(separator);
+ for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
+ return val;
+ }
+
+ function getSideBorderPadding(element) {
+ return element.outerWidth(false) - element.width();
+ }
+
+ function installKeyUpChangeEvent(element) {
+ var key="keyup-change-value";
+ element.on("keydown", function () {
+ if ($.data(element, key) === undefined) {
+ $.data(element, key, element.val());
+ }
+ });
+ element.on("keyup", function () {
+ var val= $.data(element, key);
+ if (val !== undefined && element.val() !== val) {
+ $.removeData(element, key);
+ element.trigger("keyup-change");
+ }
+ });
+ }
+
+ $document.on("mousemove", function (e) {
+ lastMousePosition.x = e.pageX;
+ lastMousePosition.y = e.pageY;
+ });
+
+ /**
+ * filters mouse events so an event is fired only if the mouse moved.
+ *
+ * filters out mouse events that occur when mouse is stationary but
+ * the elements under the pointer are scrolled.
+ */
+ function installFilteredMouseMove(element) {
+ element.on("mousemove", function (e) {
+ var lastpos = lastMousePosition;
+ if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
+ $(e.target).trigger("mousemove-filtered", e);
+ }
+ });
+ }
+
+ /**
+ * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
+ * within the last quietMillis milliseconds.
+ *
+ * @param quietMillis number of milliseconds to wait before invoking fn
+ * @param fn function to be debounced
+ * @param ctx object to be used as this reference within fn
+ * @return debounced version of fn
+ */
+ function debounce(quietMillis, fn, ctx) {
+ ctx = ctx || undefined;
+ var timeout;
+ return function () {
+ var args = arguments;
+ window.clearTimeout(timeout);
+ timeout = window.setTimeout(function() {
+ fn.apply(ctx, args);
+ }, quietMillis);
+ };
+ }
+
+ /**
+ * A simple implementation of a thunk
+ * @param formula function used to lazily initialize the thunk
+ * @return {Function}
+ */
+ function thunk(formula) {
+ var evaluated = false,
+ value;
+ return function() {
+ if (evaluated === false) { value = formula(); evaluated = true; }
+ return value;
+ };
+ };
+
+ function installDebouncedScroll(threshold, element) {
+ var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
+ element.on("scroll", function (e) {
+ if (indexOf(e.target, element.get()) >= 0) notify(e);
+ });
+ }
+
+ function focus($el) {
+ if ($el[0] === document.activeElement) return;
+
+ /* set the focus in a 0 timeout - that way the focus is set after the processing
+ of the current event has finished - which seems like the only reliable way
+ to set focus */
+ window.setTimeout(function() {
+ var el=$el[0], pos=$el.val().length, range;
+
+ $el.focus();
+
+ /* make sure el received focus so we do not error out when trying to manipulate the caret.
+ sometimes modals or others listeners may steal it after its set */
+ if ($el.is(":visible") && el === document.activeElement) {
+
+ /* after the focus is set move the caret to the end, necessary when we val()
+ just before setting focus */
+ if(el.setSelectionRange)
+ {
+ el.setSelectionRange(pos, pos);
+ }
+ else if (el.createTextRange) {
+ range = el.createTextRange();
+ range.collapse(false);
+ range.select();
+ }
+ }
+ }, 0);
+ }
+
+ function getCursorInfo(el) {
+ el = $(el)[0];
+ var offset = 0;
+ var length = 0;
+ if ('selectionStart' in el) {
+ offset = el.selectionStart;
+ length = el.selectionEnd - offset;
+ } else if ('selection' in document) {
+ el.focus();
+ var sel = document.selection.createRange();
+ length = document.selection.createRange().text.length;
+ sel.moveStart('character', -el.value.length);
+ offset = sel.text.length - length;
+ }
+ return { offset: offset, length: length };
+ }
+
+ function killEvent(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ function killEventImmediately(event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+
+ function measureTextWidth(e) {
+ if (!sizer){
+ var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
+ sizer = $(document.createElement("div")).css({
+ position: "absolute",
+ left: "-10000px",
+ top: "-10000px",
+ display: "none",
+ fontSize: style.fontSize,
+ fontFamily: style.fontFamily,
+ fontStyle: style.fontStyle,
+ fontWeight: style.fontWeight,
+ letterSpacing: style.letterSpacing,
+ textTransform: style.textTransform,
+ whiteSpace: "nowrap"
+ });
+ sizer.attr("class","select2-sizer");
+ $("body").append(sizer);
+ }
+ sizer.text(e.val());
+ return sizer.width();
+ }
+
+ function syncCssClasses(dest, src, adapter) {
+ var classes, replacements = [], adapted;
+
+ classes = dest.attr("class");
+ if (classes) {
+ classes = '' + classes; // for IE which returns object
+ $(classes.split(" ")).each2(function() {
+ if (this.indexOf("select2-") === 0) {
+ replacements.push(this);
+ }
+ });
+ }
+ classes = src.attr("class");
+ if (classes) {
+ classes = '' + classes; // for IE which returns object
+ $(classes.split(" ")).each2(function() {
+ if (this.indexOf("select2-") !== 0) {
+ adapted = adapter(this);
+ if (adapted) {
+ replacements.push(this);
+ }
+ }
+ });
+ }
+ dest.attr("class", replacements.join(" "));
+ }
+
+
+ function markMatch(text, term, markup, escapeMarkup) {
+ var match=text.toUpperCase().indexOf(term.toUpperCase()),
+ tl=term.length;
+
+ if (match<0) {
+ markup.push(escapeMarkup(text));
+ return;
+ }
+
+ markup.push(escapeMarkup(text.substring(0, match)));
+ markup.push("<span class='select2-match'>");
+ markup.push(escapeMarkup(text.substring(match, match + tl)));
+ markup.push("</span>");
+ markup.push(escapeMarkup(text.substring(match + tl, text.length)));
+ }
+
+ function defaultEscapeMarkup(markup) {
+ var replace_map = {
+ '\\': '\',
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ "/": '/'
+ };
+
+ return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+ return replace_map[match];
+ });
+ }
+
+ /**
+ * Produces an ajax-based query function
+ *
+ * @param options object containing configuration paramters
+ * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
+ * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
+ * @param options.url url for the data
+ * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
+ * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified
+ * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
+ * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2.
+ * The expected format is an object containing the following keys:
+ * results array of objects that will be used as choices
+ * more (optional) boolean indicating whether there are more results available
+ * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
+ */
+ function ajax(options) {
+ var timeout, // current scheduled but not yet executed request
+ requestSequence = 0, // sequence used to drop out-of-order responses
+ handler = null,
+ quietMillis = options.quietMillis || 100,
+ ajaxUrl = options.url,
+ self = this;
+
+ return function (query) {
+ window.clearTimeout(timeout);
+ timeout = window.setTimeout(function () {
+ requestSequence += 1; // increment the sequence
+ var requestNumber = requestSequence, // this request's sequence number
+ data = options.data, // ajax data function
+ url = ajaxUrl, // ajax url string or function
+ transport = options.transport || $.fn.select2.ajaxDefaults.transport,
+ // deprecated - to be removed in 4.0 - use params instead
+ deprecated = {
+ type: options.type || 'GET', // set type of request (GET or POST)
+ cache: options.cache || false,
+ jsonpCallback: options.jsonpCallback||undefined,
+ dataType: options.dataType||"json"
+ },
+ params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated);
+
+ data = data ? data.call(self, query.term, query.page, query.context) : null;
+ url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url;
+
+ if (handler) { handler.abort(); }
+
+ if (options.params) {
+ if ($.isFunction(options.params)) {
+ $.extend(params, options.params.call(self));
+ } else {
+ $.extend(params, options.params);
+ }
+ }
+
+ $.extend(params, {
+ url: url,
+ dataType: options.dataType,
+ data: data,
+ success: function (data) {
+ if (requestNumber < requestSequence) {
+ return;
+ }
+ // TODO - replace query.page with query so users have access to term, page, etc.
+ var results = options.results(data, query.page);
+ query.callback(results);
+ }
+ });
+ handler = transport.call(self, params);
+ }, quietMillis);
+ };
+ }
+
+ /**
+ * Produces a query function that works with a local array
+ *
+ * @param options object containing configuration parameters. The options parameter can either be an array or an
+ * object.
+ *
+ * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
+ *
+ * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
+ * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
+ * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
+ * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
+ * the text.
+ */
+ function local(options) {
+ var data = options, // data elements
+ dataText,
+ tmp,
+ text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
+
+ if ($.isArray(data)) {
+ tmp = data;
+ data = { results: tmp };
+ }
+
+ if ($.isFunction(data) === false) {
+ tmp = data;
+ data = function() { return tmp; };
+ }
+
+ var dataItem = data();
+ if (dataItem.text) {
+ text = dataItem.text;
+ // if text is not a function we assume it to be a key name
+ if (!$.isFunction(text)) {
+ dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
+ text = function (item) { return item[dataText]; };
+ }
+ }
+
+ return function (query) {
+ var t = query.term, filtered = { results: [] }, process;
+ if (t === "") {
+ query.callback(data());
+ return;
+ }
+
+ process = function(datum, collection) {
+ var group, attr;
+ datum = datum[0];
+ if (datum.children) {
+ group = {};
+ for (attr in datum) {
+ if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
+ }
+ group.children=[];
+ $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
+ if (group.children.length || query.matcher(t, text(group), datum)) {
+ collection.push(group);
+ }
+ } else {
+ if (query.matcher(t, text(datum), datum)) {
+ collection.push(datum);
+ }
+ }
+ };
+
+ $(data().results).each2(function(i, datum) { process(datum, filtered.results); });
+ query.callback(filtered);
+ };
+ }
+
+ // TODO javadoc
+ function tags(data) {
+ var isFunc = $.isFunction(data);
+ return function (query) {
+ var t = query.term, filtered = {results: []};
+ $(isFunc ? data() : data).each(function () {
+ var isObject = this.text !== undefined,
+ text = isObject ? this.text : this;
+ if (t === "" || query.matcher(t, text)) {
+ filtered.results.push(isObject ? this : {id: this, text: this});
+ }
+ });
+ query.callback(filtered);
+ };
+ }
+
+ /**
+ * Checks if the formatter function should be used.
+ *
+ * Throws an error if it is not a function. Returns true if it should be used,
+ * false if no formatting should be performed.
+ *
+ * @param formatter
+ */
+ function checkFormatter(formatter, formatterName) {
+ if ($.isFunction(formatter)) return true;
+ if (!formatter) return false;
+ throw new Error(formatterName +" must be a function or a falsy value");
+ }
+
+ function evaluate(val) {
+ return $.isFunction(val) ? val() : val;
+ }
+
+ function countResults(results) {
+ var count = 0;
+ $.each(results, function(i, item) {
+ if (item.children) {
+ count += countResults(item.children);
+ } else {
+ count++;
+ }
+ });
+ return count;
+ }
+
+ /**
+ * Default tokenizer. This function uses breaks the input on substring match of any string from the
+ * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
+ * two options have to be defined in order for the tokenizer to work.
+ *
+ * @param input text user has typed so far or pasted into the search field
+ * @param selection currently selected choices
+ * @param selectCallback function(choice) callback tho add the choice to selection
+ * @param opts select2's opts
+ * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
+ */
+ function defaultTokenizer(input, selection, selectCallback, opts) {
+ var original = input, // store the original so we can compare and know if we need to tell the search to update its text
+ dupe = false, // check for whether a token we extracted represents a duplicate selected choice
+ token, // token
+ index, // position at which the separator was found
+ i, l, // looping variables
+ separator; // the matched separator
+
+ if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
+
+ while (true) {
+ index = -1;
+
+ for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
+ separator = opts.tokenSeparators[i];
+ index = input.indexOf(separator);
+ if (index >= 0) break;
+ }
+
+ if (index < 0) break; // did not find any token separator in the input string, bail
+
+ token = input.substring(0, index);
+ input = input.substring(index + separator.length);
+
+ if (token.length > 0) {
+ token = opts.createSearchChoice.call(this, token, selection);
+ if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
+ dupe = false;
+ for (i = 0, l = selection.length; i < l; i++) {
+ if (equal(opts.id(token), opts.id(selection[i]))) {
+ dupe = true; break;
+ }
+ }
+
+ if (!dupe) selectCallback(token);
+ }
+ }
+ }
+
+ if (original!==input) return input;
+ }
+
+ /**
+ * Creates a new class
+ *
+ * @param superClass
+ * @param methods
+ */
+ function clazz(SuperClass, methods) {
+ var constructor = function () {};
+ constructor.prototype = new SuperClass;
+ constructor.prototype.constructor = constructor;
+ constructor.prototype.parent = SuperClass.prototype;
+ constructor.prototype = $.extend(constructor.prototype, methods);
+ return constructor;
+ }
+
+ AbstractSelect2 = clazz(Object, {
+
+ // abstract
+ bind: function (func) {
+ var self = this;
+ return function () {
+ func.apply(self, arguments);
+ };
+ },
+
+ // abstract
+ init: function (opts) {
+ var results, search, resultsSelector = ".select2-results", disabled, readonly;
+
+ // prepare options
+ this.opts = opts = this.prepareOpts(opts);
+
+ this.id=opts.id;
+
+ // destroy if called on an existing component
+ if (opts.element.data("select2") !== undefined &&
+ opts.element.data("select2") !== null) {
+ opts.element.data("select2").destroy();
+ }
+
+ this.container = this.createContainer();
+
+ this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
+ this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
+ this.container.attr("id", this.containerId);
+
+ // cache the body so future lookups are cheap
+ this.body = thunk(function() { return opts.element.closest("body"); });
+
+ syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
+
+ this.container.css(evaluate(opts.containerCss));
+ this.container.addClass(evaluate(opts.containerCssClass));
+
+ this.elementTabIndex = this.opts.element.attr("tabindex");
+
+ // swap container for the element
+ this.opts.element
+ .data("select2", this)
+ .attr("tabindex", "-1")
+ .before(this.container);
+ this.container.data("select2", this);
+
+ this.dropdown = this.container.find(".select2-drop");
+ this.dropdown.addClass(evaluate(opts.dropdownCssClass));
+ this.dropdown.data("select2", this);
+
+ this.results = results = this.container.find(resultsSelector);
+ this.search = search = this.container.find("input.select2-input");
+
+ this.resultsPage = 0;
+ this.context = null;
+
+ // initialize the container
+ this.initContainer();
+
+ installFilteredMouseMove(this.results);
+ this.dropdown.on("mousemove-filtered touchstart touchmove touchend", resultsSelector, this.bind(this.highlightUnderEvent));
+
+ installDebouncedScroll(80, this.results);
+ this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
+
+ // do not propagate change event from the search field out of the component
+ $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
+ $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
+
+ // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
+ if ($.fn.mousewheel) {
+ results.mousewheel(function (e, delta, deltaX, deltaY) {
+ var top = results.scrollTop(), height;
+ if (deltaY > 0 && top - deltaY <= 0) {
+ results.scrollTop(0);
+ killEvent(e);
+ } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
+ results.scrollTop(results.get(0).scrollHeight - results.height());
+ killEvent(e);
+ }
+ });
+ }
+
+ installKeyUpChangeEvent(search);
+ search.on("keyup-change input paste", this.bind(this.updateResults));
+ search.on("focus", function () { search.addClass("select2-focused"); });
+ search.on("blur", function () { search.removeClass("select2-focused");});
+
+ this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
+ if ($(e.target).closest(".select2-result-selectable").length > 0) {
+ this.highlightUnderEvent(e);
+ this.selectHighlighted(e);
+ }
+ }));
+
+ // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
+ // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
+ // dom it will trigger the popup close, which is not what we want
+ this.dropdown.on("click mouseup mousedown", function (e) { e.stopPropagation(); });
+
+ if ($.isFunction(this.opts.initSelection)) {
+ // initialize selection based on the current value of the source element
+ this.initSelection();
+
+ // if the user has provided a function that can set selection based on the value of the source element
+ // we monitor the change event on the element and trigger it, allowing for two way synchronization
+ this.monitorSource();
+ }
+
+ if (opts.maximumInputLength !== null) {
+ this.search.attr("maxlength", opts.maximumInputLength);
+ }
+
+ var disabled = opts.element.prop("disabled");
+ if (disabled === undefined) disabled = false;
+ this.enable(!disabled);
+
+ var readonly = opts.element.prop("readonly");
+ if (readonly === undefined) readonly = false;
+ this.readonly(readonly);
+
+ // Calculate size of scrollbar
+ scrollBarDimensions = scrollBarDimensions || measureScrollbar();
+
+ this.autofocus = opts.element.prop("autofocus")
+ opts.element.prop("autofocus", false);
+ if (this.autofocus) this.focus();
+ },
+
+ // abstract
+ destroy: function () {
+ var element=this.opts.element, select2 = element.data("select2");
+
+ if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
+
+ if (select2 !== undefined) {
+ select2.container.remove();
+ select2.dropdown.remove();
+ element
+ .removeClass("select2-offscreen")
+ .removeData("select2")
+ .off(".select2")
+ .prop("autofocus", this.autofocus || false);
+ if (this.elementTabIndex) {
+ element.attr({tabindex: this.elementTabIndex});
+ } else {
+ element.removeAttr("tabindex");
+ }
+ element.show();
+ }
+ },
+
+ // abstract
+ optionToData: function(element) {
+ if (element.is("option")) {
+ return {
+ id:element.prop("value"),
+ text:element.text(),
+ element: element.get(),
+ css: element.attr("class"),
+ disabled: element.prop("disabled"),
+ locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true)
+ };
+ } else if (element.is("optgroup")) {
+ return {
+ text:element.attr("label"),
+ children:[],
+ element: element.get(),
+ css: element.attr("class")
+ };
+ }
+ },
+
+ // abstract
+ prepareOpts: function (opts) {
+ var element, select, idKey, ajaxUrl, self = this;
+
+ element = opts.element;
+
+ if (element.get(0).tagName.toLowerCase() === "select") {
+ this.select = select = opts.element;
+ }
+
+ if (select) {
+ // these options are not allowed when attached to a select because they are picked up off the element itself
+ $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
+ if (this in opts) {
+ throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
+ }
+ });
+ }
+
+ opts = $.extend({}, {
+ populateResults: function(container, results, query) {
+ var populate, data, result, children, id=this.opts.id;
+
+ populate=function(results, container, depth) {
+
+ var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
+
+ results = opts.sortResults(results, container, query);
+
+ for (i = 0, l = results.length; i < l; i = i + 1) {
+
+ result=results[i];
+
+ disabled = (result.disabled === true);
+ selectable = (!disabled) && (id(result) !== undefined);
+
+ compound=result.children && result.children.length > 0;
+
+ node=$("<li></li>");
+ node.addClass("select2-results-dept-"+depth);
+ node.addClass("select2-result");
+ node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
+ if (disabled) { node.addClass("select2-disabled"); }
+ if (compound) { node.addClass("select2-result-with-children"); }
+ node.addClass(self.opts.formatResultCssClass(result));
+
+ label=$(document.createElement("div"));
+ label.addClass("select2-result-label");
+
+ formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
+ if (formatted!==undefined) {
+ label.html(formatted);
+ }
+
+ node.append(label);
+
+ if (compound) {
+
+ innerContainer=$("<ul></ul>");
+ innerContainer.addClass("select2-result-sub");
+ populate(result.children, innerContainer, depth+1);
+ node.append(innerContainer);
+ }
+
+ node.data("select2-data", result);
+ container.append(node);
+ }
+ };
+
+ populate(results, container, 0);
+ }
+ }, $.fn.select2.defaults, opts);
+
+ if (typeof(opts.id) !== "function") {
+ idKey = opts.id;
+ opts.id = function (e) { return e[idKey]; };
+ }
+
+ if ($.isArray(opts.element.data("select2Tags"))) {
+ if ("tags" in opts) {
+ throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
+ }
+ opts.tags=opts.element.data("select2Tags");
+ }
+
+ if (select) {
+ opts.query = this.bind(function (query) {
+ var data = { results: [], more: false },
+ term = query.term,
+ children, placeholderOption, process;
+
+ process=function(element, collection) {
+ var group;
+ if (element.is("option")) {
+ if (query.matcher(term, element.text(), element)) {
+ collection.push(self.optionToData(element));
+ }
+ } else if (element.is("optgroup")) {
+ group=self.optionToData(element);
+ element.children().each2(function(i, elm) { process(elm, group.children); });
+ if (group.children.length>0) {
+ collection.push(group);
+ }
+ }
+ };
+
+ children=element.children();
+
+ // ignore the placeholder option if there is one
+ if (this.getPlaceholder() !== undefined && children.length > 0) {
+ placeholderOption = this.getPlaceholderOption();
+ if (placeholderOption) {
+ children=children.not(placeholderOption);
+ }
+ }
+
+ children.each2(function(i, elm) { process(elm, data.results); });
+
+ query.callback(data);
+ });
+ // this is needed because inside val() we construct choices from options and there id is hardcoded
+ opts.id=function(e) { return e.id; };
+ opts.formatResultCssClass = function(data) { return data.css; };
+ } else {
+ if (!("query" in opts)) {
+
+ if ("ajax" in opts) {
+ ajaxUrl = opts.element.data("ajax-url");
+ if (ajaxUrl && ajaxUrl.length > 0) {
+ opts.ajax.url = ajaxUrl;
+ }
+ opts.query = ajax.call(opts.element, opts.ajax);
+ } else if ("data" in opts) {
+ opts.query = local(opts.data);
+ } else if ("tags" in opts) {
+ opts.query = tags(opts.tags);
+ if (opts.createSearchChoice === undefined) {
+ opts.createSearchChoice = function (term) { return {id: term, text: term}; };
+ }
+ if (opts.initSelection === undefined) {
+ opts.initSelection = function (element, callback) {
+ var data = [];
+ $(splitVal(element.val(), opts.separator)).each(function () {
+ var id = this, text = this, tags=opts.tags;
+ if ($.isFunction(tags)) tags=tags();
+ $(tags).each(function() { if (equal(this.id, id)) { text = this.text; return false; } });
+ data.push({id: id, text: text});
+ });
+
+ callback(data);
+ };
+ }
+ }
+ }
+ }
+ if (typeof(opts.query) !== "function") {
+ throw "query function not defined for Select2 " + opts.element.attr("id");
+ }
+
+ return opts;
+ },
+
+ /**
+ * Monitor the original element for changes and update select2 accordingly
+ */
+ // abstract
+ monitorSource: function () {
+ var el = this.opts.element, sync;
+
+ el.on("change.select2", this.bind(function (e) {
+ if (this.opts.element.data("select2-change-triggered") !== true) {
+ this.initSelection();
+ }
+ }));
+
+ sync = this.bind(function () {
+
+ var enabled, readonly, self = this;
+
+ // sync enabled state
+ var disabled = el.prop("disabled");
+ if (disabled === undefined) disabled = false;
+ this.enable(!disabled);
+
+ var readonly = el.prop("readonly");
+ if (readonly === undefined) readonly = false;
+ this.readonly(readonly);
+
+ syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
+ this.container.addClass(evaluate(this.opts.containerCssClass));
+
+ syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
+ this.dropdown.addClass(evaluate(this.opts.dropdownCssClass));
+
+ });
+
+ // mozilla and IE
+ el.on("propertychange.select2 DOMAttrModified.select2", sync);
+
+
+ // hold onto a reference of the callback to work around a chromium bug
+ if (this.mutationCallback === undefined) {
+ this.mutationCallback = function (mutations) {
+ mutations.forEach(sync);
+ }
+ }
+
+ // safari and chrome
+ if (typeof WebKitMutationObserver !== "undefined") {
+ if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
+ this.propertyObserver = new WebKitMutationObserver(this.mutationCallback);
+ this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
+ }
+ },
+
+ // abstract
+ triggerSelect: function(data) {
+ var evt = $.Event("select2-selecting", { val: this.id(data), object: data });
+ this.opts.element.trigger(evt);
+ return !evt.isDefaultPrevented();
+ },
+
+ /**
+ * Triggers the change event on the source element
+ */
+ // abstract
+ triggerChange: function (details) {
+
+ details = details || {};
+ details= $.extend({}, details, { type: "change", val: this.val() });
+ // prevents recursive triggering
+ this.opts.element.data("select2-change-triggered", true);
+ this.opts.element.trigger(details);
+ this.opts.element.data("select2-change-triggered", false);
+
+ // some validation frameworks ignore the change event and listen instead to keyup, click for selects
+ // so here we trigger the click event manually
+ this.opts.element.click();
+
+ // ValidationEngine ignorea the change event and listens instead to blur
+ // so here we trigger the blur event manually if so desired
+ if (this.opts.blurOnChange)
+ this.opts.element.blur();
+ },
+
+ //abstract
+ isInterfaceEnabled: function()
+ {
+ return this.enabledInterface === true;
+ },
+
+ // abstract
+ enableInterface: function() {
+ var enabled = this._enabled && !this._readonly,
+ disabled = !enabled;
+
+ if (enabled === this.enabledInterface) return false;
+
+ this.container.toggleClass("select2-container-disabled", disabled);
+ this.close();
+ this.enabledInterface = enabled;
+
+ return true;
+ },
+
+ // abstract
+ enable: function(enabled) {
+ if (enabled === undefined) enabled = true;
+ if (this._enabled === enabled) return false;
+ this._enabled = enabled;
+
+ this.opts.element.prop("disabled", !enabled);
+ this.enableInterface();
+ return true;
+ },
+
+ // abstract
+ readonly: function(enabled) {
+ if (enabled === undefined) enabled = false;
+ if (this._readonly === enabled) return false;
+ this._readonly = enabled;
+
+ this.opts.element.prop("readonly", enabled);
+ this.enableInterface();
+ return true;
+ },
+
+ // abstract
+ opened: function () {
+ return this.container.hasClass("select2-dropdown-open");
+ },
+
+ // abstract
+ positionDropdown: function() {
+ var $dropdown = this.dropdown,
+ offset = this.container.offset(),
+ height = this.container.outerHeight(false),
+ width = this.container.outerWidth(false),
+ dropHeight = $dropdown.outerHeight(false),
+ viewPortRight = $(window).scrollLeft() + $(window).width(),
+ viewportBottom = $(window).scrollTop() + $(window).height(),
+ dropTop = offset.top + height,
+ dropLeft = offset.left,
+ enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
+ enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(),
+ dropWidth = $dropdown.outerWidth(false),
+ enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
+ aboveNow = $dropdown.hasClass("select2-drop-above"),
+ bodyOffset,
+ above,
+ css,
+ resultsListNode;
+
+ if (this.opts.dropdownAutoWidth) {
+ resultsListNode = $('.select2-results', $dropdown)[0];
+ $dropdown.addClass('select2-drop-auto-width');
+ $dropdown.css('width', '');
+ // Add scrollbar width to dropdown if vertical scrollbar is present
+ dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
+ dropWidth > width ? width = dropWidth : dropWidth = width;
+ enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
+ }
+ else {
+ this.container.removeClass('select2-drop-auto-width');
+ }
+
+ //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
+ //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove);
+
+ // fix positioning when body has an offset and is not position: static
+ if (this.body().css('position') !== 'static') {
+ bodyOffset = this.body().offset();
+ dropTop -= bodyOffset.top;
+ dropLeft -= bodyOffset.left;
+ }
+
+ // always prefer the current above/below alignment, unless there is not enough room
+ if (aboveNow) {
+ above = true;
+ if (!enoughRoomAbove && enoughRoomBelow) above = false;
+ } else {
+ above = false;
+ if (!enoughRoomBelow && enoughRoomAbove) above = true;
+ }
+
+ if (!enoughRoomOnRight) {
+ dropLeft = offset.left + width - dropWidth;
+ }
+
+ if (above) {
+ dropTop = offset.top - dropHeight;
+ this.container.addClass("select2-drop-above");
+ $dropdown.addClass("select2-drop-above");
+ }
+ else {
+ this.container.removeClass("select2-drop-above");
+ $dropdown.removeClass("select2-drop-above");
+ }
+
+ css = $.extend({
+ top: dropTop,
+ left: dropLeft,
+ width: width
+ }, evaluate(this.opts.dropdownCss));
+
+ $dropdown.css(css);
+ },
+
+ // abstract
+ shouldOpen: function() {
+ var event;
+
+ if (this.opened()) return false;
+
+ if (this._enabled === false || this._readonly === true) return false;
+
+ event = $.Event("select2-opening");
+ this.opts.element.trigger(event);
+ return !event.isDefaultPrevented();
+ },
+
+ // abstract
+ clearDropdownAlignmentPreference: function() {
+ // clear the classes used to figure out the preference of where the dropdown should be opened
+ this.container.removeClass("select2-drop-above");
+ this.dropdown.removeClass("select2-drop-above");
+ },
+
+ /**
+ * Opens the dropdown
+ *
+ * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
+ * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
+ */
+ // abstract
+ open: function () {
+
+ if (!this.shouldOpen()) return false;
+
+ this.opening();
+
+ return true;
+ },
+
+ /**
+ * Performs the opening of the dropdown
+ */
+ // abstract
+ opening: function() {
+ var cid = this.containerId,
+ scroll = "scroll." + cid,
+ resize = "resize."+cid,
+ orient = "orientationchange."+cid,
+ mask, maskCss;
+
+ this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
+
+ this.clearDropdownAlignmentPreference();
+
+ if(this.dropdown[0] !== this.body().children().last()[0]) {
+ this.dropdown.detach().appendTo(this.body());
+ }
+
+ // create the dropdown mask if doesnt already exist
+ mask = $("#select2-drop-mask");
+ if (mask.length == 0) {
+ mask = $(document.createElement("div"));
+ mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
+ mask.hide();
+ mask.appendTo(this.body());
+ mask.on("mousedown touchstart click", function (e) {
+ var dropdown = $("#select2-drop"), self;
+ if (dropdown.length > 0) {
+ self=dropdown.data("select2");
+ if (self.opts.selectOnBlur) {
+ self.selectHighlighted({noFocus: true});
+ }
+ self.close();
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ });
+ }
+
+ // ensure the mask is always right before the dropdown
+ if (this.dropdown.prev()[0] !== mask[0]) {
+ this.dropdown.before(mask);
+ }
+
+ // move the global id to the correct dropdown
+ $("#select2-drop").removeAttr("id");
+ this.dropdown.attr("id", "select2-drop");
+
+ // show the elements
+ maskCss=_makeMaskCss();
+
+ mask.css(maskCss).show();
+
+ this.dropdown.show();
+ this.positionDropdown();
+
+ this.dropdown.addClass("select2-drop-active");
+
+ // attach listeners to events that can change the position of the container and thus require
+ // the position of the dropdown to be updated as well so it does not come unglued from the container
+ var that = this;
+ this.container.parents().add(window).each(function () {
+ $(this).on(resize+" "+scroll+" "+orient, function (e) {
+ var maskCss=_makeMaskCss();
+ $("#select2-drop-mask").css(maskCss);
+ that.positionDropdown();
+ });
+ });
+
+ function _makeMaskCss() {
+ return {
+ width : Math.max(document.documentElement.scrollWidth, $(window).width()),
+ height : Math.max(document.documentElement.scrollHeight, $(window).height())
+ }
+ }
+ },
+
+ // abstract
+ close: function () {
+ if (!this.opened()) return;
+
+ var cid = this.containerId,
+ scroll = "scroll." + cid,
+ resize = "resize."+cid,
+ orient = "orientationchange."+cid;
+
+ // unbind event listeners
+ this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
+
+ this.clearDropdownAlignmentPreference();
+
+ $("#select2-drop-mask").hide();
+ this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
+ this.dropdown.hide();
+ this.container.removeClass("select2-dropdown-open");
+ this.results.empty();
+
+
+ this.clearSearch();
+ this.search.removeClass("select2-active");
+ this.opts.element.trigger($.Event("select2-close"));
+ },
+
+ /**
+ * Opens control, sets input value, and updates results.
+ */
+ // abstract
+ externalSearch: function (term) {
+ this.open();
+ this.search.val(term);
+ this.updateResults(false);
+ },
+
+ // abstract
+ clearSearch: function () {
+
+ },
+
+ //abstract
+ getMaximumSelectionSize: function() {
+ return evaluate(this.opts.maximumSelectionSize);
+ },
+
+ // abstract
+ ensureHighlightVisible: function () {
+ var results = this.results, children, index, child, hb, rb, y, more;
+
+ index = this.highlight();
+
+ if (index < 0) return;
+
+ if (index == 0) {
+
+ // if the first element is highlighted scroll all the way to the top,
+ // that way any unselectable headers above it will also be scrolled
+ // into view
+
+ results.scrollTop(0);
+ return;
+ }
+
+ children = this.findHighlightableChoices().find('.select2-result-label');
+
+ child = $(children[index]);
+
+ hb = child.offset().top + child.outerHeight(true);
+
+ // if this is the last child lets also make sure select2-more-results is visible
+ if (index === children.length - 1) {
+ more = results.find("li.select2-more-results");
+ if (more.length > 0) {
+ hb = more.offset().top + more.outerHeight(true);
+ }
+ }
+
+ rb = results.offset().top + results.outerHeight(true);
+ if (hb > rb) {
+ results.scrollTop(results.scrollTop() + (hb - rb));
+ }
+ y = child.offset().top - results.offset().top;
+
+ // make sure the top of the element is visible
+ if (y < 0 && child.css('display') != 'none' ) {
+ results.scrollTop(results.scrollTop() + y); // y is negative
+ }
+ },
+
+ // abstract
+ findHighlightableChoices: function() {
+ return this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)");
+ },
+
+ // abstract
+ moveHighlight: function (delta) {
+ var choices = this.findHighlightableChoices(),
+ index = this.highlight();
+
+ while (index > -1 && index < choices.length) {
+ index += delta;
+ var choice = $(choices[index]);
+ if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
+ this.highlight(index);
+ break;
+ }
+ }
+ },
+
+ // abstract
+ highlight: function (index) {
+ var choices = this.findHighlightableChoices(),
+ choice,
+ data;
+
+ if (arguments.length === 0) {
+ return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
+ }
+
+ if (index >= choices.length) index = choices.length - 1;
+ if (index < 0) index = 0;
+
+ this.results.find(".select2-highlighted").removeClass("select2-highlighted");
+
+ choice = $(choices[index]);
+ choice.addClass("select2-highlighted");
+
+ this.ensureHighlightVisible();
+
+ data = choice.data("select2-data");
+ if (data) {
+ this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
+ }
+ },
+
+ // abstract
+ countSelectableResults: function() {
+ return this.findHighlightableChoices().length;
+ },
+
+ // abstract
+ highlightUnderEvent: function (event) {
+ var el = $(event.target).closest(".select2-result-selectable");
+ if (el.length > 0 && !el.is(".select2-highlighted")) {
+ var choices = this.findHighlightableChoices();
+ this.highlight(choices.index(el));
+ } else if (el.length == 0) {
+ // if we are over an unselectable item remove al highlights
+ this.results.find(".select2-highlighted").removeClass("select2-highlighted");
+ }
+ },
+
+ // abstract
+ loadMoreIfNeeded: function () {
+ var results = this.results,
+ more = results.find("li.select2-more-results"),
+ below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
+ offset = -1, // index of first element without data
+ page = this.resultsPage + 1,
+ self=this,
+ term=this.search.val(),
+ context=this.context;
+
+ if (more.length === 0) return;
+ below = more.offset().top - results.offset().top - results.height();
+
+ if (below <= this.opts.loadMorePadding) {
+ more.addClass("select2-active");
+ this.opts.query({
+ element: this.opts.element,
+ term: term,
+ page: page,
+ context: context,
+ matcher: this.opts.matcher,
+ callback: this.bind(function (data) {
+
+ // ignore a response if the select2 has been closed before it was received
+ if (!self.opened()) return;
+
+
+ self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
+ self.postprocessResults(data, false, false);
+
+ if (data.more===true) {
+ more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
+ window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
+ } else {
+ more.remove();
+ }
+ self.positionDropdown();
+ self.resultsPage = page;
+ self.context = data.context;
+ })});
+ }
+ },
+
+ /**
+ * Default tokenizer function which does nothing
+ */
+ tokenize: function() {
+
+ },
+
+ /**
+ * @param initial whether or not this is the call to this method right after the dropdown has been opened
+ */
+ // abstract
+ updateResults: function (initial) {
+ var search = this.search,
+ results = this.results,
+ opts = this.opts,
+ data,
+ self = this,
+ input,
+ term = search.val(),
+ lastTerm=$.data(this.container, "select2-last-term");
+
+ // prevent duplicate queries against the same term
+ if (initial !== true && lastTerm && equal(term, lastTerm)) return;
+
+ $.data(this.container, "select2-last-term", term);
+
+ // if the search is currently hidden we do not alter the results
+ if (initial !== true && (this.showSearchInput === false || !this.opened())) {
+ return;
+ }
+
+ function postRender() {
+ search.removeClass("select2-active");
+ self.positionDropdown();
+ }
+
+ function render(html) {
+ results.html(html);
+ postRender();
+ }
+
+ var maxSelSize = this.getMaximumSelectionSize();
+ if (maxSelSize >=1) {
+ data = this.data();
+ if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
+ render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(maxSelSize) + "</li>");
+ return;
+ }
+ }
+
+ if (search.val().length < opts.minimumInputLength) {
+ if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
+ render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
+ } else {
+ render("");
+ }
+ if (initial && this.showSearch) this.showSearch(true);
+ return;
+ }
+
+ if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
+ if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
+ render("<li class='select2-no-results'>" + opts.formatInputTooLong(search.val(), opts.maximumInputLength) + "</li>");
+ } else {
+ render("");
+ }
+ return;
+ }
+
+ if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
+ render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
+ }
+
+ search.addClass("select2-active");
+
+ // give the tokenizer a chance to pre-process the input
+ input = this.tokenize();
+ if (input != undefined && input != null) {
+ search.val(input);
+ }
+
+ this.resultsPage = 1;
+
+ opts.query({
+ element: opts.element,
+ term: search.val(),
+ page: this.resultsPage,
+ context: null,
+ matcher: opts.matcher,
+ callback: this.bind(function (data) {
+ var def; // default choice
+
+ // ignore a response if the select2 has been closed before it was received
+ if (!this.opened()) {
+ this.search.removeClass("select2-active");
+ return;
+ }
+
+ // save context, if any
+ this.context = (data.context===undefined) ? null : data.context;
+ // create a default choice and prepend it to the list
+ if (this.opts.createSearchChoice && search.val() !== "") {
+ def = this.opts.createSearchChoice.call(self, search.val(), data.results);
+ if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
+ if ($(data.results).filter(
+ function () {
+ return equal(self.id(this), self.id(def));
+ }).length === 0) {
+ data.results.unshift(def);
+ }
+ }
+ }
+
+ if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
+ render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
+ return;
+ }
+
+ results.empty();
+ self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
+
+ if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
+ results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>");
+ window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
+ }
+
+ this.postprocessResults(data, initial);
+
+ postRender();
+
+ this.opts.element.trigger({ type: "select2-loaded", items: data });
+ })});
+ },
+
+ // abstract
+ cancel: function () {
+ this.close();
+ },
+
+ // abstract
+ blur: function () {
+ // if selectOnBlur == true, select the currently highlighted option
+ if (this.opts.selectOnBlur)
+ this.selectHighlighted({noFocus: true});
+
+ this.close();
+ this.container.removeClass("select2-container-active");
+ // synonymous to .is(':focus'), which is available in jquery >= 1.6
+ if (this.search[0] === document.activeElement) { this.search.blur(); }
+ this.clearSearch();
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
+ },
+
+ // abstract
+ focusSearch: function () {
+ focus(this.search);
+ },
+
+ // abstract
+ selectHighlighted: function (options) {
+ var index=this.highlight(),
+ highlighted=this.results.find(".select2-highlighted"),
+ data = highlighted.closest('.select2-result').data("select2-data");
+
+ if (data) {
+ this.highlight(index);
+ this.onSelect(data, options);
+ } else if (options && options.noFocus) {
+ this.close();
+ }
+ },
+
+ // abstract
+ getPlaceholder: function () {
+ var placeholderOption;
+ return this.opts.element.attr("placeholder") ||
+ this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
+ this.opts.element.data("placeholder") ||
+ this.opts.placeholder ||
+ ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
+ },
+
+ // abstract
+ getPlaceholderOption: function() {
+ if (this.select) {
+ var firstOption = this.select.children().first();
+ if (this.opts.placeholderOption !== undefined ) {
+ //Determine the placeholder option based on the specified placeholderOption setting
+ return (this.opts.placeholderOption === "first" && firstOption) ||
+ (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
+ } else if (firstOption.text() === "" && firstOption.val() === "") {
+ //No explicit placeholder option specified, use the first if it's blank
+ return firstOption;
+ }
+ }
+ },
+
+ /**
+ * Get the desired width for the container element. This is
+ * derived first from option `width` passed to select2, then
+ * the inline 'style' on the original element, and finally
+ * falls back to the jQuery calculated element width.
+ */
+ // abstract
+ initContainerWidth: function () {
+ function resolveContainerWidth() {
+ var style, attrs, matches, i, l;
+
+ if (this.opts.width === "off") {
+ return null;
+ } else if (this.opts.width === "element"){
+ return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
+ } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
+ // check if there is inline style on the element that contains width
+ style = this.opts.element.attr('style');
+ if (style !== undefined) {
+ attrs = style.split(';');
+ for (i = 0, l = attrs.length; i < l; i = i + 1) {
+ matches = attrs[i].replace(/\s/g, '')
+ .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
+ if (matches !== null && matches.length >= 1)
+ return matches[1];
+ }
+ }
+
+ if (this.opts.width === "resolve") {
+ // next check if css('width') can resolve a width that is percent based, this is sometimes possible
+ // when attached to input type=hidden or elements hidden via css
+ style = this.opts.element.css('width');
+ if (style.indexOf("%") > 0) return style;
+
+ // finally, fallback on the calculated width of the element
+ return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
+ }
+
+ return null;
+ } else if ($.isFunction(this.opts.width)) {
+ return this.opts.width();
+ } else {
+ return this.opts.width;
+ }
+ };
+
+ var width = resolveContainerWidth.call(this);
+ if (width !== null) {
+ this.container.css("width", width);
+ }
+ }
+ });
+
+ SingleSelect2 = clazz(AbstractSelect2, {
+
+ // single
+
+ createContainer: function () {
+ var container = $(document.createElement("div")).attr({
+ "class": "select2-container"
+ }).html([
+ "<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>",
+ " <span class='select2-chosen'> </span><abbr class='select2-search-choice-close'></abbr>",
+ " <span class='select2-arrow'><b></b></span>",
+ "</a>",
+ "<input class='select2-focusser select2-offscreen' type='text'/>",
+ "<div class='select2-drop select2-display-none'>",
+ " <div class='select2-search'>",
+ " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'/>",
+ " </div>",
+ " <ul class='select2-results'>",
+ " </ul>",
+ "</div>"].join(""));
+ return container;
+ },
+
+ // single
+ enableInterface: function() {
+ if (this.parent.enableInterface.apply(this, arguments)) {
+ this.focusser.prop("disabled", !this.isInterfaceEnabled());
+ }
+ },
+
+ // single
+ opening: function () {
+ var el, range, len;
+
+ if (this.opts.minimumResultsForSearch >= 0) {
+ this.showSearch(true);
+ }
+
+ this.parent.opening.apply(this, arguments);
+
+ if (this.showSearchInput !== false) {
+ // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
+ // all other browsers handle this just fine
+
+ this.search.val(this.focusser.val());
+ }
+ this.search.focus();
+ // move the cursor to the end after focussing, otherwise it will be at the beginning and
+ // new text will appear *before* focusser.val()
+ el = this.search.get(0);
+ if (el.createTextRange) {
+ range = el.createTextRange();
+ range.collapse(false);
+ range.select();
+ } else if (el.setSelectionRange) {
+ len = this.search.val().length;
+ el.setSelectionRange(len, len);
+ }
+
+ this.focusser.prop("disabled", true).val("");
+ this.updateResults(true);
+ this.opts.element.trigger($.Event("select2-open"));
+ },
+
+ // single
+ close: function () {
+ if (!this.opened()) return;
+ this.parent.close.apply(this, arguments);
+ this.focusser.removeAttr("disabled");
+ this.focusser.focus();
+ },
+
+ // single
+ focus: function () {
+ if (this.opened()) {
+ this.close();
+ } else {
+ this.focusser.removeAttr("disabled");
+ this.focusser.focus();
+ }
+ },
+
+ // single
+ isFocused: function () {
+ return this.container.hasClass("select2-container-active");
+ },
+
+ // single
+ cancel: function () {
+ this.parent.cancel.apply(this, arguments);
+ this.focusser.removeAttr("disabled");
+ this.focusser.focus();
+ },
+
+ // single
+ initContainer: function () {
+
+ var selection,
+ container = this.container,
+ dropdown = this.dropdown;
+
+ if (this.opts.minimumResultsForSearch < 0) {
+ this.showSearch(false);
+ } else {
+ this.showSearch(true);
+ }
+
+ this.selection = selection = container.find(".select2-choice");
+
+ this.focusser = container.find(".select2-focusser");
+
+ // rewrite labels from original element to focusser
+ this.focusser.attr("id", "s2id_autogen"+nextUid());
+
+ $("label[for='" + this.opts.element.attr("id") + "']")
+ .attr('for', this.focusser.attr('id'));
+
+ this.focusser.attr("tabindex", this.elementTabIndex);
+
+ this.search.on("keydown", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
+
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
+ // prevent the page from scrolling
+ killEvent(e);
+ return;
+ }
+
+ switch (e.which) {
+ case KEY.UP:
+ case KEY.DOWN:
+ this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
+ killEvent(e);
+ return;
+ case KEY.ENTER:
+ this.selectHighlighted();
+ killEvent(e);
+ return;
+ case KEY.TAB:
+ this.selectHighlighted({noFocus: true});
+ return;
+ case KEY.ESC:
+ this.cancel(e);
+ killEvent(e);
+ return;
+ }
+ }));
+
+ this.search.on("blur", this.bind(function(e) {
+ // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
+ // without this the search field loses focus which is annoying
+ if (document.activeElement === this.body().get(0)) {
+ window.setTimeout(this.bind(function() {
+ this.search.focus();
+ }), 0);
+ }
+ }));
+
+ this.focusser.on("keydown", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
+
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
+ return;
+ }
+
+ if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
+ killEvent(e);
+ return;
+ }
+
+ if (e.which == KEY.DOWN || e.which == KEY.UP
+ || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
+
+ if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
+
+ this.open();
+ killEvent(e);
+ return;
+ }
+
+ if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
+ if (this.opts.allowClear) {
+ this.clear();
+ }
+ killEvent(e);
+ return;
+ }
+ }));
+
+
+ installKeyUpChangeEvent(this.focusser);
+ this.focusser.on("keyup-change input", this.bind(function(e) {
+ if (this.opts.minimumResultsForSearch >= 0) {
+ e.stopPropagation();
+ if (this.opened()) return;
+ this.open();
+ }
+ }));
+
+ selection.on("mousedown", "abbr", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
+ this.clear();
+ killEventImmediately(e);
+ this.close();
+ this.selection.focus();
+ }));
+
+ selection.on("mousedown", this.bind(function (e) {
+
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
+
+ if (this.opened()) {
+ this.close();
+ } else if (this.isInterfaceEnabled()) {
+ this.open();
+ }
+
+ killEvent(e);
+ }));
+
+ dropdown.on("mousedown", this.bind(function() { this.search.focus(); }));
+
+ selection.on("focus", this.bind(function(e) {
+ killEvent(e);
+ }));
+
+ this.focusser.on("focus", this.bind(function(){
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
+ this.container.addClass("select2-container-active");
+ })).on("blur", this.bind(function() {
+ if (!this.opened()) {
+ this.container.removeClass("select2-container-active");
+ this.opts.element.trigger($.Event("select2-blur"));
+ }
+ }));
+ this.search.on("focus", this.bind(function(){
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
+ this.container.addClass("select2-container-active");
+ }));
+
+ this.initContainerWidth();
+ this.opts.element.addClass("select2-offscreen");
+ this.setPlaceholder();
+
+ },
+
+ // single
+ clear: function(triggerChange) {
+ var data=this.selection.data("select2-data");
+ if (data) { // guard against queued quick consecutive clicks
+ var placeholderOption = this.getPlaceholderOption();
+ this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
+ this.selection.find(".select2-chosen").empty();
+ this.selection.removeData("select2-data");
+ this.setPlaceholder();
+
+ if (triggerChange !== false){
+ this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
+ this.triggerChange({removed:data});
+ }
+ }
+ },
+
+ /**
+ * Sets selection based on source element's value
+ */
+ // single
+ initSelection: function () {
+ var selected;
+ if (this.isPlaceholderOptionSelected()) {
+ this.updateSelection([]);
+ this.close();
+ this.setPlaceholder();
+ } else {
+ var self = this;
+ this.opts.initSelection.call(null, this.opts.element, function(selected){
+ if (selected !== undefined && selected !== null) {
+ self.updateSelection(selected);
+ self.close();
+ self.setPlaceholder();
+ }
+ });
+ }
+ },
+
+ isPlaceholderOptionSelected: function() {
+ var placeholderOption;
+ return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.is(':selected')) ||
+ (this.opts.element.val() === "") ||
+ (this.opts.element.val() === undefined) ||
+ (this.opts.element.val() === null);
+ },
+
+ // single
+ prepareOpts: function () {
+ var opts = this.parent.prepareOpts.apply(this, arguments),
+ self=this;
+
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
+ // install the selection initializer
+ opts.initSelection = function (element, callback) {
+ var selected = element.find(":selected");
+ // a single select box always has a value, no need to null check 'selected'
+ callback(self.optionToData(selected));
+ };
+ } else if ("data" in opts) {
+ // install default initSelection when applied to hidden input and data is local
+ opts.initSelection = opts.initSelection || function (element, callback) {
+ var id = element.val();
+ //search in data by id, storing the actual matching item
+ var match = null;
+ opts.query({
+ matcher: function(term, text, el){
+ var is_match = equal(id, opts.id(el));
+ if (is_match) {
+ match = el;
+ }
+ return is_match;
+ },
+ callback: !$.isFunction(callback) ? $.noop : function() {
+ callback(match);
+ }
+ });
+ };
+ }
+
+ return opts;
+ },
+
+ // single
+ getPlaceholder: function() {
+ // if a placeholder is specified on a single select without a valid placeholder option ignore it
+ if (this.select) {
+ if (this.getPlaceholderOption() === undefined) {
+ return undefined;
+ }
+ }
+
+ return this.parent.getPlaceholder.apply(this, arguments);
+ },
+
+ // single
+ setPlaceholder: function () {
+ var placeholder = this.getPlaceholder();
+
+ if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
+
+ // check for a placeholder option if attached to a select
+ if (this.select && this.getPlaceholderOption() === undefined) return;
+
+ this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
+
+ this.selection.addClass("select2-default");
+
+ this.container.removeClass("select2-allowclear");
+ }
+ },
+
+ // single
+ postprocessResults: function (data, initial, noHighlightUpdate) {
+ var selected = 0, self = this, showSearchInput = true;
+
+ // find the selected element in the result list
+
+ this.findHighlightableChoices().each2(function (i, elm) {
+ if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
+ selected = i;
+ return false;
+ }
+ });
+
+ // and highlight it
+ if (noHighlightUpdate !== false) {
+ if (initial === true && selected >= 0) {
+ this.highlight(selected);
+ } else {
+ this.highlight(0);
+ }
+ }
+
+ // hide the search box if this is the first we got the results and there are enough of them for search
+
+ if (initial === true) {
+ var min = this.opts.minimumResultsForSearch;
+ if (min >= 0) {
+ this.showSearch(countResults(data.results) >= min);
+ }
+ }
+ },
+
+ // single
+ showSearch: function(showSearchInput) {
+ if (this.showSearchInput === showSearchInput) return;
+
+ this.showSearchInput = showSearchInput;
+
+ this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
+ this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
+ //add "select2-with-searchbox" to the container if search box is shown
+ $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
+ },
+
+ // single
+ onSelect: function (data, options) {
+
+ if (!this.triggerSelect(data)) { return; }
+
+ var old = this.opts.element.val(),
+ oldData = this.data();
+
+ this.opts.element.val(this.id(data));
+ this.updateSelection(data);
+
+ this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
+
+ this.close();
+
+ if (!options || !options.noFocus)
+ this.selection.focus();
+
+ if (!equal(old, this.id(data))) { this.triggerChange({added:data,removed:oldData}); }
+ },
+
+ // single
+ updateSelection: function (data) {
+
+ var container=this.selection.find(".select2-chosen"), formatted, cssClass;
+
+ this.selection.data("select2-data", data);
+
+ container.empty();
+ formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
+ if (formatted !== undefined) {
+ container.append(formatted);
+ }
+ cssClass=this.opts.formatSelectionCssClass(data, container);
+ if (cssClass !== undefined) {
+ container.addClass(cssClass);
+ }
+
+ this.selection.removeClass("select2-default");
+
+ if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
+ this.container.addClass("select2-allowclear");
+ }
+ },
+
+ // single
+ val: function () {
+ var val,
+ triggerChange = false,
+ data = null,
+ self = this,
+ oldData = this.data();
+
+ if (arguments.length === 0) {
+ return this.opts.element.val();
+ }
+
+ val = arguments[0];
+
+ if (arguments.length > 1) {
+ triggerChange = arguments[1];
+ }
+
+ if (this.select) {
+ this.select
+ .val(val)
+ .find(":selected").each2(function (i, elm) {
+ data = self.optionToData(elm);
+ return false;
+ });
+ this.updateSelection(data);
+ this.setPlaceholder();
+ if (triggerChange) {
+ this.triggerChange({added: data, removed:oldData});
+ }
+ } else {
+ // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
+ if (!val && val !== 0) {
+ this.clear(triggerChange);
+ return;
+ }
+ if (this.opts.initSelection === undefined) {
+ throw new Error("cannot call val() if initSelection() is not defined");
+ }
+ this.opts.element.val(val);
+ this.opts.initSelection(this.opts.element, function(data){
+ self.opts.element.val(!data ? "" : self.id(data));
+ self.updateSelection(data);
+ self.setPlaceholder();
+ if (triggerChange) {
+ self.triggerChange({added: data, removed:oldData});
+ }
+ });
+ }
+ },
+
+ // single
+ clearSearch: function () {
+ this.search.val("");
+ this.focusser.val("");
+ },
+
+ // single
+ data: function(value, triggerChange) {
+ var data;
+
+ if (arguments.length === 0) {
+ data = this.selection.data("select2-data");
+ if (data == undefined) data = null;
+ return data;
+ } else {
+ if (!value || value === "") {
+ this.clear(triggerChange);
+ } else {
+ data = this.data();
+ this.opts.element.val(!value ? "" : this.id(value));
+ this.updateSelection(value);
+ if (triggerChange) {
+ this.triggerChange({added: value, removed:data});
+ }
+ }
+ }
+ }
+ });
+
+ MultiSelect2 = clazz(AbstractSelect2, {
+
+ // multi
+ createContainer: function () {
+ var container = $(document.createElement("div")).attr({
+ "class": "select2-container select2-container-multi"
+ }).html([
+ "<ul class='select2-choices'>",
+ " <li class='select2-search-field'>",
+ " <input type='text' autocomplete='off' autocorrect='off' autocapitilize='off' spellcheck='false' class='select2-input'>",
+ " </li>",
+ "</ul>",
+ "<div class='select2-drop select2-drop-multi select2-display-none'>",
+ " <ul class='select2-results'>",
+ " </ul>",
+ "</div>"].join(""));
+ return container;
+ },
+
+ // multi
+ prepareOpts: function () {
+ var opts = this.parent.prepareOpts.apply(this, arguments),
+ self=this;
+
+ // TODO validate placeholder is a string if specified
+
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
+ // install sthe selection initializer
+ opts.initSelection = function (element, callback) {
+
+ var data = [];
+
+ element.find(":selected").each2(function (i, elm) {
+ data.push(self.optionToData(elm));
+ });
+ callback(data);
+ };
+ } else if ("data" in opts) {
+ // install default initSelection when applied to hidden input and data is local
+ opts.initSelection = opts.initSelection || function (element, callback) {
+ var ids = splitVal(element.val(), opts.separator);
+ //search in data by array of ids, storing matching items in a list
+ var matches = [];
+ opts.query({
+ matcher: function(term, text, el){
+ var is_match = $.grep(ids, function(id) {
+ return equal(id, opts.id(el));
+ }).length;
+ if (is_match) {
+ matches.push(el);
+ }
+ return is_match;
+ },
+ callback: !$.isFunction(callback) ? $.noop : function() {
+ // reorder matches based on the order they appear in the ids array because right now
+ // they are in the order in which they appear in data array
+ var ordered = [];
+ for (var i = 0; i < ids.length; i++) {
+ var id = ids[i];
+ for (var j = 0; j < matches.length; j++) {
+ var match = matches[j];
+ if (equal(id, opts.id(match))) {
+ ordered.push(match);
+ matches.splice(j, 1);
+ break;
+ }
+ }
+ }
+ callback(ordered);
+ }
+ });
+ };
+ }
+
+ return opts;
+ },
+
+ selectChoice: function (choice) {
+
+ var selected = this.container.find(".select2-search-choice-focus");
+ if (selected.length && choice && choice[0] == selected[0]) {
+
+ } else {
+ if (selected.length) {
+ this.opts.element.trigger("choice-deselected", selected);
+ }
+ selected.removeClass("select2-search-choice-focus");
+ if (choice && choice.length) {
+ this.close();
+ choice.addClass("select2-search-choice-focus");
+ this.opts.element.trigger("choice-selected", choice);
+ }
+ }
+ },
+
+ // multi
+ initContainer: function () {
+
+ var selector = ".select2-choices", selection;
+
+ this.searchContainer = this.container.find(".select2-search-field");
+ this.selection = selection = this.container.find(selector);
+
+ var _this = this;
+ this.selection.on("mousedown", ".select2-search-choice", function (e) {
+ //killEvent(e);
+ _this.search[0].focus();
+ _this.selectChoice($(this));
+ })
+
+ // rewrite labels from original element to focusser
+ this.search.attr("id", "s2id_autogen"+nextUid());
+ $("label[for='" + this.opts.element.attr("id") + "']")
+ .attr('for', this.search.attr('id'));
+
+ this.search.on("input paste", this.bind(function() {
+ if (!this.isInterfaceEnabled()) return;
+ if (!this.opened()) {
+ this.open();
+ }
+ }));
+
+ this.search.attr("tabindex", this.elementTabIndex);
+
+ this.keydowns = 0;
+ this.search.on("keydown", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
+
+ ++this.keydowns;
+ var selected = selection.find(".select2-search-choice-focus");
+ var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
+ var next = selected.next(".select2-search-choice:not(.select2-locked)");
+ var pos = getCursorInfo(this.search);
+
+ if (selected.length &&
+ (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
+ var selectedChoice = selected;
+ if (e.which == KEY.LEFT && prev.length) {
+ selectedChoice = prev;
+ }
+ else if (e.which == KEY.RIGHT) {
+ selectedChoice = next.length ? next : null;
+ }
+ else if (e.which === KEY.BACKSPACE) {
+ this.unselect(selected.first());
+ this.search.width(10);
+ selectedChoice = prev.length ? prev : next;
+ } else if (e.which == KEY.DELETE) {
+ this.unselect(selected.first());
+ this.search.width(10);
+ selectedChoice = next.length ? next : null;
+ } else if (e.which == KEY.ENTER) {
+ selectedChoice = null;
+ }
+
+ this.selectChoice(selectedChoice);
+ killEvent(e);
+ if (!selectedChoice || !selectedChoice.length) {
+ this.open();
+ }
+ return;
+ } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
+ || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
+
+ this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
+ killEvent(e);
+ return;
+ } else {
+ this.selectChoice(null);
+ }
+
+ if (this.opened()) {
+ switch (e.which) {
+ case KEY.UP:
+ case KEY.DOWN:
+ this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
+ killEvent(e);
+ return;
+ case KEY.ENTER:
+ this.selectHighlighted();
+ killEvent(e);
+ return;
+ case KEY.TAB:
+ this.selectHighlighted({noFocus:true});
+ this.close();
+ return;
+ case KEY.ESC:
+ this.cancel(e);
+ killEvent(e);
+ return;
+ }
+ }
+
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
+ || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
+ return;
+ }
+
+ if (e.which === KEY.ENTER) {
+ if (this.opts.openOnEnter === false) {
+ return;
+ } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
+ return;
+ }
+ }
+
+ this.open();
+
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
+ // prevent the page from scrolling
+ killEvent(e);
+ }
+
+ if (e.which === KEY.ENTER) {
+ // prevent form from being submitted
+ killEvent(e);
+ }
+
+ }));
+
+ this.search.on("keyup", this.bind(function (e) {
+ this.keydowns = 0;
+ this.resizeSearch();
+ })
+ );
+
+ this.search.on("blur", this.bind(function(e) {
+ this.container.removeClass("select2-container-active");
+ this.search.removeClass("select2-focused");
+ this.selectChoice(null);
+ if (!this.opened()) this.clearSearch();
+ e.stopImmediatePropagation();
+ this.opts.element.trigger($.Event("select2-blur"));
+ }));
+
+ this.container.on("click", selector, this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
+ if ($(e.target).closest(".select2-search-choice").length > 0) {
+ // clicked inside a select2 search choice, do not open
+ return;
+ }
+ this.selectChoice(null);
+ this.clearPlaceholder();
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
+ this.open();
+ this.focusSearch();
+ e.preventDefault();
+ }));
+
+ this.container.on("focus", selector, this.bind(function () {
+ if (!this.isInterfaceEnabled()) return;
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
+ this.container.addClass("select2-container-active");
+ this.dropdown.addClass("select2-drop-active");
+ this.clearPlaceholder();
+ }));
+
+ this.initContainerWidth();
+ this.opts.element.addClass("select2-offscreen");
+
+ // set the placeholder if necessary
+ this.clearSearch();
+ },
+
+ // multi
+ enableInterface: function() {
+ if (this.parent.enableInterface.apply(this, arguments)) {
+ this.search.prop("disabled", !this.isInterfaceEnabled());
+ }
+ },
+
+ // multi
+ initSelection: function () {
+ var data;
+ if (this.opts.element.val() === "" && this.opts.element.text() === "") {
+ this.updateSelection([]);
+ this.close();
+ // set the placeholder if necessary
+ this.clearSearch();
+ }
+ if (this.select || this.opts.element.val() !== "") {
+ var self = this;
+ this.opts.initSelection.call(null, this.opts.element, function(data){
+ if (data !== undefined && data !== null) {
+ self.updateSelection(data);
+ self.close();
+ // set the placeholder if necessary
+ self.clearSearch();
+ }
+ });
+ }
+ },
+
+ // multi
+ clearSearch: function () {
+ var placeholder = this.getPlaceholder(),
+ maxWidth = this.getMaxSearchWidth();
+
+ if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
+ this.search.val(placeholder).addClass("select2-default");
+ // stretch the search box to full width of the container so as much of the placeholder is visible as possible
+ // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944
+ this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
+ } else {
+ this.search.val("").width(10);
+ }
+ },
+
+ // multi
+ clearPlaceholder: function () {
+ if (this.search.hasClass("select2-default")) {
+ this.search.val("").removeClass("select2-default");
+ }
+ },
+
+ // multi
+ opening: function () {
+ this.clearPlaceholder(); // should be done before super so placeholder is not used to search
+ this.resizeSearch();
+
+ this.parent.opening.apply(this, arguments);
+
+ this.focusSearch();
+
+ this.updateResults(true);
+ this.search.focus();
+ this.opts.element.trigger($.Event("select2-open"));
+ },
+
+ // multi
+ close: function () {
+ if (!this.opened()) return;
+ this.parent.close.apply(this, arguments);
+ },
+
+ // multi
+ focus: function () {
+ this.close();
+ this.search.focus();
+ },
+
+ // multi
+ isFocused: function () {
+ return this.search.hasClass("select2-focused");
+ },
+
+ // multi
+ updateSelection: function (data) {
+ var ids = [], filtered = [], self = this;
+
+ // filter out duplicates
+ $(data).each(function () {
+ if (indexOf(self.id(this), ids) < 0) {
+ ids.push(self.id(this));
+ filtered.push(this);
+ }
+ });
+ data = filtered;
+
+ this.selection.find(".select2-search-choice").remove();
+ $(data).each(function () {
+ self.addSelectedChoice(this);
+ });
+ self.postprocessResults();
+ },
+
+ // multi
+ tokenize: function() {
+ var input = this.search.val();
+ input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
+ if (input != null && input != undefined) {
+ this.search.val(input);
+ if (input.length > 0) {
+ this.open();
+ }
+ }
+
+ },
+
+ // multi
+ onSelect: function (data, options) {
+
+ if (!this.triggerSelect(data)) { return; }
+
+ this.addSelectedChoice(data);
+
+ this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
+
+ if (this.select || !this.opts.closeOnSelect) this.postprocessResults();
+
+ if (this.opts.closeOnSelect) {
+ this.close();
+ this.search.width(10);
+ } else {
+ if (this.countSelectableResults()>0) {
+ this.search.width(10);
+ this.resizeSearch();
+ if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
+ // if we reached max selection size repaint the results so choices
+ // are replaced with the max selection reached message
+ this.updateResults(true);
+ }
+ this.positionDropdown();
+ } else {
+ // if nothing left to select close
+ this.close();
+ this.search.width(10);
+ }
+ }
+
+ // since its not possible to select an element that has already been
+ // added we do not need to check if this is a new element before firing change
+ this.triggerChange({ added: data });
+
+ if (!options || !options.noFocus)
+ this.focusSearch();
+ },
+
+ // multi
+ cancel: function () {
+ this.close();
+ this.focusSearch();
+ },
+
+ addSelectedChoice: function (data) {
+ var enableChoice = !data.locked,
+ enabledItem = $(
+ "<li class='select2-search-choice'>" +
+ " <div></div>" +
+ " <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" +
+ "</li>"),
+ disabledItem = $(
+ "<li class='select2-search-choice select2-locked'>" +
+ "<div></div>" +
+ "</li>");
+ var choice = enableChoice ? enabledItem : disabledItem,
+ id = this.id(data),
+ val = this.getVal(),
+ formatted,
+ cssClass;
+
+ formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
+ if (formatted != undefined) {
+ choice.find("div").replaceWith("<div>"+formatted+"</div>");
+ }
+ cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
+ if (cssClass != undefined) {
+ choice.addClass(cssClass);
+ }
+
+ if(enableChoice){
+ choice.find(".select2-search-choice-close")
+ .on("mousedown", killEvent)
+ .on("click dblclick", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
+
+ $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){
+ this.unselect($(e.target));
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
+ this.close();
+ this.focusSearch();
+ })).dequeue();
+ killEvent(e);
+ })).on("focus", this.bind(function () {
+ if (!this.isInterfaceEnabled()) return;
+ this.container.addClass("select2-container-active");
+ this.dropdown.addClass("select2-drop-active");
+ }));
+ }
+
+ choice.data("select2-data", data);
+ choice.insertBefore(this.searchContainer);
+
+ val.push(id);
+ this.setVal(val);
+ },
+
+ // multi
+ unselect: function (selected) {
+ var val = this.getVal(),
+ data,
+ index;
+
+ selected = selected.closest(".select2-search-choice");
+
+ if (selected.length === 0) {
+ throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
+ }
+
+ data = selected.data("select2-data");
+
+ if (!data) {
+ // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
+ // and invoked on an element already removed
+ return;
+ }
+
+ index = indexOf(this.id(data), val);
+
+ if (index >= 0) {
+ val.splice(index, 1);
+ this.setVal(val);
+ if (this.select) this.postprocessResults();
+ }
+ selected.remove();
+
+ this.opts.element.trigger({ type: "removed", val: this.id(data), choice: data });
+ this.triggerChange({ removed: data });
+ },
+
+ // multi
+ postprocessResults: function (data, initial, noHighlightUpdate) {
+ var val = this.getVal(),
+ choices = this.results.find(".select2-result"),
+ compound = this.results.find(".select2-result-with-children"),
+ self = this;
+
+ choices.each2(function (i, choice) {
+ var id = self.id(choice.data("select2-data"));
+ if (indexOf(id, val) >= 0) {
+ choice.addClass("select2-selected");
+ // mark all children of the selected parent as selected
+ choice.find(".select2-result-selectable").addClass("select2-selected");
+ }
+ });
+
+ compound.each2(function(i, choice) {
+ // hide an optgroup if it doesnt have any selectable children
+ if (!choice.is('.select2-result-selectable')
+ && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
+ choice.addClass("select2-selected");
+ }
+ });
+
+ if (this.highlight() == -1 && noHighlightUpdate !== false){
+ self.highlight(0);
+ }
+
+ //If all results are chosen render formatNoMAtches
+ if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
+ if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
+ if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
+ this.results.append("<li class='select2-no-results'>" + self.opts.formatNoMatches(self.search.val()) + "</li>");
+ }
+ }
+ }
+
+ },
+
+ // multi
+ getMaxSearchWidth: function() {
+ return this.selection.width() - getSideBorderPadding(this.search);
+ },
+
+ // multi
+ resizeSearch: function () {
+ var minimumWidth, left, maxWidth, containerLeft, searchWidth,
+ sideBorderPadding = getSideBorderPadding(this.search);
+
+ minimumWidth = measureTextWidth(this.search) + 10;
+
+ left = this.search.offset().left;
+
+ maxWidth = this.selection.width();
+ containerLeft = this.selection.offset().left;
+
+ searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
+
+ if (searchWidth < minimumWidth) {
+ searchWidth = maxWidth - sideBorderPadding;
+ }
+
+ if (searchWidth < 40) {
+ searchWidth = maxWidth - sideBorderPadding;
+ }
+
+ if (searchWidth <= 0) {
+ searchWidth = minimumWidth;
+ }
+
+ this.search.width(searchWidth);
+ },
+
+ // multi
+ getVal: function () {
+ var val;
+ if (this.select) {
+ val = this.select.val();
+ return val === null ? [] : val;
+ } else {
+ val = this.opts.element.val();
+ return splitVal(val, this.opts.separator);
+ }
+ },
+
+ // multi
+ setVal: function (val) {
+ var unique;
+ if (this.select) {
+ this.select.val(val);
+ } else {
+ unique = [];
+ // filter out duplicates
+ $(val).each(function () {
+ if (indexOf(this, unique) < 0) unique.push(this);
+ });
+ this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
+ }
+ },
+
+ // multi
+ buildChangeDetails: function (old, current) {
+ var current = current.slice(0),
+ old = old.slice(0);
+
+ // remove intersection from each array
+ for (var i = 0; i < current.length; i++) {
+ for (var j = 0; j < old.length; j++) {
+ if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
+ current.splice(i, 1);
+ i--;
+ old.splice(j, 1);
+ j--;
+ }
+ }
+ }
+
+ return {added: current, removed: old};
+ },
+
+
+ // multi
+ val: function (val, triggerChange) {
+ var oldData, self=this, changeDetails;
+
+ if (arguments.length === 0) {
+ return this.getVal();
+ }
+
+ oldData=this.data();
+ if (!oldData.length) oldData=[];
+
+ // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
+ if (!val && val !== 0) {
+ this.opts.element.val("");
+ this.updateSelection([]);
+ this.clearSearch();
+ if (triggerChange) {
+ this.triggerChange({added: this.data(), removed: oldData});
+ }
+ return;
+ }
+
+ // val is a list of ids
+ this.setVal(val);
+
+ if (this.select) {
+ this.opts.initSelection(this.select, this.bind(this.updateSelection));
+ if (triggerChange) {
+ this.triggerChange(this.buildChangeDetails(oldData, this.data()));
+ }
+ } else {
+ if (this.opts.initSelection === undefined) {
+ throw new Error("val() cannot be called if initSelection() is not defined");
+ }
+
+ this.opts.initSelection(this.opts.element, function(data){
+ var ids=$.map(data, self.id);
+ self.setVal(ids);
+ self.updateSelection(data);
+ self.clearSearch();
+ if (triggerChange) {
+ self.triggerChange(this.buildChangeDetails(oldData, this.data()));
+ }
+ });
+ }
+ this.clearSearch();
+ },
+
+ // multi
+ onSortStart: function() {
+ if (this.select) {
+ throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
+ }
+
+ // collapse search field into 0 width so its container can be collapsed as well
+ this.search.width(0);
+ // hide the container
+ this.searchContainer.hide();
+ },
+
+ // multi
+ onSortEnd:function() {
+
+ var val=[], self=this;
+
+ // show search and move it to the end of the list
+ this.searchContainer.show();
+ // make sure the search container is the last item in the list
+ this.searchContainer.appendTo(this.searchContainer.parent());
+ // since we collapsed the width in dragStarted, we resize it here
+ this.resizeSearch();
+
+ // update selection
+ this.selection.find(".select2-search-choice").each(function() {
+ val.push(self.opts.id($(this).data("select2-data")));
+ });
+ this.setVal(val);
+ this.triggerChange();
+ },
+
+ // multi
+ data: function(values, triggerChange) {
+ var self=this, ids, old;
+ if (arguments.length === 0) {
+ return this.selection
+ .find(".select2-search-choice")
+ .map(function() { return $(this).data("select2-data"); })
+ .get();
+ } else {
+ old = this.data();
+ if (!values) { values = []; }
+ ids = $.map(values, function(e) { return self.opts.id(e); });
+ this.setVal(ids);
+ this.updateSelection(values);
+ this.clearSearch();
+ if (triggerChange) {
+ this.triggerChange(this.buildChangeDetails(old, this.data()));
+ }
+ }
+ }
+ });
+
+ $.fn.select2 = function () {
+
+ var args = Array.prototype.slice.call(arguments, 0),
+ opts,
+ select2,
+ method, value, multiple,
+ allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "readonly", "positionDropdown", "data", "search"],
+ valueMethods = ["val", "opened", "isFocused", "container", "data"],
+ methodsMap = { search: "externalSearch" };
+
+ this.each(function () {
+ if (args.length === 0 || typeof(args[0]) === "object") {
+ opts = args.length === 0 ? {} : $.extend({}, args[0]);
+ opts.element = $(this);
+
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
+ multiple = opts.element.prop("multiple");
+ } else {
+ multiple = opts.multiple || false;
+ if ("tags" in opts) {opts.multiple = multiple = true;}
+ }
+
+ select2 = multiple ? new MultiSelect2() : new SingleSelect2();
+ select2.init(opts);
+ } else if (typeof(args[0]) === "string") {
+
+ if (indexOf(args[0], allowedMethods) < 0) {
+ throw "Unknown method: " + args[0];
+ }
+
+ value = undefined;
+ select2 = $(this).data("select2");
+ if (select2 === undefined) return;
+
+ method=args[0];
+
+ if (method === "container") {
+ value = select2.container;
+ } else if (method === "dropdown") {
+ value = select2.dropdown;
+ } else {
+ if (methodsMap[method]) method = methodsMap[method];
+
+ value = select2[method].apply(select2, args.slice(1));
+ }
+ if (indexOf(args[0], valueMethods) >= 0) {
+ return false;
+ }
+ } else {
+ throw "Invalid arguments to select2 plugin: " + args;
+ }
+ });
+ return (value === undefined) ? this : value;
+ };
+
+ // plugin defaults, accessible to users
+ $.fn.select2.defaults = {
+ width: "copy",
+ loadMorePadding: 0,
+ closeOnSelect: true,
+ openOnEnter: true,
+ containerCss: {},
+ dropdownCss: {},
+ containerCssClass: "",
+ dropdownCssClass: "",
+ formatResult: function(result, container, query, escapeMarkup) {
+ var markup=[];
+ markMatch(result.text, query.term, markup, escapeMarkup);
+ return markup.join("");
+ },
+ formatSelection: function (data, container, escapeMarkup) {
+ return data ? escapeMarkup(data.text) : undefined;
+ },
+ sortResults: function (results, container, query) {
+ return results;
+ },
+ formatResultCssClass: function(data) {return undefined;},
+ formatSelectionCssClass: function(data, container) {return undefined;},
+ formatNoMatches: function () { return "No matches found"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); },
+ formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Loading more results..."; },
+ formatSearching: function () { return "Searching..."; },
+ minimumResultsForSearch: 0,
+ minimumInputLength: 0,
+ maximumInputLength: null,
+ maximumSelectionSize: 0,
+ id: function (e) { return e.id; },
+ matcher: function(term, text) {
+ return (''+text).toUpperCase().indexOf((''+term).toUpperCase()) >= 0;
+ },
+ separator: ",",
+ tokenSeparators: [],
+ tokenizer: defaultTokenizer,
+ escapeMarkup: defaultEscapeMarkup,
+ blurOnChange: false,
+ selectOnBlur: false,
+ adaptContainerCssClass: function(c) { return c; },
+ adaptDropdownCssClass: function(c) { return null; }
+ };
+
+ $.fn.select2.ajaxDefaults = {
+ transport: $.ajax,
+ params: {
+ type: "GET",
+ cache: false,
+ dataType: "json"
+ }
+ };
+
+ // exports
+ window.Select2 = {
+ query: {
+ ajax: ajax,
+ local: local,
+ tags: tags
+ }, util: {
+ debounce: debounce,
+ markMatch: markMatch,
+ escapeMarkup: defaultEscapeMarkup
+ }, "class": {
+ "abstract": AbstractSelect2,
+ "single": SingleSelect2,
+ "multi": MultiSelect2
+ }
+ };
+
+}(jQuery));
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.min.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.min.js
new file mode 100644
index 0000000..1537edc
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.min.js
@@ -0,0 +1,22 @@
+/*
+Copyright 2012 Igor Vaynberg
+
+Version: 3.4.1 Timestamp: Thu Jun 27 18:02:10 PDT 2013
+
+This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
+General Public License version 2 (the "GPL License"). You may choose either license to govern your
+use of this software only upon the condition that you accept all of the terms of either the Apache
+License or the GPL License.
+
+You may obtain a copy of the Apache License and the GPL License at:
+
+http://www.apache.org/licenses/LICENSE-2.0
+http://www.gnu.org/licenses/gpl-2.0.html
+
+Unless required by applicable law or agreed to in writing, software distributed under the Apache License
+or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+either express or implied. See the Apache License and the GPL License for the specific language governing
+permissions and limitations under the Apache License and the GPL License.
+*/
+(function(a){a.fn.each2===void 0&&a.fn.extend({each2:function(b){for(var c=a([0]),d=-1,e=this.length;e>++d&&(c.context=c[0]=this[d])&&b.call(c[0],d,c)!==!1;);return this}})})(jQuery),function(a,b){"use strict";function m(a,b){for(var c=0,d=b.length;d>c;c+=1)if(o(a,b[c]))return c;return-1}function n(){var b=a(l);b.appendTo("body");var c={width:b.width()-b[0].clientWidth,height:b.height()-b[0].clientHeight};return b.remove(),c}function o(a,c){return a===c?!0:a===b||c===b?!1:null===a||null===c?!1:a.constructor===String?a+""==c+"":c.constructor===String?c+""==a+"":!1}function p(b,c){var d,e,f;if(null===b||1>b.length)return[];for(d=b.split(c),e=0,f=d.length;f>e;e+=1)d[e]=a.trim(d[e]);return d}function q(a){return a.outerWidth(!1)-a.width()}function r(c){var d="keyup-change-value";c.on("keydown",function(){a.data(c,d)===b&&a.data(c,d,c.val())}),c.on("keyup",function(){var e=a.data(c,d);e!==b&&c.val()!==e&&(a.removeData(c,d),c.trigger("keyup-change"))})}function s(c){c.on("mousemove",function(c){var d=i;(d===b||d.x!==c.pageX||d.y!==c.pageY)&&a(c.target).trigger("mousemove-filtered",c)})}function t(a,c,d){d=d||b;var e;return function(){var b=arguments;window.clearTimeout(e),e=window.setTimeout(function(){c.apply(d,b)},a)}}function u(a){var c,b=!1;return function(){return b===!1&&(c=a(),b=!0),c}}function v(a,b){var c=t(a,function(a){b.trigger("scroll-debounced",a)});b.on("scroll",function(a){m(a.target,b.get())>=0&&c(a)})}function w(a){a[0]!==document.activeElement&&window.setTimeout(function(){var d,b=a[0],c=a.val().length;a.focus(),a.is(":visible")&&b===document.activeElement&&(b.setSelectionRange?b.setSelectionRange(c,c):b.createTextRange&&(d=b.createTextRange(),d.collapse(!1),d.select()))},0)}function x(b){b=a(b)[0];var c=0,d=0;if("selectionStart"in b)c=b.selectionStart,d=b.selectionEnd-c;else if("selection"in document){b.focus();var e=document.selection.createRange();d=document.selection.createRange().text.length,e.moveStart("character",-b.value.length),c=e.text.length-d}return{offset:c,length:d}}function y(a){a.preventDefault(),a.stopPropagation()}function z(a){a.preventDefault(),a.stopImmediatePropagation()}function A(b){if(!h){var c=b[0].currentStyle||window.getComputedStyle(b[0],null);h=a(document.createElement("div")).css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:c.fontSize,fontFamily:c.fontFamily,fontStyle:c.fontStyle,fontWeight:c.fontWeight,letterSpacing:c.letterSpacing,textTransform:c.textTransform,whiteSpace:"nowrap"}),h.attr("class","select2-sizer"),a("body").append(h)}return h.text(b.val()),h.width()}function B(b,c,d){var e,g,f=[];e=b.attr("class"),e&&(e=""+e,a(e.split(" ")).each2(function(){0===this.indexOf("select2-")&&f.push(this)})),e=c.attr("class"),e&&(e=""+e,a(e.split(" ")).each2(function(){0!==this.indexOf("select2-")&&(g=d(this),g&&f.push(this))})),b.attr("class",f.join(" "))}function C(a,c,d,e){var f=a.toUpperCase().indexOf(c.toUpperCase()),g=c.length;return 0>f?(d.push(e(a)),b):(d.push(e(a.substring(0,f))),d.push("<span class='select2-match'>"),d.push(e(a.substring(f,f+g))),d.push("</span>"),d.push(e(a.substring(f+g,a.length))),b)}function D(a){var b={"\\":"\","&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return(a+"").replace(/[&<>"'\/\\]/g,function(a){return b[a]})}function E(c){var d,e=0,f=null,g=c.quietMillis||100,h=c.url,i=this;return function(j){window.clearTimeout(d),d=window.setTimeout(function(){e+=1;var d=e,g=c.data,k=h,l=c.transport||a.fn.select2.ajaxDefaults.transport,m={type:c.type||"GET",cache:c.cache||!1,jsonpCallback:c.jsonpCallback||b,dataType:c.dataType||"json"},n=a.extend({},a.fn.select2.ajaxDefaults.params,m);g=g?g.call(i,j.term,j.page,j.context):null,k="function"==typeof k?k.call(i,j.term,j.page,j.context):k,f&&f.abort(),c.params&&(a.isFunction(c.params)?a.extend(n,c.params.call(i)):a.extend(n,c.params)),a.extend(n,{url:k,dataType:c.dataType,data:g,success:function(a){if(!(e>d)){var b=c.results(a,j.page);j.callback(b)}}}),f=l.call(i,n)},g)}}function F(c){var e,f,d=c,g=function(a){return""+a.text};a.isArray(d)&&(f=d,d={results:f}),a.isFunction(d)===!1&&(f=d,d=function(){return f});var h=d();return h.text&&(g=h.text,a.isFunction(g)||(e=h.text,g=function(a){return a[e]})),function(c){var h,e=c.term,f={results:[]};return""===e?(c.callback(d()),b):(h=function(b,d){var f,i;if(b=b[0],b.children){f={};for(i in b)b.hasOwnProperty(i)&&(f[i]=b[i]);f.children=[],a(b.children).each2(function(a,b){h(b,f.children)}),(f.children.length||c.matcher(e,g(f),b))&&d.push(f)}else c.matcher(e,g(b),b)&&d.push(b)},a(d().results).each2(function(a,b){h(b,f.results)}),c.callback(f),b)}}function G(c){var d=a.isFunction(c);return function(e){var f=e.term,g={results:[]};a(d?c():c).each(function(){var a=this.text!==b,c=a?this.text:this;(""===f||e.matcher(f,c))&&g.results.push(a?this:{id:this,text:this})}),e.callback(g)}}function H(b,c){if(a.isFunction(b))return!0;if(!b)return!1;throw Error(c+" must be a function or a falsy value")}function I(b){return a.isFunction(b)?b():b}function J(b){var c=0;return a.each(b,function(a,b){b.children?c+=J(b.children):c++}),c}function K(a,c,d,e){var h,i,j,k,l,f=a,g=!1;if(!e.createSearchChoice||!e.tokenSeparators||1>e.tokenSeparators.length)return b;for(;;){for(i=-1,j=0,k=e.tokenSeparators.length;k>j&&(l=e.tokenSeparators[j],i=a.indexOf(l),!(i>=0));j++);if(0>i)break;if(h=a.substring(0,i),a=a.substring(i+l.length),h.length>0&&(h=e.createSearchChoice.call(this,h,c),h!==b&&null!==h&&e.id(h)!==b&&null!==e.id(h))){for(g=!1,j=0,k=c.length;k>j;j++)if(o(e.id(h),e.id(c[j]))){g=!0;break}g||d(h)}}return f!==a?a:b}function L(b,c){var d=function(){};return d.prototype=new b,d.prototype.constructor=d,d.prototype.parent=b.prototype,d.prototype=a.extend(d.prototype,c),d}if(window.Select2===b){var c,d,e,f,g,h,j,k,i={x:0,y:0},c={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,isArrow:function(a){switch(a=a.which?a.which:a){case c.LEFT:case c.RIGHT:case c.UP:case c.DOWN:return!0}return!1},isControl:function(a){var b=a.which;switch(b){case c.SHIFT:case c.CTRL:case c.ALT:return!0}return a.metaKey?!0:!1},isFunctionKey:function(a){return a=a.which?a.which:a,a>=112&&123>=a}},l="<div class='select2-measure-scrollbar'></div>";j=a(document),g=function(){var a=1;return function(){return a++}}(),j.on("mousemove",function(a){i.x=a.pageX,i.y=a.pageY}),d=L(Object,{bind:function(a){var b=this;return function(){a.apply(b,arguments)}},init:function(c){var d,e,h,i,f=".select2-results";this.opts=c=this.prepareOpts(c),this.id=c.id,c.element.data("select2")!==b&&null!==c.element.data("select2")&&c.element.data("select2").destroy(),this.container=this.createContainer(),this.containerId="s2id_"+(c.element.attr("id")||"autogen"+g()),this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g,"\\$1"),this.container.attr("id",this.containerId),this.body=u(function(){return c.element.closest("body")}),B(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.css(I(c.containerCss)),this.container.addClass(I(c.containerCssClass)),this.elementTabIndex=this.opts.element.attr("tabindex"),this.opts.element.data("select2",this).attr("tabindex","-1").before(this.container),this.container.data("select2",this),this.dropdown=this.container.find(".select2-drop"),this.dropdown.addClass(I(c.dropdownCssClass)),this.dropdown.data("select2",this),this.results=d=this.container.find(f),this.search=e=this.container.find("input.select2-input"),this.resultsPage=0,this.context=null,this.initContainer(),s(this.results),this.dropdown.on("mousemove-filtered touchstart touchmove touchend",f,this.bind(this.highlightUnderEvent)),v(80,this.results),this.dropdown.on("scroll-debounced",f,this.bind(this.loadMoreIfNeeded)),a(this.container).on("change",".select2-input",function(a){a.stopPropagation()}),a(this.dropdown).on("change",".select2-input",function(a){a.stopPropagation()}),a.fn.mousewheel&&d.mousewheel(function(a,b,c,e){var f=d.scrollTop();e>0&&0>=f-e?(d.scrollTop(0),y(a)):0>e&&d.get(0).scrollHeight-d.scrollTop()+e<=d.height()&&(d.scrollTop(d.get(0).scrollHeight-d.height()),y(a))}),r(e),e.on("keyup-change input paste",this.bind(this.updateResults)),e.on("focus",function(){e.addClass("select2-focused")}),e.on("blur",function(){e.removeClass("select2-focused")}),this.dropdown.on("mouseup",f,this.bind(function(b){a(b.target).closest(".select2-result-selectable").length>0&&(this.highlightUnderEvent(b),this.selectHighlighted(b))})),this.dropdown.on("click mouseup mousedown",function(a){a.stopPropagation()}),a.isFunction(this.opts.initSelection)&&(this.initSelection(),this.monitorSource()),null!==c.maximumInputLength&&this.search.attr("maxlength",c.maximumInputLength);var h=c.element.prop("disabled");h===b&&(h=!1),this.enable(!h);var i=c.element.prop("readonly");i===b&&(i=!1),this.readonly(i),k=k||n(),this.autofocus=c.element.prop("autofocus"),c.element.prop("autofocus",!1),this.autofocus&&this.focus()},destroy:function(){var a=this.opts.element,c=a.data("select2");this.propertyObserver&&(delete this.propertyObserver,this.propertyObserver=null),c!==b&&(c.container.remove(),c.dropdown.remove(),a.removeClass("select2-offscreen").removeData("select2").off(".select2").prop("autofocus",this.autofocus||!1),this.elementTabIndex?a.attr({tabindex:this.elementTabIndex}):a.removeAttr("tabindex"),a.show())},optionToData:function(a){return a.is("option")?{id:a.prop("value"),text:a.text(),element:a.get(),css:a.attr("class"),disabled:a.prop("disabled"),locked:o(a.attr("locked"),"locked")||o(a.data("locked"),!0)}:a.is("optgroup")?{text:a.attr("label"),children:[],element:a.get(),css:a.attr("class")}:b},prepareOpts:function(c){var d,e,f,g,h=this;if(d=c.element,"select"===d.get(0).tagName.toLowerCase()&&(this.select=e=c.element),e&&a.each(["id","multiple","ajax","query","createSearchChoice","initSelection","data","tags"],function(){if(this in c)throw Error("Option '"+this+"' is not allowed for Select2 when attached to a <select> element.")}),c=a.extend({},{populateResults:function(d,e,f){var g,l=this.opts.id;g=function(d,e,i){var j,k,m,n,o,p,q,r,s,t;for(d=c.sortResults(d,e,f),j=0,k=d.length;k>j;j+=1)m=d[j],o=m.disabled===!0,n=!o&&l(m)!==b,p=m.children&&m.children.length>0,q=a("<li></li>"),q.addClass("select2-results-dept-"+i),q.addClass("select2-result"),q.addClass(n?"select2-result-selectable":"select2-result-unselectable"),o&&q.addClass("select2-disabled"),p&&q.addClass("select2-result-with-children"),q.addClass(h.opts.formatResultCssClass(m)),r=a(document.createElement("div")),r.addClass("select2-result-label"),t=c.formatResult(m,r,f,h.opts.escapeMarkup),t!==b&&r.html(t),q.append(r),p&&(s=a("<ul></ul>"),s.addClass("select2-result-sub"),g(m.children,s,i+1),q.append(s)),q.data("select2-data",m),e.append(q)},g(e,d,0)}},a.fn.select2.defaults,c),"function"!=typeof c.id&&(f=c.id,c.id=function(a){return a[f]}),a.isArray(c.element.data("select2Tags"))){if("tags"in c)throw"tags specified as both an attribute 'data-select2-tags' and in options of Select2 "+c.element.attr("id");c.tags=c.element.data("select2Tags")}if(e?(c.query=this.bind(function(a){var f,g,i,c={results:[],more:!1},e=a.term;i=function(b,c){var d;b.is("option")?a.matcher(e,b.text(),b)&&c.push(h.optionToData(b)):b.is("optgroup")&&(d=h.optionToData(b),b.children().each2(function(a,b){i(b,d.children)}),d.children.length>0&&c.push(d))},f=d.children(),this.getPlaceholder()!==b&&f.length>0&&(g=this.getPlaceholderOption(),g&&(f=f.not(g))),f.each2(function(a,b){i(b,c.results)}),a.callback(c)}),c.id=function(a){return a.id},c.formatResultCssClass=function(a){return a.css}):"query"in c||("ajax"in c?(g=c.element.data("ajax-url"),g&&g.length>0&&(c.ajax.url=g),c.query=E.call(c.element,c.ajax)):"data"in c?c.query=F(c.data):"tags"in c&&(c.query=G(c.tags),c.createSearchChoice===b&&(c.createSearchChoice=function(a){return{id:a,text:a}}),c.initSelection===b&&(c.initSelection=function(d,e){var f=[];a(p(d.val(),c.separator)).each(function(){var d=this,e=this,g=c.tags;a.isFunction(g)&&(g=g()),a(g).each(function(){return o(this.id,d)?(e=this.text,!1):b}),f.push({id:d,text:e})}),e(f)}))),"function"!=typeof c.query)throw"query function not defined for Select2 "+c.element.attr("id");return c},monitorSource:function(){var c,a=this.opts.element;a.on("change.select2",this.bind(function(){this.opts.element.data("select2-change-triggered")!==!0&&this.initSelection()})),c=this.bind(function(){var d,f=a.prop("disabled");f===b&&(f=!1),this.enable(!f);var d=a.prop("readonly");d===b&&(d=!1),this.readonly(d),B(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.addClass(I(this.opts.containerCssClass)),B(this.dropdown,this.opts.element,this.opts.adaptDropdownCssClass),this.dropdown.addClass(I(this.opts.dropdownCssClass))}),a.on("propertychange.select2 DOMAttrModified.select2",c),this.mutationCallback===b&&(this.mutationCallback=function(a){a.forEach(c)}),"undefined"!=typeof WebKitMutationObserver&&(this.propertyObserver&&(delete this.propertyObserver,this.propertyObserver=null),this.propertyObserver=new WebKitMutationObserver(this.mutationCallback),this.propertyObserver.observe(a.get(0),{attributes:!0,subtree:!1}))},triggerSelect:function(b){var c=a.Event("select2-selecting",{val:this.id(b),object:b});return this.opts.element.trigger(c),!c.isDefaultPrevented()},triggerChange:function(b){b=b||{},b=a.extend({},b,{type:"change",val:this.val()}),this.opts.element.data("select2-change-triggered",!0),this.opts.element.trigger(b),this.opts.element.data("select2-change-triggered",!1),this.opts.element.click(),this.opts.blurOnChange&&this.opts.element.blur()},isInterfaceEnabled:function(){return this.enabledInterface===!0},enableInterface:function(){var a=this._enabled&&!this._readonly,b=!a;return a===this.enabledInterface?!1:(this.container.toggleClass("select2-container-disabled",b),this.close(),this.enabledInterface=a,!0)},enable:function(a){return a===b&&(a=!0),this._enabled===a?!1:(this._enabled=a,this.opts.element.prop("disabled",!a),this.enableInterface(),!0)},readonly:function(a){return a===b&&(a=!1),this._readonly===a?!1:(this._readonly=a,this.opts.element.prop("readonly",a),this.enableInterface(),!0)},opened:function(){return this.container.hasClass("select2-dropdown-open")},positionDropdown:function(){var q,r,s,t,b=this.dropdown,c=this.container.offset(),d=this.container.outerHeight(!1),e=this.container.outerWidth(!1),f=b.outerHeight(!1),g=a(window).scrollLeft()+a(window).width(),h=a(window).scrollTop()+a(window).height(),i=c.top+d,j=c.left,l=h>=i+f,m=c.top-f>=this.body().scrollTop(),n=b.outerWidth(!1),o=g>=j+n,p=b.hasClass("select2-drop-above");this.opts.dropdownAutoWidth?(t=a(".select2-results",b)[0],b.addClass("select2-drop-auto-width"),b.css("width",""),n=b.outerWidth(!1)+(t.scrollHeight===t.clientHeight?0:k.width),n>e?e=n:n=e,o=g>=j+n):this.container.removeClass("select2-drop-auto-width"),"static"!==this.body().css("position")&&(q=this.body().offset(),i-=q.top,j-=q.left),p?(r=!0,!m&&l&&(r=!1)):(r=!1,!l&&m&&(r=!0)),o||(j=c.left+e-n),r?(i=c.top-f,this.container.addClass("select2-drop-above"),b.addClass("select2-drop-above")):(this.container.removeClass("select2-drop-above"),b.removeClass("select2-drop-above")),s=a.extend({top:i,left:j,width:e},I(this.opts.dropdownCss)),b.css(s)},shouldOpen:function(){var b;return this.opened()?!1:this._enabled===!1||this._readonly===!0?!1:(b=a.Event("select2-opening"),this.opts.element.trigger(b),!b.isDefaultPrevented())},clearDropdownAlignmentPreference:function(){this.container.removeClass("select2-drop-above"),this.dropdown.removeClass("select2-drop-above")},open:function(){return this.shouldOpen()?(this.opening(),!0):!1},opening:function(){function i(){return{width:Math.max(document.documentElement.scrollWidth,a(window).width()),height:Math.max(document.documentElement.scrollHeight,a(window).height())}}var f,g,b=this.containerId,c="scroll."+b,d="resize."+b,e="orientationchange."+b;this.container.addClass("select2-dropdown-open").addClass("select2-container-active"),this.clearDropdownAlignmentPreference(),this.dropdown[0]!==this.body().children().last()[0]&&this.dropdown.detach().appendTo(this.body()),f=a("#select2-drop-mask"),0==f.length&&(f=a(document.createElement("div")),f.attr("id","select2-drop-mask").attr("class","select2-drop-mask"),f.hide(),f.appendTo(this.body()),f.on("mousedown touchstart click",function(b){var d,c=a("#select2-drop");c.length>0&&(d=c.data("select2"),d.opts.selectOnBlur&&d.selectHighlighted({noFocus:!0}),d.close(),b.preventDefault(),b.stopPropagation())})),this.dropdown.prev()[0]!==f[0]&&this.dropdown.before(f),a("#select2-drop").removeAttr("id"),this.dropdown.attr("id","select2-drop"),g=i(),f.css(g).show(),this.dropdown.show(),this.positionDropdown(),this.dropdown.addClass("select2-drop-active");var h=this;this.container.parents().add(window).each(function(){a(this).on(d+" "+c+" "+e,function(){var c=i();a("#select2-drop-mask").css(c),h.positionDropdown()})})},close:function(){if(this.opened()){var b=this.containerId,c="scroll."+b,d="resize."+b,e="orientationchange."+b;this.container.parents().add(window).each(function(){a(this).off(c).off(d).off(e)}),this.clearDropdownAlignmentPreference(),a("#select2-drop-mask").hide(),this.dropdown.removeAttr("id"),this.dropdown.hide(),this.container.removeClass("select2-dropdown-open"),this.results.empty(),this.clearSearch(),this.search.removeClass("select2-active"),this.opts.element.trigger(a.Event("select2-close"))}},externalSearch:function(a){this.open(),this.search.val(a),this.updateResults(!1)},clearSearch:function(){},getMaximumSelectionSize:function(){return I(this.opts.maximumSelectionSize)},ensureHighlightVisible:function(){var d,e,f,g,h,i,j,c=this.results;if(e=this.highlight(),!(0>e)){if(0==e)return c.scrollTop(0),b;d=this.findHighlightableChoices().find(".select2-result-label"),f=a(d[e]),g=f.offset().top+f.outerHeight(!0),e===d.length-1&&(j=c.find("li.select2-more-results"),j.length>0&&(g=j.offset().top+j.outerHeight(!0))),h=c.offset().top+c.outerHeight(!0),g>h&&c.scrollTop(c.scrollTop()+(g-h)),i=f.offset().top-c.offset().top,0>i&&"none"!=f.css("display")&&c.scrollTop(c.scrollTop()+i)}},findHighlightableChoices:function(){return this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)")},moveHighlight:function(b){for(var c=this.findHighlightableChoices(),d=this.highlight();d>-1&&c.length>d;){d+=b;var e=a(c[d]);if(e.hasClass("select2-result-selectable")&&!e.hasClass("select2-disabled")&&!e.hasClass("select2-selected")){this.highlight(d);break}}},highlight:function(c){var e,f,d=this.findHighlightableChoices();return 0===arguments.length?m(d.filter(".select2-highlighted")[0],d.get()):(c>=d.length&&(c=d.length-1),0>c&&(c=0),this.results.find(".select2-highlighted").removeClass("select2-highlighted"),e=a(d[c]),e.addClass("select2-highlighted"),this.ensureHighlightVisible(),f=e.data("select2-data"),f&&this.opts.element.trigger({type:"select2-highlight",val:this.id(f),choice:f}),b)},countSelectableResults:function(){return this.findHighlightableChoices().length},highlightUnderEvent:function(b){var c=a(b.target).closest(".select2-result-selectable");if(c.length>0&&!c.is(".select2-highlighted")){var d=this.findHighlightableChoices();this.highlight(d.index(c))}else 0==c.length&&this.results.find(".select2-highlighted").removeClass("select2-highlighted")},loadMoreIfNeeded:function(){var c,a=this.results,b=a.find("li.select2-more-results"),e=this.resultsPage+1,f=this,g=this.search.val(),h=this.context;0!==b.length&&(c=b.offset().top-a.offset().top-a.height(),this.opts.loadMorePadding>=c&&(b.addClass("select2-active"),this.opts.query({element:this.opts.element,term:g,page:e,context:h,matcher:this.opts.matcher,callback:this.bind(function(c){f.opened()&&(f.opts.populateResults.call(this,a,c.results,{term:g,page:e,context:h}),f.postprocessResults(c,!1,!1),c.more===!0?(b.detach().appendTo(a).text(f.opts.formatLoadMore(e+1)),window.setTimeout(function(){f.loadMoreIfNeeded()},10)):b.remove(),f.positionDropdown(),f.resultsPage=e,f.context=c.context)})})))},tokenize:function(){},updateResults:function(c){function l(){d.removeClass("select2-active"),h.positionDropdown()}function m(a){e.html(a),l()}var g,i,d=this.search,e=this.results,f=this.opts,h=this,j=d.val(),k=a.data(this.container,"select2-last-term");if((c===!0||!k||!o(j,k))&&(a.data(this.container,"select2-last-term",j),c===!0||this.showSearchInput!==!1&&this.opened())){var n=this.getMaximumSelectionSize();if(n>=1&&(g=this.data(),a.isArray(g)&&g.length>=n&&H(f.formatSelectionTooBig,"formatSelectionTooBig")))return m("<li class='select2-selection-limit'>"+f.formatSelectionTooBig(n)+"</li>"),b;if(d.val().length<f.minimumInputLength)return H(f.formatInputTooShort,"formatInputTooShort")?m("<li class='select2-no-results'>"+f.formatInputTooShort(d.val(),f.minimumInputLength)+"</li>"):m(""),c&&this.showSearch&&this.showSearch(!0),b;if(f.maximumInputLength&&d.val().length>f.maximumInputLength)return H(f.formatInputTooLong,"formatInputTooLong")?m("<li class='select2-no-results'>"+f.formatInputTooLong(d.val(),f.maximumInputLength)+"</li>"):m(""),b;f.formatSearching&&0===this.findHighlightableChoices().length&&m("<li class='select2-searching'>"+f.formatSearching()+"</li>"),d.addClass("select2-active"),i=this.tokenize(),i!=b&&null!=i&&d.val(i),this.resultsPage=1,f.query({element:f.element,term:d.val(),page:this.resultsPage,context:null,matcher:f.matcher,callback:this.bind(function(g){var i;return this.opened()?(this.context=g.context===b?null:g.context,this.opts.createSearchChoice&&""!==d.val()&&(i=this.opts.createSearchChoice.call(h,d.val(),g.results),i!==b&&null!==i&&h.id(i)!==b&&null!==h.id(i)&&0===a(g.results).filter(function(){return o(h.id(this),h.id(i))}).length&&g.results.unshift(i)),0===g.results.length&&H(f.formatNoMatches,"formatNoMatches")?(m("<li class='select2-no-results'>"+f.formatNoMatches(d.val())+"</li>"),b):(e.empty(),h.opts.populateResults.call(this,e,g.results,{term:d.val(),page:this.resultsPage,context:null}),g.more===!0&&H(f.formatLoadMore,"formatLoadMore")&&(e.append("<li class='select2-more-results'>"+h.opts.escapeMarkup(f.formatLoadMore(this.resultsPage))+"</li>"),window.setTimeout(function(){h.loadMoreIfNeeded()},10)),this.postprocessResults(g,c),l(),this.opts.element.trigger({type:"select2-loaded",items:g}),b)):(this.search.removeClass("select2-active"),b)})})}},cancel:function(){this.close()},blur:function(){this.opts.selectOnBlur&&this.selectHighlighted({noFocus:!0}),this.close(),this.container.removeClass("select2-container-active"),this.search[0]===document.activeElement&&this.search.blur(),this.clearSearch(),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus")},focusSearch:function(){w(this.search)},selectHighlighted:function(a){var b=this.highlight(),c=this.results.find(".select2-highlighted"),d=c.closest(".select2-result").data("select2-data");d?(this.highlight(b),this.onSelect(d,a)):a&&a.noFocus&&this.close()},getPlaceholder:function(){var a;return this.opts.element.attr("placeholder")||this.opts.element.attr("data-placeholder")||this.opts.element.data("placeholder")||this.opts.placeholder||((a=this.getPlaceholderOption())!==b?a.text():b)},getPlaceholderOption:function(){if(this.select){var a=this.select.children().first();if(this.opts.placeholderOption!==b)return"first"===this.opts.placeholderOption&&a||"function"==typeof this.opts.placeholderOption&&this.opts.placeholderOption(this.select);if(""===a.text()&&""===a.val())return a}},initContainerWidth:function(){function c(){var c,d,e,f,g;if("off"===this.opts.width)return null;if("element"===this.opts.width)return 0===this.opts.element.outerWidth(!1)?"auto":this.opts.element.outerWidth(!1)+"px";if("copy"===this.opts.width||"resolve"===this.opts.width){if(c=this.opts.element.attr("style"),c!==b)for(d=c.split(";"),f=0,g=d.length;g>f;f+=1)if(e=d[f].replace(/\s/g,"").match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i),null!==e&&e.length>=1)return e[1];return"resolve"===this.opts.width?(c=this.opts.element.css("width"),c.indexOf("%")>0?c:0===this.opts.element.outerWidth(!1)?"auto":this.opts.element.outerWidth(!1)+"px"):null}return a.isFunction(this.opts.width)?this.opts.width():this.opts.width}var d=c.call(this);null!==d&&this.container.css("width",d)}}),e=L(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container"}).html(["<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>"," <span class='select2-chosen'> </span><abbr class='select2-search-choice-close'></abbr>"," <span class='select2-arrow'><b></b></span>","</a>","<input class='select2-focusser select2-offscreen' type='text'/>","<div class='select2-drop select2-display-none'>"," <div class='select2-search'>"," <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'/>"," </div>"," <ul class='select2-results'>"," </ul>","</div>"].join(""));return b},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.focusser.prop("disabled",!this.isInterfaceEnabled())},opening:function(){var b,c,d;this.opts.minimumResultsForSearch>=0&&this.showSearch(!0),this.parent.opening.apply(this,arguments),this.showSearchInput!==!1&&this.search.val(this.focusser.val()),this.search.focus(),b=this.search.get(0),b.createTextRange?(c=b.createTextRange(),c.collapse(!1),c.select()):b.setSelectionRange&&(d=this.search.val().length,b.setSelectionRange(d,d)),this.focusser.prop("disabled",!0).val(""),this.updateResults(!0),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&(this.parent.close.apply(this,arguments),this.focusser.removeAttr("disabled"),this.focusser.focus())},focus:function(){this.opened()?this.close():(this.focusser.removeAttr("disabled"),this.focusser.focus())},isFocused:function(){return this.container.hasClass("select2-container-active")},cancel:function(){this.parent.cancel.apply(this,arguments),this.focusser.removeAttr("disabled"),this.focusser.focus()},initContainer:function(){var d,e=this.container,f=this.dropdown;0>this.opts.minimumResultsForSearch?this.showSearch(!1):this.showSearch(!0),this.selection=d=e.find(".select2-choice"),this.focusser=e.find(".select2-focusser"),this.focusser.attr("id","s2id_autogen"+g()),a("label[for='"+this.opts.element.attr("id")+"']").attr("for",this.focusser.attr("id")),this.focusser.attr("tabindex",this.elementTabIndex),this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){if(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)return y(a),b;switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),y(a),b;case c.ENTER:return this.selectHighlighted(),y(a),b;case c.TAB:return this.selectHighlighted({noFocus:!0}),b;case c.ESC:return this.cancel(a),y(a),b}}})),this.search.on("blur",this.bind(function(){document.activeElement===this.body().get(0)&&window.setTimeout(this.bind(function(){this.search.focus()}),0)})),this.focusser.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()&&a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.ESC){if(this.opts.openOnEnter===!1&&a.which===c.ENTER)return y(a),b;if(a.which==c.DOWN||a.which==c.UP||a.which==c.ENTER&&this.opts.openOnEnter){if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return;return this.open(),y(a),b}return a.which==c.DELETE||a.which==c.BACKSPACE?(this.opts.allowClear&&this.clear(),y(a),b):b}})),r(this.focusser),this.focusser.on("keyup-change input",this.bind(function(a){if(this.opts.minimumResultsForSearch>=0){if(a.stopPropagation(),this.opened())return;this.open()}})),d.on("mousedown","abbr",this.bind(function(a){this.isInterfaceEnabled()&&(this.clear(),z(a),this.close(),this.selection.focus())})),d.on("mousedown",this.bind(function(b){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.opened()?this.close():this.isInterfaceEnabled()&&this.open(),y(b)})),f.on("mousedown",this.bind(function(){this.search.focus()})),d.on("focus",this.bind(function(a){y(a)})),this.focusser.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})).on("blur",this.bind(function(){this.opened()||(this.container.removeClass("select2-container-active"),this.opts.element.trigger(a.Event("select2-blur")))})),this.search.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.setPlaceholder()},clear:function(a){var b=this.selection.data("select2-data");if(b){var c=this.getPlaceholderOption();this.opts.element.val(c?c.val():""),this.selection.find(".select2-chosen").empty(),this.selection.removeData("select2-data"),this.setPlaceholder(),a!==!1&&(this.opts.element.trigger({type:"select2-removed",val:this.id(b),choice:b}),this.triggerChange({removed:b}))}},initSelection:function(){if(this.isPlaceholderOptionSelected())this.updateSelection([]),this.close(),this.setPlaceholder();else{var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.setPlaceholder())})}},isPlaceholderOptionSelected:function(){var a;return(a=this.getPlaceholderOption())!==b&&a.is(":selected")||""===this.opts.element.val()||this.opts.element.val()===b||null===this.opts.element.val()},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=a.find(":selected");b(c.optionToData(d))}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=c.val(),f=null;b.query({matcher:function(a,c,d){var g=o(e,b.id(d));return g&&(f=d),g},callback:a.isFunction(d)?function(){d(f)}:a.noop})}),b},getPlaceholder:function(){return this.select&&this.getPlaceholderOption()===b?b:this.parent.getPlaceholder.apply(this,arguments)},setPlaceholder:function(){var a=this.getPlaceholder();if(this.isPlaceholderOptionSelected()&&a!==b){if(this.select&&this.getPlaceholderOption()===b)return;this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(a)),this.selection.addClass("select2-default"),this.container.removeClass("select2-allowclear")}},postprocessResults:function(a,c,d){var e=0,f=this;if(this.findHighlightableChoices().each2(function(a,c){return o(f.id(c.data("select2-data")),f.opts.element.val())?(e=a,!1):b}),d!==!1&&(c===!0&&e>=0?this.highlight(e):this.highlight(0)),c===!0){var h=this.opts.minimumResultsForSearch;h>=0&&this.showSearch(J(a.results)>=h)}},showSearch:function(b){this.showSearchInput!==b&&(this.showSearchInput=b,this.dropdown.find(".select2-search").toggleClass("select2-search-hidden",!b),this.dropdown.find(".select2-search").toggleClass("select2-offscreen",!b),a(this.dropdown,this.container).toggleClass("select2-with-searchbox",b))},onSelect:function(a,b){if(this.triggerSelect(a)){var c=this.opts.element.val(),d=this.data();this.opts.element.val(this.id(a)),this.updateSelection(a),this.opts.element.trigger({type:"select2-selected",val:this.id(a),choice:a}),this.close(),b&&b.noFocus||this.selection.focus(),o(c,this.id(a))||this.triggerChange({added:a,removed:d})}},updateSelection:function(a){var d,e,c=this.selection.find(".select2-chosen");this.selection.data("select2-data",a),c.empty(),d=this.opts.formatSelection(a,c,this.opts.escapeMarkup),d!==b&&c.append(d),e=this.opts.formatSelectionCssClass(a,c),e!==b&&c.addClass(e),this.selection.removeClass("select2-default"),this.opts.allowClear&&this.getPlaceholder()!==b&&this.container.addClass("select2-allowclear")
+},val:function(){var a,c=!1,d=null,e=this,f=this.data();if(0===arguments.length)return this.opts.element.val();if(a=arguments[0],arguments.length>1&&(c=arguments[1]),this.select)this.select.val(a).find(":selected").each2(function(a,b){return d=e.optionToData(b),!1}),this.updateSelection(d),this.setPlaceholder(),c&&this.triggerChange({added:d,removed:f});else{if(!a&&0!==a)return this.clear(c),b;if(this.opts.initSelection===b)throw Error("cannot call val() if initSelection() is not defined");this.opts.element.val(a),this.opts.initSelection(this.opts.element,function(a){e.opts.element.val(a?e.id(a):""),e.updateSelection(a),e.setPlaceholder(),c&&e.triggerChange({added:a,removed:f})})}},clearSearch:function(){this.search.val(""),this.focusser.val("")},data:function(a,c){var d;return 0===arguments.length?(d=this.selection.data("select2-data"),d==b&&(d=null),d):(a&&""!==a?(d=this.data(),this.opts.element.val(a?this.id(a):""),this.updateSelection(a),c&&this.triggerChange({added:a,removed:d})):this.clear(c),b)}}),f=L(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container select2-container-multi"}).html(["<ul class='select2-choices'>"," <li class='select2-search-field'>"," <input type='text' autocomplete='off' autocorrect='off' autocapitilize='off' spellcheck='false' class='select2-input'>"," </li>","</ul>","<div class='select2-drop select2-drop-multi select2-display-none'>"," <ul class='select2-results'>"," </ul>","</div>"].join(""));return b},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=[];a.find(":selected").each2(function(a,b){d.push(c.optionToData(b))}),b(d)}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=p(c.val(),b.separator),f=[];b.query({matcher:function(c,d,g){var h=a.grep(e,function(a){return o(a,b.id(g))}).length;return h&&f.push(g),h},callback:a.isFunction(d)?function(){for(var a=[],c=0;e.length>c;c++)for(var g=e[c],h=0;f.length>h;h++){var i=f[h];if(o(g,b.id(i))){a.push(i),f.splice(h,1);break}}d(a)}:a.noop})}),b},selectChoice:function(a){var b=this.container.find(".select2-search-choice-focus");b.length&&a&&a[0]==b[0]||(b.length&&this.opts.element.trigger("choice-deselected",b),b.removeClass("select2-search-choice-focus"),a&&a.length&&(this.close(),a.addClass("select2-search-choice-focus"),this.opts.element.trigger("choice-selected",a)))},initContainer:function(){var e,d=".select2-choices";this.searchContainer=this.container.find(".select2-search-field"),this.selection=e=this.container.find(d);var f=this;this.selection.on("mousedown",".select2-search-choice",function(){f.search[0].focus(),f.selectChoice(a(this))}),this.search.attr("id","s2id_autogen"+g()),a("label[for='"+this.opts.element.attr("id")+"']").attr("for",this.search.attr("id")),this.search.on("input paste",this.bind(function(){this.isInterfaceEnabled()&&(this.opened()||this.open())})),this.search.attr("tabindex",this.elementTabIndex),this.keydowns=0,this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){++this.keydowns;var d=e.find(".select2-search-choice-focus"),f=d.prev(".select2-search-choice:not(.select2-locked)"),g=d.next(".select2-search-choice:not(.select2-locked)"),h=x(this.search);if(d.length&&(a.which==c.LEFT||a.which==c.RIGHT||a.which==c.BACKSPACE||a.which==c.DELETE||a.which==c.ENTER)){var i=d;return a.which==c.LEFT&&f.length?i=f:a.which==c.RIGHT?i=g.length?g:null:a.which===c.BACKSPACE?(this.unselect(d.first()),this.search.width(10),i=f.length?f:g):a.which==c.DELETE?(this.unselect(d.first()),this.search.width(10),i=g.length?g:null):a.which==c.ENTER&&(i=null),this.selectChoice(i),y(a),i&&i.length||this.open(),b}if((a.which===c.BACKSPACE&&1==this.keydowns||a.which==c.LEFT)&&0==h.offset&&!h.length)return this.selectChoice(e.find(".select2-search-choice:not(.select2-locked)").last()),y(a),b;if(this.selectChoice(null),this.opened())switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),y(a),b;case c.ENTER:return this.selectHighlighted(),y(a),b;case c.TAB:return this.selectHighlighted({noFocus:!0}),this.close(),b;case c.ESC:return this.cancel(a),y(a),b}if(a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.BACKSPACE&&a.which!==c.ESC){if(a.which===c.ENTER){if(this.opts.openOnEnter===!1)return;if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return}this.open(),(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)&&y(a),a.which===c.ENTER&&y(a)}}})),this.search.on("keyup",this.bind(function(){this.keydowns=0,this.resizeSearch()})),this.search.on("blur",this.bind(function(b){this.container.removeClass("select2-container-active"),this.search.removeClass("select2-focused"),this.selectChoice(null),this.opened()||this.clearSearch(),b.stopImmediatePropagation(),this.opts.element.trigger(a.Event("select2-blur"))})),this.container.on("click",d,this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").length>0||(this.selectChoice(null),this.clearPlaceholder(),this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.open(),this.focusSearch(),b.preventDefault()))})),this.container.on("focus",d,this.bind(function(){this.isInterfaceEnabled()&&(this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"),this.clearPlaceholder())})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.clearSearch()},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.search.prop("disabled",!this.isInterfaceEnabled())},initSelection:function(){if(""===this.opts.element.val()&&""===this.opts.element.text()&&(this.updateSelection([]),this.close(),this.clearSearch()),this.select||""!==this.opts.element.val()){var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.clearSearch())})}},clearSearch:function(){var a=this.getPlaceholder(),c=this.getMaxSearchWidth();a!==b&&0===this.getVal().length&&this.search.hasClass("select2-focused")===!1?(this.search.val(a).addClass("select2-default"),this.search.width(c>0?c:this.container.css("width"))):this.search.val("").width(10)},clearPlaceholder:function(){this.search.hasClass("select2-default")&&this.search.val("").removeClass("select2-default")},opening:function(){this.clearPlaceholder(),this.resizeSearch(),this.parent.opening.apply(this,arguments),this.focusSearch(),this.updateResults(!0),this.search.focus(),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&this.parent.close.apply(this,arguments)},focus:function(){this.close(),this.search.focus()},isFocused:function(){return this.search.hasClass("select2-focused")},updateSelection:function(b){var c=[],d=[],e=this;a(b).each(function(){0>m(e.id(this),c)&&(c.push(e.id(this)),d.push(this))}),b=d,this.selection.find(".select2-search-choice").remove(),a(b).each(function(){e.addSelectedChoice(this)}),e.postprocessResults()},tokenize:function(){var a=this.search.val();a=this.opts.tokenizer.call(this,a,this.data(),this.bind(this.onSelect),this.opts),null!=a&&a!=b&&(this.search.val(a),a.length>0&&this.open())},onSelect:function(a,b){this.triggerSelect(a)&&(this.addSelectedChoice(a),this.opts.element.trigger({type:"selected",val:this.id(a),choice:a}),(this.select||!this.opts.closeOnSelect)&&this.postprocessResults(),this.opts.closeOnSelect?(this.close(),this.search.width(10)):this.countSelectableResults()>0?(this.search.width(10),this.resizeSearch(),this.getMaximumSelectionSize()>0&&this.val().length>=this.getMaximumSelectionSize()&&this.updateResults(!0),this.positionDropdown()):(this.close(),this.search.width(10)),this.triggerChange({added:a}),b&&b.noFocus||this.focusSearch())},cancel:function(){this.close(),this.focusSearch()},addSelectedChoice:function(c){var j,k,d=!c.locked,e=a("<li class='select2-search-choice'> <div></div> <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a></li>"),f=a("<li class='select2-search-choice select2-locked'><div></div></li>"),g=d?e:f,h=this.id(c),i=this.getVal();j=this.opts.formatSelection(c,g.find("div"),this.opts.escapeMarkup),j!=b&&g.find("div").replaceWith("<div>"+j+"</div>"),k=this.opts.formatSelectionCssClass(c,g.find("div")),k!=b&&g.addClass(k),d&&g.find(".select2-search-choice-close").on("mousedown",y).on("click dblclick",this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").fadeOut("fast",this.bind(function(){this.unselect(a(b.target)),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"),this.close(),this.focusSearch()})).dequeue(),y(b))})).on("focus",this.bind(function(){this.isInterfaceEnabled()&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))})),g.data("select2-data",c),g.insertBefore(this.searchContainer),i.push(h),this.setVal(i)},unselect:function(a){var c,d,b=this.getVal();if(a=a.closest(".select2-search-choice"),0===a.length)throw"Invalid argument: "+a+". Must be .select2-search-choice";c=a.data("select2-data"),c&&(d=m(this.id(c),b),d>=0&&(b.splice(d,1),this.setVal(b),this.select&&this.postprocessResults()),a.remove(),this.opts.element.trigger({type:"removed",val:this.id(c),choice:c}),this.triggerChange({removed:c}))},postprocessResults:function(a,b,c){var d=this.getVal(),e=this.results.find(".select2-result"),f=this.results.find(".select2-result-with-children"),g=this;e.each2(function(a,b){var c=g.id(b.data("select2-data"));m(c,d)>=0&&(b.addClass("select2-selected"),b.find(".select2-result-selectable").addClass("select2-selected"))}),f.each2(function(a,b){b.is(".select2-result-selectable")||0!==b.find(".select2-result-selectable:not(.select2-selected)").length||b.addClass("select2-selected")}),-1==this.highlight()&&c!==!1&&g.highlight(0),!this.opts.createSearchChoice&&!e.filter(".select2-result:not(.select2-selected)").length>0&&(!a||a&&!a.more&&0===this.results.find(".select2-no-results").length)&&H(g.opts.formatNoMatches,"formatNoMatches")&&this.results.append("<li class='select2-no-results'>"+g.opts.formatNoMatches(g.search.val())+"</li>")},getMaxSearchWidth:function(){return this.selection.width()-q(this.search)},resizeSearch:function(){var a,b,c,d,e,f=q(this.search);a=A(this.search)+10,b=this.search.offset().left,c=this.selection.width(),d=this.selection.offset().left,e=c-(b-d)-f,a>e&&(e=c-f),40>e&&(e=c-f),0>=e&&(e=a),this.search.width(e)},getVal:function(){var a;return this.select?(a=this.select.val(),null===a?[]:a):(a=this.opts.element.val(),p(a,this.opts.separator))},setVal:function(b){var c;this.select?this.select.val(b):(c=[],a(b).each(function(){0>m(this,c)&&c.push(this)}),this.opts.element.val(0===c.length?"":c.join(this.opts.separator)))},buildChangeDetails:function(a,b){for(var b=b.slice(0),a=a.slice(0),c=0;b.length>c;c++)for(var d=0;a.length>d;d++)o(this.opts.id(b[c]),this.opts.id(a[d]))&&(b.splice(c,1),c--,a.splice(d,1),d--);return{added:b,removed:a}},val:function(c,d){var e,f=this;if(0===arguments.length)return this.getVal();if(e=this.data(),e.length||(e=[]),!c&&0!==c)return this.opts.element.val(""),this.updateSelection([]),this.clearSearch(),d&&this.triggerChange({added:this.data(),removed:e}),b;if(this.setVal(c),this.select)this.opts.initSelection(this.select,this.bind(this.updateSelection)),d&&this.triggerChange(this.buildChangeDetails(e,this.data()));else{if(this.opts.initSelection===b)throw Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,function(b){var c=a.map(b,f.id);f.setVal(c),f.updateSelection(b),f.clearSearch(),d&&f.triggerChange(this.buildChangeDetails(e,this.data()))})}this.clearSearch()},onSortStart:function(){if(this.select)throw Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");this.search.width(0),this.searchContainer.hide()},onSortEnd:function(){var b=[],c=this;this.searchContainer.show(),this.searchContainer.appendTo(this.searchContainer.parent()),this.resizeSearch(),this.selection.find(".select2-search-choice").each(function(){b.push(c.opts.id(a(this).data("select2-data")))}),this.setVal(b),this.triggerChange()},data:function(c,d){var f,g,e=this;return 0===arguments.length?this.selection.find(".select2-search-choice").map(function(){return a(this).data("select2-data")}).get():(g=this.data(),c||(c=[]),f=a.map(c,function(a){return e.opts.id(a)}),this.setVal(f),this.updateSelection(c),this.clearSearch(),d&&this.triggerChange(this.buildChangeDetails(g,this.data())),b)}}),a.fn.select2=function(){var d,g,h,i,j,c=Array.prototype.slice.call(arguments,0),k=["val","destroy","opened","open","close","focus","isFocused","container","dropdown","onSortStart","onSortEnd","enable","readonly","positionDropdown","data","search"],l=["val","opened","isFocused","container","data"],n={search:"externalSearch"};return this.each(function(){if(0===c.length||"object"==typeof c[0])d=0===c.length?{}:a.extend({},c[0]),d.element=a(this),"select"===d.element.get(0).tagName.toLowerCase()?j=d.element.prop("multiple"):(j=d.multiple||!1,"tags"in d&&(d.multiple=j=!0)),g=j?new f:new e,g.init(d);else{if("string"!=typeof c[0])throw"Invalid arguments to select2 plugin: "+c;if(0>m(c[0],k))throw"Unknown method: "+c[0];if(i=b,g=a(this).data("select2"),g===b)return;if(h=c[0],"container"===h?i=g.container:"dropdown"===h?i=g.dropdown:(n[h]&&(h=n[h]),i=g[h].apply(g,c.slice(1))),m(c[0],l)>=0)return!1}}),i===b?this:i},a.fn.select2.defaults={width:"copy",loadMorePadding:0,closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c,d){var e=[];return C(a.text,c.term,e,d),e.join("")},formatSelection:function(a,c,d){return a?d(a.text):b},sortResults:function(a){return a},formatResultCssClass:function(){return b},formatSelectionCssClass:function(){return b},formatNoMatches:function(){return"No matches found"},formatInputTooShort:function(a,b){var c=b-a.length;return"Please enter "+c+" more character"+(1==c?"":"s")},formatInputTooLong:function(a,b){var c=a.length-b;return"Please delete "+c+" character"+(1==c?"":"s")},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results..."},formatSearching:function(){return"Searching..."},minimumResultsForSearch:0,minimumInputLength:0,maximumInputLength:null,maximumSelectionSize:0,id:function(a){return a.id},matcher:function(a,b){return(""+b).toUpperCase().indexOf((""+a).toUpperCase())>=0},separator:",",tokenSeparators:[],tokenizer:K,escapeMarkup:D,blurOnChange:!1,selectOnBlur:!1,adaptContainerCssClass:function(a){return a},adaptDropdownCssClass:function(){return null}},a.fn.select2.ajaxDefaults={transport:a.ajax,params:{type:"GET",cache:!1,dataType:"json"}},window.Select2={query:{ajax:E,local:F,tags:G},util:{debounce:t,markMatch:C,escapeMarkup:D},"class":{"abstract":d,single:e,multi:f}}}}(jQuery);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.png b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.png
new file mode 100644
index 0000000..1d804ff
Binary files /dev/null and b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2.png differ
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ar.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ar.js
new file mode 100644
index 0000000..5ce0106
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ar.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 Arabic translation.
+ *
+ * Author: Your Name <amedhat3@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "لا توجد نتائج"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "من فضلك أدخل " + n + " حروف أكثر"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "من فضلك أحذف " + n + " حروف"; },
+ formatSelectionTooBig: function (limit) { return "يمكنك ان تختار " + limit + " أختيارات فقط"; },
+ formatLoadMore: function (pageNumber) { return "تحمل المذيد من النتائج ..."; },
+ formatSearching: function () { return "جاري البحث ..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ca.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ca.js
new file mode 100644
index 0000000..bdcdaa7
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ca.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 Catalan translation.
+ *
+ * Author: David Planella <david.planella@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "No s'ha trobat cap coincidència"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduïu " + n + " caràcter" + (n == 1 ? "" : "s") + " més"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Introduïu " + n + " caràcter" + (n == 1? "" : "s") + "menys"; },
+ formatSelectionTooBig: function (limit) { return "Només podeu seleccionar " + limit + " element" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "S'estan carregant més resultats..."; },
+ formatSearching: function () { return "S'està cercant..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_cs.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_cs.js
new file mode 100644
index 0000000..b3c748c
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_cs.js
@@ -0,0 +1,49 @@
+/**
+ * Select2 Czech translation.
+ *
+ * Author: Michal Marek <ahoj@michal-marek.cz>
+ * Author - sklonovani: David Vallner <david@vallner.net>
+ */
+(function ($) {
+ "use strict";
+ // use text for the numbers 2 through 4
+ var smallNumbers = {
+ 2: function(masc) { return (masc ? "dva" : "dvě"); },
+ 3: function() { return "tři"; },
+ 4: function() { return "čtyři"; }
+ }
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Nenalezeny žádné položky"; },
+ formatInputTooShort: function (input, min) {
+ var n = min - input.length;
+ if (n == 1) {
+ return "Prosím zadejte ještě jeden znak";
+ } else if (n <= 4) {
+ return "Prosím zadejte ještě další "+smallNumbers[n](true)+" znaky";
+ } else {
+ return "Prosím zadejte ještě dalších "+n+" znaků";
+ }
+ },
+ formatInputTooLong: function (input, max) {
+ var n = input.length - max;
+ if (n == 1) {
+ return "Prosím zadejte o jeden znak méně";
+ } else if (n <= 4) {
+ return "Prosím zadejte o "+smallNumbers[n](true)+" znaky méně";
+ } else {
+ return "Prosím zadejte o "+n+" znaků méně";
+ }
+ },
+ formatSelectionTooBig: function (limit) {
+ if (limit == 1) {
+ return "Můžete zvolit jen jednu položku";
+ } else if (limit <= 4) {
+ return "Můžete zvolit maximálně "+smallNumbers[limit](false)+" položky";
+ } else {
+ return "Můžete zvolit maximálně "+limit+" položek";
+ }
+ },
+ formatLoadMore: function (pageNumber) { return "Načítají se další výsledky..."; },
+ formatSearching: function () { return "Vyhledávání..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_da.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_da.js
new file mode 100644
index 0000000..dbce3e1
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_da.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 Danish translation.
+ *
+ * Author: Anders Jenbo <anders@jenbo.dk>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Ingen resultater fundet"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Angiv venligst " + n + " tegn mere"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Angiv venligst " + n + " tegn mindre"; },
+ formatSelectionTooBig: function (limit) { return "Du kan kun vælge " + limit + " emne" + (limit === 1 ? "" : "r"); },
+ formatLoadMore: function (pageNumber) { return "Indlæser flere resultater…"; },
+ formatSearching: function () { return "Søger…"; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_de.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_de.js
new file mode 100644
index 0000000..01f94ed
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_de.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 German translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Keine Übereinstimmungen gefunden"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Bitte " + n + " Zeichen mehr eingeben"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Bitte " + n + " Zeichen weniger eingeben"; },
+ formatSelectionTooBig: function (limit) { return "Sie können nur " + limit + " Eintr" + (limit === 1 ? "ag" : "äge") + " auswählen"; },
+ formatLoadMore: function (pageNumber) { return "Lade mehr Ergebnisse..."; },
+ formatSearching: function () { return "Suche..."; }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_el.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_el.js
new file mode 100644
index 0000000..65b6e07
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_el.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 <Language> translation.
+ *
+ * Author: Your Name <your@email>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Δεν βρέθηκαν αποτελέσματα"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Παρακαλούμε εισάγετε " + n + " περισσότερους χαρακτήρες" + (n == 1 ? "" : "s"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Παρακαλούμε διαγράψτε " + n + " χαρακτήρες" + (n == 1 ? "" : "s"); },
+ formatSelectionTooBig: function (limit) { return "Μπορείτε να επιλέξετε μόνο " + limit + " αντικείμενο" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Φόρτωση περισσότερων..."; },
+ formatSearching: function () { return "Αναζήτηση..."; }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_en.js.template b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_en.js.template
new file mode 100644
index 0000000..f76b374
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_en.js.template
@@ -0,0 +1,17 @@
+/**
+ * Select2 <Language> translation.
+ *
+ * Author: Your Name <your@email>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "No matches found"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1 ? "" : "s"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); },
+ formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Loading more results..."; },
+ formatSearching: function () { return "Searching..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_es.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_es.js
new file mode 100644
index 0000000..1b0a021
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_es.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Spanish translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "No se encontraron resultados"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Por favor adicione " + n + " caracter" + (n == 1? "" : "es"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Por favor elimine " + n + " caracter" + (n == 1? "" : "es"); },
+ formatSelectionTooBig: function (limit) { return "Solo puede seleccionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Cargando más resultados..."; },
+ formatSearching: function () { return "Buscando..."; }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_et.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_et.js
new file mode 100644
index 0000000..a4045d2
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_et.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 Estonian translation.
+ *
+ * Author: Kuldar Kalvik <kuldar@kalvik.ee>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Tulemused puuduvad"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Sisesta " + n + " täht" + (n == 1 ? "" : "e") + " rohkem"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Sisesta " + n + " täht" + (n == 1? "" : "e") + " vähem"; },
+ formatSelectionTooBig: function (limit) { return "Saad vaid " + limit + " tulemus" + (limit == 1 ? "e" : "t") + " valida"; },
+ formatLoadMore: function (pageNumber) { return "Laen tulemusi.."; },
+ formatSearching: function () { return "Otsin.."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_eu.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_eu.js
new file mode 100644
index 0000000..05665f5
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_eu.js
@@ -0,0 +1,43 @@
+/**
+ * Select2 Basque translation.
+ *
+ * Author: Julen Ruiz Aizpuru <julenx at gmail dot com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () {
+ return "Ez da bat datorrenik aurkitu";
+ },
+ formatInputTooShort: function (input, min) {
+ var n = min - input.length;
+ if (n === 1) {
+ return "Idatzi karaktere bat gehiago";
+ } else {
+ return "Idatzi " + n + " karaktere gehiago";
+ }
+ },
+ formatInputTooLong: function (input, max) {
+ var n = input.length - max;
+ if (n === 1) {
+ return "Idatzi karaktere bat gutxiago";
+ } else {
+ return "Idatzi " + n + " karaktere gutxiago";
+ }
+ },
+ formatSelectionTooBig: function (limit) {
+ if (limit === 1 ) {
+ return "Elementu bakarra hauta dezakezu";
+ } else {
+ return limit + " elementu hauta ditzakezu soilik";
+ }
+ },
+ formatLoadMore: function (pageNumber) {
+ return "Emaitza gehiago kargatzen...";
+ },
+ formatSearching: function () {
+ return "Bilatzen...";
+ }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_fi.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_fi.js
new file mode 100644
index 0000000..fa0917f
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_fi.js
@@ -0,0 +1,28 @@
+/**
+ * Select2 Finnish translation
+ */
+(function ($) {
+ "use strict";
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () {
+ return "Ei tuloksia";
+ },
+ formatInputTooShort: function (input, min) {
+ var n = min - input.length;
+ return "Ole hyvä ja anna " + n + " merkkiä lisää.";
+ },
+ formatInputTooLong: function (input, max) {
+ var n = input.length - max;
+ return "Ole hyvä ja annar " + n + " merkkiä vähemmän.";
+ },
+ formatSelectionTooBig: function (limit) {
+ return "Voit valita ainoastaan " + limit + " kpl";
+ },
+ formatLoadMore: function (pageNumber) {
+ return "Ladataan lisää tuloksia...";
+ },
+ formatSearching: function () {
+ return "Etsitään...";
+ }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_fr.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_fr.js
new file mode 100644
index 0000000..f8d3e48
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_fr.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 French translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Aucun résultat trouvé"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Merci de saisir " + n + " caractère" + (n == 1? "" : "s") + " de plus"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Merci de supprimer " + n + " caractère" + (n == 1? "" : "s"); },
+ formatSelectionTooBig: function (limit) { return "Vous pouvez seulement sélectionner " + limit + " élément" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Chargement de résultats supplémentaires..."; },
+ formatSearching: function () { return "Recherche en cours..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_gl.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_gl.js
new file mode 100644
index 0000000..1017c20
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_gl.js
@@ -0,0 +1,43 @@
+/**
+ * Select2 Galician translation
+ *
+ * Author: Leandro Regueiro <leandro.regueiro@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () {
+ return "Non se atoparon resultados";
+ },
+ formatInputTooShort: function (input, min) {
+ var n = min - input.length;
+ if (n === 1) {
+ return "Engada un carácter";
+ } else {
+ return "Engada " + n + " caracteres";
+ }
+ },
+ formatInputTooLong: function (input, max) {
+ var n = input.length - max;
+ if (n === 1) {
+ return "Elimine un carácter";
+ } else {
+ return "Elimine " + n + " caracteres";
+ }
+ },
+ formatSelectionTooBig: function (limit) {
+ if (limit === 1 ) {
+ return "Só pode seleccionar un elemento";
+ } else {
+ return "Só pode seleccionar " + limit + " elementos";
+ }
+ },
+ formatLoadMore: function (pageNumber) {
+ return "Cargando máis resultados...";
+ },
+ formatSearching: function () {
+ return "Buscando...";
+ }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_he.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_he.js
new file mode 100644
index 0000000..dd72eaa
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_he.js
@@ -0,0 +1,17 @@
+/**
+* Select2 Hebrew translation.
+*
+* Author: Yakir Sitbon <http://www.yakirs.net/>
+*/
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "לא נמצאו התאמות"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "נא להזין עוד " + n + " תווים נוספים"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "נא להזין פחות " + n + " תווים"; },
+ formatSelectionTooBig: function (limit) { return "ניתן לבחור " + limit + " פריטים"; },
+ formatLoadMore: function (pageNumber) { return "טוען תוצאות נוספות..."; },
+ formatSearching: function () { return "מחפש..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_hr.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_hr.js
new file mode 100644
index 0000000..b061540
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_hr.js
@@ -0,0 +1,42 @@
+/**
+ * Select2 Croatian translation.
+ *
+ * Author: Edi Modrić <edi.modric@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ var specialNumbers = {
+ 1: function(n) { return (n % 100 != 11 ? "znak" : "znakova"); },
+ 2: function(n) { return (n % 100 != 12 ? "znaka" : "znakova"); },
+ 3: function(n) { return (n % 100 != 13 ? "znaka" : "znakova"); },
+ 4: function(n) { return (n % 100 != 14 ? "znaka" : "znakova"); }
+ };
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Nema rezultata"; },
+ formatInputTooShort: function (input, min) {
+ var n = min - input.length;
+ var nMod10 = n % 10;
+
+ if (nMod10 > 0 && nMod10 < 5) {
+ return "Unesite još " + n + " " + specialNumbers[nMod10](n);
+ }
+
+ return "Unesite još " + n + " znakova";
+ },
+ formatInputTooLong: function (input, max) {
+ var n = input.length - max;
+ var nMod10 = n % 10;
+
+ if (nMod10 > 0 && nMod10 < 5) {
+ return "Unesite " + n + " " + specialNumbers[nMod10](n) + " manje";
+ }
+
+ return "Unesite " + n + " znakova manje";
+ },
+ formatSelectionTooBig: function (limit) { return "Maksimalan broj odabranih stavki je " + limit; },
+ formatLoadMore: function (pageNumber) { return "Učitavanje rezultata..."; },
+ formatSearching: function () { return "Pretraga..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_hu.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_hu.js
new file mode 100644
index 0000000..572dea9
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_hu.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Hungarian translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Nincs találat."; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Túl rövid. Még " + n + " karakter hiányzik."; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Túl hosszú. " + n + " kerekterrel több mint kellene."; },
+ formatSelectionTooBig: function (limit) { return "Csak " + limit + " elemet lehet kiválasztani."; },
+ formatLoadMore: function (pageNumber) { return "Töltés..."; },
+ formatSearching: function () { return "Keresés..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_id.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_id.js
new file mode 100644
index 0000000..59a896a
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_id.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 Indonesian translation.
+ *
+ * Author: Ibrahim Yusuf <ibrahim7usuf@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Tidak ada data yang sesuai"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Masukkan " + n + " huruf lagi" + (n == 1 ? "" : "s"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Hapus " + n + " huruf" + (n == 1 ? "" : "s"); },
+ formatSelectionTooBig: function (limit) { return "Anda hanya dapat memilih " + limit + " pilihan" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Mengambil data..."; },
+ formatSearching: function () { return "Mencari..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_is.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_is.js
new file mode 100644
index 0000000..b10073b
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_is.js
@@ -0,0 +1,16 @@
+/**
+ * Select2 Icelandic translation.
+ *
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Ekkert fannst"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vinsamlegast skrifið " + n + " staf" + (n == 1 ? "" : "i") + " í viðbót"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vinsamlegast styttið texta um " + n + " staf" + (n == 1 ? "" : "i"); },
+ formatSelectionTooBig: function (limit) { return "Þú getur aðeins valið " + limit + " atriði"; },
+ formatLoadMore: function (pageNumber) { return "Sæki fleiri niðurstöður..."; },
+ formatSearching: function () { return "Leita..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_it.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_it.js
new file mode 100644
index 0000000..98369dd
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_it.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Italian translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Nessuna corrispondenza trovata"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Inserisci ancora " + n + " caratter" + (n == 1? "e" : "i"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Inserisci " + n + " caratter" + (n == 1? "e" : "i") + " in meno"; },
+ formatSelectionTooBig: function (limit) { return "Puoi selezionare solo " + limit + " element" + (limit == 1 ? "o" : "i"); },
+ formatLoadMore: function (pageNumber) { return "Caricamento in corso..."; },
+ formatSearching: function () { return "Ricerca..."; }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ja.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ja.js
new file mode 100644
index 0000000..81106e7
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ja.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Japanese translation.
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "該当なし"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "後" + n + "文字入れてください"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "検索文字列が" + n + "文字長すぎます"; },
+ formatSelectionTooBig: function (limit) { return "最多で" + limit + "項目までしか選択できません"; },
+ formatLoadMore: function (pageNumber) { return "読込中・・・"; },
+ formatSearching: function () { return "検索中・・・"; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ko.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ko.js
new file mode 100644
index 0000000..864906b
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ko.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 <Language> translation.
+ *
+ * Author: Swen Mun <longfinfunnel@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "결과 없음"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "너무 짧습니다. "+n+"글자 더 입력해주세요."; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "너무 깁니다. "+n+"글자 지워주세요."; },
+ formatSelectionTooBig: function (limit) { return "최대 "+limit+"개까지만 선택하실 수 있습니다."; },
+ formatLoadMore: function (pageNumber) { return "불러오는 중…"; },
+ formatSearching: function () { return "검색 중…"; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_lt.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_lt.js
new file mode 100644
index 0000000..dbb1f09
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_lt.js
@@ -0,0 +1,29 @@
+/**
+ * Select2 lithuanian translation.
+ *
+ * Author: CRONUS Karmalakas <cronus dot karmalakas at gmail dot com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Atitikmenų nerasta"; },
+ formatInputTooShort: function (input, min) {
+ var n = min - input.length,
+ suffix = (n % 10 == 1) && (n % 100 != 11) ? 'į' : (((n % 10 >= 2) && ((n % 100 < 10) || (n % 100 >= 20))) ? 'ius' : 'ių');
+ return "Įrašykite dar " + n + " simbol" + suffix;
+ },
+ formatInputTooLong: function (input, max) {
+ var n = input.length - max,
+ suffix = (n % 10 == 1) && (n % 100 != 11) ? 'į' : (((n % 10 >= 2) && ((n % 100 < 10) || (n % 100 >= 20))) ? 'ius' : 'ių');
+ return "Pašalinkite " + n + " simbol" + suffix;
+ },
+ formatSelectionTooBig: function (limit) {
+ var n = limit,
+ suffix = (n % 10 == 1) && (n % 100 != 11) ? 'ą' : (((n % 10 >= 2) && ((n % 100 < 10) || (n % 100 >= 20))) ? 'us' : 'ų');
+ return "Jūs galite pasirinkti tik " + limit + " element" + suffix;
+ },
+ formatLoadMore: function (pageNumber) { return "Kraunama daugiau rezultatų..."; },
+ formatSearching: function () { return "Ieškoma..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_lv.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_lv.js
new file mode 100644
index 0000000..2c05cfd
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_lv.js
@@ -0,0 +1,16 @@
+/**
+ * Select2 Latvian translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Sakritību nav"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Lūdzu ievadiet vēl " + n + " simbol" + (n == 11 ? "us" : (/^\d*[1]$/im.test(n)? "u" : "us")); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Lūdzu ievadiet par " + n + " simbol" + (n == 11 ? "iem" : (/^\d*[1]$/im.test(n)? "u" : "iem")) + " mazāk"; },
+ formatSelectionTooBig: function (limit) { return "Jūs varat izvēlēties ne vairāk kā " + limit + " element" + (limit == 11 ? "us" : (/^\d*[1]$/im.test(limit)? "u" : "us")); },
+ formatLoadMore: function (pageNumber) { return "Datu ielāde..."; },
+ formatSearching: function () { return "Meklēšana..."; }
+ });
+
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_mk.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_mk.js
new file mode 100644
index 0000000..69e3981
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_mk.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 Macedonian translation.
+ *
+ * Author: Marko Aleksic <psybaron@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Нема пронајдено совпаѓања"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Ве молиме внесете уште " + n + " карактер" + (n == 1 ? "" : "и"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Ве молиме внесете " + n + " помалку карактер" + (n == 1? "" : "и"); },
+ formatSelectionTooBig: function (limit) { return "Можете да изберете само " + limit + " ставк" + (limit == 1 ? "а" : "и"); },
+ formatLoadMore: function (pageNumber) { return "Вчитување резултати..."; },
+ formatSearching: function () { return "Пребарување..."; }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_nl.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_nl.js
new file mode 100644
index 0000000..2ee2662
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_nl.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Dutch translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Geen resultaten gevonden"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vul " + n + " karakter" + (n == 1? "" : "s") + " meer in"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vul " + n + " karakter" + (n == 1? "" : "s") + " minder in"; },
+ formatSelectionTooBig: function (limit) { return "Maximaal " + limit + " item" + (limit == 1 ? "" : "s") + " toegestaan"; },
+ formatLoadMore: function (pageNumber) { return "Meer resultaten laden..."; },
+ formatSearching: function () { return "Zoeken..."; },
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_no.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_no.js
new file mode 100644
index 0000000..0831360
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_no.js
@@ -0,0 +1,18 @@
+/**
+ * Select2 Norwegian translation.
+ *
+ * Author: Torgeir Veimo <torgeir.veimo@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Ingen treff"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vennligst skriv inn " + n + (n>1 ? " flere tegn" : " tegn til"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vennligst fjern " + n + " tegn"; },
+ formatSelectionTooBig: function (limit) { return "Du kan velge maks " + limit + " elementer"; },
+ formatLoadMore: function (pageNumber) { return "Laster flere resultater..."; },
+ formatSearching: function () { return "Søker..."; }
+ });
+})(jQuery);
+
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pl.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pl.js
new file mode 100644
index 0000000..1d5b327
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pl.js
@@ -0,0 +1,37 @@
+/**
+ * Select2 Polish translation.
+ *
+ * Author: Jan Kondratowicz <jan@kondratowicz.pl>
+ */
+(function ($) {
+ "use strict";
+
+ var pl_suffix = function(n) {
+ if(n == 1) return "";
+ if((n%100 > 1 && n%100 < 5) || (n%100 > 20 && n%10 > 1 && n%10 < 5)) return "i";
+ return "ów";
+ };
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () {
+ return "Brak wyników.";
+ },
+ formatInputTooShort: function (input, min) {
+ var n = min - input.length;
+ return "Wpisz jeszcze " + n + " znak" + pl_suffix(n) + ".";
+ },
+ formatInputTooLong: function (input, max) {
+ var n = input.length - max;
+ return "Wpisana fraza jest za długa o " + n + " znak" + pl_suffix(n) + ".";
+ },
+ formatSelectionTooBig: function (limit) {
+ return "Możesz zaznaczyć najwyżej " + limit + " element" + pl_suffix(limit) + ".";
+ },
+ formatLoadMore: function (pageNumber) {
+ return "Ładowanie wyników...";
+ },
+ formatSearching: function () {
+ return "Szukanie...";
+ }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pt-BR.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pt-BR.js
new file mode 100644
index 0000000..701fb7f
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pt-BR.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Brazilian Portuguese translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Nenhum resultado encontrado"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Informe " + n + " caracter" + (n == 1? "" : "es"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " caracter" + (n == 1? "" : "es"); },
+ formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Carregando mais resultados..."; },
+ formatSearching: function () { return "Buscando..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pt-PT.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pt-PT.js
new file mode 100644
index 0000000..008653e
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_pt-PT.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Portuguese (Portugal) translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Nenhum resultado encontrado"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduza " + n + " caracter" + (n == 1 ? "" : "es"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " caracter" + (n == 1 ? "" : "es"); },
+ formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "A carregar mais resultados..."; },
+ formatSearching: function () { return "A pesquisar..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ro.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ro.js
new file mode 100644
index 0000000..88b3ac4
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ro.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Romanian translation.
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Nu a fost găsit nimic"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vă rugăm să introduceți incă " + n + " caracter" + (n == 1 ? "" : "e"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vă rugăm să introduceți mai puțin de " + n + " caracter" + (n == 1? "" : "e"); },
+ formatSelectionTooBig: function (limit) { return "Aveți voie să selectați cel mult " + limit + " element" + (limit == 1 ? "" : "e"); },
+ formatLoadMore: function (pageNumber) { return "Se încarcă..."; },
+ formatSearching: function () { return "Căutare..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ru.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ru.js
new file mode 100644
index 0000000..3da956a
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ru.js
@@ -0,0 +1,15 @@
+/**
+ * Select2 Russian translation
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Совпадений не найдено"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Пожалуйста, введите еще " + n + " символ" + (n == 1 ? "" : ((n > 1)&&(n < 5) ? "а" : "ов")); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Пожалуйста, введите на " + n + " символ" + (n == 1 ? "" : ((n > 1)&&(n < 5)? "а" : "ов")) + " меньше"; },
+ formatSelectionTooBig: function (limit) { return "Вы можете выбрать не более " + limit + " элемент" + (limit == 1 ? "а" : "ов"); },
+ formatLoadMore: function (pageNumber) { return "Загрузка данных..."; },
+ formatSearching: function () { return "Поиск..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_sk.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_sk.js
new file mode 100644
index 0000000..8d4e46a
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_sk.js
@@ -0,0 +1,48 @@
+/**
+ * Select2 Slovak translation.
+ *
+ * Author: David Vallner <david@vallner.net>
+ */
+(function ($) {
+ "use strict";
+ // use text for the numbers 2 through 4
+ var smallNumbers = {
+ 2: function(masc) { return (masc ? "dva" : "dve"); },
+ 3: function() { return "tri"; },
+ 4: function() { return "štyri"; }
+ }
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Nenašli sa žiadne položky"; },
+ formatInputTooShort: function (input, min) {
+ var n = min - input.length;
+ if (n == 1) {
+ return "Prosím zadajte ešte jeden znak";
+ } else if (n <= 4) {
+ return "Prosím zadajte ešte ďalšie "+smallNumbers[n](true)+" znaky";
+ } else {
+ return "Prosím zadajte ešte ďalších "+n+" znakov";
+ }
+ },
+ formatInputTooLong: function (input, max) {
+ var n = input.length - max;
+ if (n == 1) {
+ return "Prosím zadajte o jeden znak menej";
+ } else if (n <= 4) {
+ return "Prosím zadajte o "+smallNumbers[n](true)+" znaky menej";
+ } else {
+ return "Prosím zadajte o "+n+" znakov menej";
+ }
+ },
+ formatSelectionTooBig: function (limit) {
+ if (limit == 1) {
+ return "Môžete zvoliť len jednu položku";
+ } else if (limit <= 4) {
+ return "Môžete zvoliť najviac "+smallNumbers[limit](false)+" položky";
+ } else {
+ return "Môžete zvoliť najviac "+limit+" položiek";
+ }
+ },
+ formatLoadMore: function (pageNumber) { return "Načítavajú sa ďalšie výsledky..."; },
+ formatSearching: function () { return "Vyhľadávanie..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_sv.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_sv.js
new file mode 100644
index 0000000..9f09de3
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_sv.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 Swedish translation.
+ *
+ * Author: Jens Rantil <jens.rantil@telavox.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Inga träffar"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Var god skriv in " + n + (n>1 ? " till tecken" : " tecken till"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Var god sudda ut " + n + " tecken"; },
+ formatSelectionTooBig: function (limit) { return "Du kan max välja " + limit + " element"; },
+ formatLoadMore: function (pageNumber) { return "Laddar fler resultat..."; },
+ formatSearching: function () { return "Söker..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_tr.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_tr.js
new file mode 100644
index 0000000..b47a2fa
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_tr.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 Turkish translation.
+ *
+ * Author: Salim KAYABAŞI <salim.kayabasi@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Sonuç bulunamadı"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "En az " + n + " karakter daha girmelisiniz"; },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return n + " karakter azaltmalısınız"; },
+ formatSelectionTooBig: function (limit) { return "Sadece " + limit + " seçim yapabilirsiniz"; },
+ formatLoadMore: function (pageNumber) { return "Daha fazla..."; },
+ formatSearching: function () { return "Aranıyor..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ua.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ua.js
new file mode 100644
index 0000000..58d31e7
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_ua.js
@@ -0,0 +1,17 @@
+/**
+ * Select2 <Language> translation.
+ *
+ * Author: bigmihail <bigmihail@bigmir.net>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Нічого не знайдено"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length, s = ["", "и", "ів"], p = [2,0,1,1,1,2]; return "Введіть буль ласка ще " + n + " символ" + s[ (n%100>4 && n%100<=20)? 2 : p[Math.min(n%10, 5)] ]; },
+ formatInputTooLong: function (input, max) { var n = input.length - max, s = ["", "и", "ів"], p = [2,0,1,1,1,2]; return "Введіть буль ласка на " + n + " символ" + s[ (n%100>4 && n%100<=20)? 2 : p[Math.min(n%10, 5)] ] + " менше"; },
+ formatSelectionTooBig: function (limit) {var s = ["", "и", "ів"], p = [2,0,1,1,1,2]; return "Ви можете вибрати лише " + limit + " елемент" + s[ (limit%100>4 && limit%100<=20)? 2 : p[Math.min(limit%10, 5)] ]; },
+ formatLoadMore: function (pageNumber) { return "Завантаження даних..."; },
+ formatSearching: function () { return "Пошук..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_vi.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_vi.js
new file mode 100644
index 0000000..0a45dfc
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_vi.js
@@ -0,0 +1,18 @@
+/**
+ * Select2 Vietnamese translation.
+ *
+ * Author: Long Nguyen <olragon@gmail.com>
+ */
+(function ($) {
+ "use strict";
+
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "Không tìm thấy kết quả"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vui lòng nhập nhiều hơn " + n + " ký tự" + (n == 1 ? "" : "s"); },
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vui lòng nhập ít hơn " + n + " ký tự" + (n == 1? "" : "s"); },
+ formatSelectionTooBig: function (limit) { return "Chỉ có thể chọn được " + limit + " tùy chọn" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Đang lấy thêm kết quả..."; },
+ formatSearching: function () { return "Đang tìm..."; }
+ });
+})(jQuery);
+
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_zh-CN.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_zh-CN.js
new file mode 100644
index 0000000..49d8e59
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_zh-CN.js
@@ -0,0 +1,14 @@
+/**
+ * Select2 Chinese translation
+ */
+(function ($) {
+ "use strict";
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "没有找到匹配项"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "请再输入" + n + "个字符";},
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "请删掉" + n + "个字符";},
+ formatSelectionTooBig: function (limit) { return "你只能选择最多" + limit + "项"; },
+ formatLoadMore: function (pageNumber) { return "加载结果中..."; },
+ formatSearching: function () { return "搜索中..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_zh-TW.js b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_zh-TW.js
new file mode 100755
index 0000000..3d447d6
--- /dev/null
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2_locale_zh-TW.js
@@ -0,0 +1,14 @@
+/**
+ * Select2 Traditional Chinese translation
+ */
+(function ($) {
+ "use strict";
+ $.extend($.fn.select2.defaults, {
+ formatNoMatches: function () { return "沒有找到相符的項目"; },
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "請再輸入" + n + "個字元";},
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "請刪掉" + n + "個字元";},
+ formatSelectionTooBig: function (limit) { return "你只能選擇最多" + limit + "項"; },
+ formatLoadMore: function (pageNumber) { return "載入中..."; },
+ formatSearching: function () { return "搜尋中..."; }
+ });
+})(jQuery);
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2-spinner.gif b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2-spinner.gif
new file mode 100644
index 0000000..5b33f7e
Binary files /dev/null and b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2-spinner.gif differ
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2x2.png b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2x2.png
new file mode 100644
index 0000000..4bdd5c9
Binary files /dev/null and b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/lib/select2-3.4.1/select2x2.png differ
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/application-menu.html b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/application-menu.html
index 5eaa5f3..9246307 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/application-menu.html
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/application-menu.html
@@ -16,10 +16,10 @@
</ul>
<hr/>
<div>
- <a href="#/realms/{{realm.id}}">Back to realm management...</a>
+ <a href="#/create/application/{{realm.id}}">New Application...</a>
</div>
<div>
- <a href="#/create/application/{{realm.id}}">New Application...</a>
+ <a href="#/realms/{{realm.id}}">Back to realm management...</a>
</div>
</nav>
</div>
\ No newline at end of file
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/menu.html b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/menu.html
index e12ec4a..e10cd0c 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/menu.html
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/menu.html
@@ -5,9 +5,9 @@
<nav id="global-nav">
<div data-ng-controller="RealmDropdownCtrl" >
<ul class="nav pull-left" data-ng-show="showNav()">
- <li class="divider-vertical-right"><a href="#/realms/{{currentRealmId}}">Realm</a></li>
+ <li class="divider-vertical-right"><a href="#/realms/{{current.realm.id}}">Realm</a></li>
</ul>
- <select class="nav pull-left" data-ng-show="showNav()" ng-change="changeRealm()" ng-model="current.realm" ng-options="name for (id, name) in current.realms">
+ <select class="nav pull-left" data-ng-show="showNav()" ng-change="changeRealm()" ng-model="current.realm" ng-options="r.realm for r in current.realms">
</select>
<!-- <select class="nav pull-left" ng-options="r.name for r in current.realms"></select> -->
</div>
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-detail.html b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-detail.html
index a10e742..a5675d8 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-detail.html
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-detail.html
@@ -17,39 +17,74 @@
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset>
- <legend>Settings</legend>
- <div data-kc-input>
- <label>Name</label>
- <input class="input-xlarge" type="text" name="name" data-ng-model="realm.name" autofocus
- required>
- </div>
+ <div class="control-group">
+ <label for="realmForm-name" class="control-label">Name <span class="required">*</span></label>
- <div data-kc-input>
- <label>Enabled</label>
- <input class="input-xlarge" type="checkbox" name="enabled" data-ng-model="realm.enabled">
+ <div class="controls">
+ <input class="input-xlarge" type="text" name="name" data-ng-model="realm.realm" autofocus
+ required>
+ </div>
</div>
- <div data-kc-input>
- <label>Social login</label>
- <input class="input-xlarge" type="checkbox" name="social" data-ng-model="realm.social">
- </div>
+ <table>
+ <tr>
+ <td>
+ <div class="control-group">
+ <label class="control-label">Enabled</label>
- <div data-kc-input>
- <label>Require SSL</label>
- <input class="input-xlarge" type="checkbox" name="requireSsl" data-ng-model="realm.requireSsl">
- </div>
+ <div class="controls">
+ <input class="input-xlarge" type="checkbox" name="enabled"
+ data-ng-model="realm.enabled">
+ </div>
+ </div>
+ </td>
+ <td>
+ <div class="control-group">
+ <label class="control-label">Social login</label>
- <div data-kc-input>
- <label>Cookie login allowed</label>
- <input class="input-xlarge" type="checkbox" name="cookieLoginAllowed" data-ng-model="realm.cookieLoginAllowed">
- </div>
+ <div class="controls">
+ <input class="input-xlarge" type="checkbox" name="social"
+ data-ng-model="realm.social">
+ </div>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <div class="control-group">
+ <label class="control-label">Require SSL</label>
- <div data-kc-input>
- <label>User registration</label>
- <input class="input-xlarge" type="checkbox" name="social"
- data-ng-model="realm.userRegistration">
- </div>
+ <div class="controls">
+ <input class="input-xlarge" type="checkbox" name="requireSsl"
+ data-ng-model="realm.requireSsl">
+ </div>
+ </div>
+ </td>
+ <td>
+ <div class="control-group">
+ <label class="control-label">Cookie login allowed</label>
+
+ <div class="controls">
+ <input class="input-xlarge" type="checkbox" name="cookieLoginAllowed"
+ data-ng-model="realm.cookieLoginAllowed">
+ </div>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <div class="control-group">
+ <label class="control-label">User registration</label>
+
+ <div class="controls">
+ <input class="input-xlarge" type="checkbox" name="userRegistration"
+ data-ng-model="realm.userRegistration">
+ </div>
+ </div>
+ </td>
+ </tr>
+ </table>
<div class="control-group">
<label for="realmForm-tokenLifespan" class="control-label">Token lifespan</label>
@@ -74,13 +109,35 @@
data-ng-model="realm.accessCodeLifespan">
<select style="width: auto;" name="accessCodeLifespanUnit"
data-ng-model="realm.accessCodeLifespanUnit">
- <option value="SECONDS" data-ng-selected="!realm.accessCodeLifespanUnit">Seconds</option>
+ <option value="SECONDS" data-ng-selected="!realm.accessCodeLifespanUnit">Seconds
+ </option>
<option value="MINUTES">Minutes</option>
<option value="HOURS">Hours</option>
<option value="DAYS">Days</option>
</select>
</div>
</div>
+ <div class="control-group">
+ <label class="control-label">Required User Credentials</label>
+
+ <div class="controls">
+ <input style="width:250px" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredCredentials">
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label">Required Application Credentials</label>
+
+ <div class="controls">
+ <input style="width:250px" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredApplicationCredentials">
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label">Required OAuth Credentials</label>
+
+ <div class="controls">
+ <input style="width:250px" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredOAuthClientCredentials">
+ </div>
+ </div>
</fieldset>
<div class="form-actions" data-ng-show="createRealm">
<button type="submit" data-ng-click="save()" class="btn btn-primary" data-ng-show="changed">Save
@@ -96,7 +153,6 @@
</button>
<button type="submit" data-ng-click="reset()" class="btn" data-ng-show="changed">Clear changes
</button>
- <a href="#/realms" data-ng-hide="changed">View realms »</a>
<button type="submit" data-ng-click="remove()" class="btn btn-danger" data-ng-hide="changed">
Delete
</button>
diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-menu.html b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-menu.html
index b8d1205..fc10132 100755
--- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-menu.html
+++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-menu.html
@@ -1,23 +1,22 @@
<div data-ng-hide="createRealm">
<nav id="local-nav">
<ul class="nav nav-list">
- <li data-ng-class="!path[2] && 'active'"><a href="#/realms/{{realm.id}}">Realm Settings</a></li>
- <li data-ng-class="path[2] == 'roles' && 'active'"><a href="#/realms/{{realm.id}}/roles">Realm Roles</a>
- <ul class="sub-items">
- <li data-ng-class="path[0] == 'create' && path[1] == 'role' && 'active'"><a
- href="#/create/role/{{realm.id}}">Add Role</a></li>
- </ul>
- </li>
- <li data-ng-class="path[2] == 'users' && 'active'"><a href="#/realms/{{realm.id}}/users">Realm Users</a>
+ <li data-ng-class="path[2] == 'users' && 'active'"><a href="#/realms/{{realm.id}}/users">Users</a>
<ul class="sub-items">
<li data-ng-class="path[0] == 'create' && path[1] == 'user' && 'active'"><a
- href="#/create/user/{{realm.id}}">Add User</a></li>
+ href="#/create/user/{{realm.id}}">New User</a></li>
<li data-ng-class="path[0] == 'find' && path[1] == 'user' && 'active'"><a
href="#/find/user/{{realm.id}}">Find User</a></li>
</ul>
</li>
- <li data-ng-class="path[2] == 'resources' && 'active'"><a href="#/realms/{{realm.id}}/applications">Manage Applications</a></li>
-
+ <li data-ng-class="path[2] == 'roles' && 'active'"><a href="#/realms/{{realm.id}}/roles">Roles</a>
+ <ul class="sub-items">
+ <li data-ng-class="path[0] == 'create' && path[1] == 'role' && 'active'"><a
+ href="#/create/role/{{realm.id}}">New Role</a></li>
+ </ul>
+ </li>
+ <li data-ng-class="path[2] == 'applications' && 'active'"><a href="#/realms/{{realm.id}}/applications">Manage Applications</a></li>
+ <li data-ng-class="!path[2] && 'active'"><a href="#/realms/{{realm.id}}">Realm Settings</a></li>
</ul>
</nav>
</div>
\ No newline at end of file
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java
index 66621df..d51c79d 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java
@@ -447,7 +447,7 @@ public class OAuthAuthenticationServerValve extends FormAuthenticator implements
userSessionManagement.logout(username);
request.setUserPrincipal(null);
request.setAuthType(null);
- // logout user on all declared authenticated resources
+ // logout user on all declared authenticated applications
logoutResources(username, admin);
redirectToWelcomePage(request, response);
}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java
index f046eed..0d4dfb1 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java
@@ -6,7 +6,7 @@ import org.keycloak.RealmConfiguration;
import org.keycloak.VerificationException;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.SkeletonKeyToken;
-import org.keycloak.representations.idm.RequiredCredentialRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@@ -230,7 +230,7 @@ public class ServletOAuthLogin {
form.param("grant_type", "authorization_code")
.param("code", code)
.param("client_id", client_id)
- .param(RequiredCredentialRepresentation.PASSWORD, password)
+ .param(CredentialRepresentation.PASSWORD, password)
.param("redirect_uri", redirectUri);
Response res = realmInfo.getCodeUrl().request()
diff --git a/sdk-html/src/main/java/org/keycloak/sdk/LoginBean.java b/sdk-html/src/main/java/org/keycloak/sdk/LoginBean.java
index 61d5515..a953ec8 100755
--- a/sdk-html/src/main/java/org/keycloak/sdk/LoginBean.java
+++ b/sdk-html/src/main/java/org/keycloak/sdk/LoginBean.java
@@ -153,7 +153,7 @@ public class LoginBean {
requiredCredentials = new LinkedList<RequiredCredential>();
for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
if (m.isInput()) {
- requiredCredentials.add(new RequiredCredential(m.getType(), m.isSecret()));
+ requiredCredentials.add(new RequiredCredential(m.getType(), m.isSecret(), m.getFormLabel()));
}
}
}
@@ -193,10 +193,12 @@ public class LoginBean {
public class RequiredCredential {
private String type;
private boolean secret;
+ private String formLabel;
- public RequiredCredential(String type, boolean secure) {
+ public RequiredCredential(String type, boolean secure, String formLabel) {
this.type = type;
this.secret = secure;
+ this.formLabel = formLabel;
}
public String getName() {
@@ -204,7 +206,7 @@ public class LoginBean {
}
public String getLabel() {
- return type;
+ return formLabel;
}
public String getInputType() {
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 02c87eb..7ecc27a 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -8,7 +8,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.representations.SkeletonKeyToken;
-import org.keycloak.representations.idm.RequiredCredentialRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RequiredCredentialModel;
import org.keycloak.services.models.UserModel;
@@ -206,9 +206,9 @@ public class AuthenticationManager {
List<RequiredCredentialModel> requiredCredentials = null;
if (realm.hasRole(user, RealmManager.RESOURCE_ROLE)) {
- requiredCredentials = realm.getResourceRequiredCredentials();
+ requiredCredentials = realm.getRequiredApplicationCredentials();
} else if (realm.hasRole(user, RealmManager.IDENTITY_REQUESTER_ROLE)) {
- requiredCredentials = realm.getOAuthClientRequiredCredentials();
+ requiredCredentials = realm.getRequiredOAuthClientCredentials();
} else {
requiredCredentials = realm.getRequiredCredentials();
}
@@ -216,21 +216,23 @@ public class AuthenticationManager {
types.add(credential.getType());
}
- if (types.contains(RequiredCredentialRepresentation.PASSWORD)) {
- String password = formData.getFirst(RequiredCredentialRepresentation.PASSWORD);
+ if (types.contains(CredentialRepresentation.PASSWORD)) {
+ String password = formData.getFirst(CredentialRepresentation.PASSWORD);
if (password == null) {
logger.warn("Password not provided");
return false;
}
- if (types.contains(RequiredCredentialRepresentation.TOTP)) {
- String token = formData.getFirst(RequiredCredentialRepresentation.TOTP);
+ if (types.contains(CredentialRepresentation.TOTP)) {
+ String token = formData.getFirst(CredentialRepresentation.TOTP);
if (token == null) {
logger.warn("TOTP token not provided");
return false;
}
+ logger.info("validating TOTP");
return realm.validateTOTP(user, password, token);
} else {
+ logger.info("validating password for user: " + user.getLoginName());
return realm.validatePassword(user, password);
}
} else {
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 56d65ca..524fb88 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -1,28 +1,14 @@
package org.keycloak.services.managers;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.RequiredCredentialRepresentation;
-import org.keycloak.representations.idm.ResourceRepresentation;
-import org.keycloak.representations.idm.RoleMappingRepresentation;
-import org.keycloak.representations.idm.RoleRepresentation;
-import org.keycloak.representations.idm.ScopeMappingRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.services.models.KeycloakSession;
-import org.keycloak.services.models.RealmModel;
-import org.keycloak.services.models.RequiredCredentialModel;
-import org.keycloak.services.models.ResourceModel;
-import org.keycloak.services.models.RoleModel;
-import org.keycloak.services.models.UserCredentialModel;
-import org.keycloak.services.models.UserModel;
-
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Response;
+import org.jboss.resteasy.logging.Logger;
+import org.keycloak.representations.idm.*;
+import org.keycloak.representations.idm.ApplicationRepresentation;
+import org.keycloak.services.models.*;
+
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -32,6 +18,7 @@ import java.util.concurrent.atomic.AtomicLong;
* @version $Revision: 1 $
*/
public class RealmManager {
+ protected static final Logger logger = Logger.getLogger(RealmManager.class);
private static AtomicLong counter = new AtomicLong(1);
public static final String RESOURCE_ROLE = "KEYCLOAK_RESOURCE";
public static final String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
@@ -79,6 +66,26 @@ public class RealmManager {
realm.setPublicKey(keyPair.getPublic());
}
+ public void updateRealm(RealmRepresentation rep, RealmModel realm) {
+ if (rep.getRealm() != null) realm.setName(rep.getRealm());
+ realm.setEnabled(rep.isEnabled());
+ realm.setSocial(rep.isSocial());
+ realm.setCookieLoginAllowed(rep.isCookieLoginAllowed());
+ realm.setRegistrationAllowed(rep.isRegistrationAllowed());
+ realm.setSslNotRequired((rep.isSslNotRequired()));
+ realm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
+ realm.setTokenLifespan(rep.getTokenLifespan());
+ if (rep.getRequiredOAuthClientCredentials() != null) {
+ realm.updateRequiredOAuthClientCredentials(rep.getRequiredOAuthClientCredentials());
+ }
+ if (rep.getRequiredCredentials() != null) {
+ realm.updateRequiredCredentials(rep.getRequiredCredentials());
+ }
+ if (rep.getRequiredApplicationCredentials() != null) {
+ realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials());
+ }
+ }
+
public RealmModel importRealm(RealmRepresentation rep, UserModel realmCreator) {
//verifyRealmRepresentation(rep);
RealmModel realm = createRealm(rep.getRealm());
@@ -91,6 +98,7 @@ public class RealmManager {
public void importRealm(RealmRepresentation rep, RealmModel newRealm) {
newRealm.setName(rep.getRealm());
newRealm.setEnabled(rep.isEnabled());
+ newRealm.setSocial(rep.isSocial());
newRealm.setTokenLifespan(rep.getTokenLifespan());
newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
newRealm.setSslNotRequired(rep.isSslNotRequired());
@@ -105,19 +113,19 @@ public class RealmManager {
Map<String, UserModel> userMap = new HashMap<String, UserModel>();
if (rep.getRequiredCredentials() != null) {
- for (RequiredCredentialRepresentation requiredCred : rep.getRequiredCredentials()) {
+ for (String requiredCred : rep.getRequiredCredentials()) {
addRequiredCredential(newRealm, requiredCred);
}
}
- if (rep.getRequiredResourceCredentials() != null) {
- for (RequiredCredentialRepresentation requiredCred : rep.getRequiredCredentials()) {
+ if (rep.getRequiredApplicationCredentials() != null) {
+ for (String requiredCred : rep.getRequiredCredentials()) {
addResourceRequiredCredential(newRealm, requiredCred);
}
}
if (rep.getRequiredOAuthClientCredentials() != null) {
- for (RequiredCredentialRepresentation requiredCred : rep.getRequiredCredentials()) {
+ for (String requiredCred : rep.getRequiredCredentials()) {
addOAuthClientRequiredCredential(newRealm, requiredCred);
}
}
@@ -137,7 +145,7 @@ public class RealmManager {
}
}
- if (rep.getResources() != null) {
+ if (rep.getApplications() != null) {
createResources(rep, newRealm);
}
@@ -193,33 +201,22 @@ public class RealmManager {
return user;
}
- public void addRequiredCredential(RealmModel newRealm, RequiredCredentialRepresentation requiredCred) {
- RequiredCredentialModel credential = initializeCred(requiredCred);
- newRealm.addRequiredCredential(credential);
+ public void addRequiredCredential(RealmModel newRealm, String requiredCred) {
+ newRealm.addRequiredCredential(requiredCred);
}
- public void addResourceRequiredCredential(RealmModel newRealm, RequiredCredentialRepresentation requiredCred) {
- RequiredCredentialModel credential = initializeCred(requiredCred);
- newRealm.addResourceRequiredCredential(credential);
+ public void addResourceRequiredCredential(RealmModel newRealm, String requiredCred) {
+ newRealm.addRequiredResourceCredential(requiredCred);
}
- public void addOAuthClientRequiredCredential(RealmModel newRealm, RequiredCredentialRepresentation requiredCred) {
- RequiredCredentialModel credential = initializeCred(requiredCred);
- newRealm.addOAuthClientRequiredCredential(credential);
+ public void addOAuthClientRequiredCredential(RealmModel newRealm, String requiredCred) {
+ newRealm.addRequiredOAuthClientCredential(requiredCred);
}
- private RequiredCredentialModel initializeCred(RequiredCredentialRepresentation requiredCred) {
- RequiredCredentialModel credential = new RequiredCredentialModel();
- credential.setType(requiredCred.getType());
- credential.setInput(requiredCred.isInput());
- credential.setSecret(requiredCred.isSecret());
- return credential;
- }
-
- protected void createResources(RealmRepresentation rep, RealmModel realm) {
+ protected void createResources(RealmRepresentation rep, RealmModel realm) {
RoleModel loginRole = realm.getRole(RealmManager.RESOURCE_ROLE);
ResourceManager manager = new ResourceManager(this);
- for (ResourceRepresentation resourceRep : rep.getResources()) {
+ for (ApplicationRepresentation resourceRep : rep.getApplications()) {
manager.createResource(realm, loginRole, resourceRep);
}
}
@@ -232,4 +229,40 @@ public class RealmManager {
return rep;
}
+ public RealmRepresentation toRepresentation(RealmModel realm) {
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setId(realm.getId());
+ rep.setRealm(realm.getName());
+ rep.setEnabled(realm.isEnabled());
+ rep.setSocial(realm.isSocial());
+ rep.setSslNotRequired(realm.isSslNotRequired());
+ rep.setCookieLoginAllowed(realm.isCookieLoginAllowed());
+ rep.setPublicKey(realm.getPublicKeyPem());
+ rep.setTokenLifespan(realm.getTokenLifespan());
+ rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
+ List<RequiredCredentialModel> requiredCredentialModels = realm.getRequiredCredentials();
+ if (requiredCredentialModels.size() > 0) {
+ rep.setRequiredCredentials(new HashSet<String>());
+ for (RequiredCredentialModel cred : requiredCredentialModels) {
+ rep.getRequiredCredentials().add(cred.getType());
+ }
+ }
+ List<RequiredCredentialModel> requiredResourceCredentialModels = realm.getRequiredApplicationCredentials();
+ if (requiredResourceCredentialModels.size() > 0) {
+ rep.setRequiredApplicationCredentials(new HashSet<String>());
+ for (RequiredCredentialModel cred : requiredResourceCredentialModels) {
+ rep.getRequiredApplicationCredentials().add(cred.getType());
+ }
+ }
+ List<RequiredCredentialModel> requiredOAuthCredentialModels = realm.getRequiredOAuthClientCredentials();
+ if (requiredOAuthCredentialModels.size() > 0) {
+ rep.setRequiredOAuthClientCredentials(new HashSet<String>());
+ for (RequiredCredentialModel cred : requiredOAuthCredentialModels) {
+ rep.getRequiredOAuthClientCredentials().add(cred.getType());
+ }
+ }
+ return rep;
+ }
+
+
}
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 0f1760c..fb1d342 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -6,7 +6,7 @@ import org.jboss.resteasy.logging.Logger;
import org.keycloak.TokenIdGenerator;
import org.keycloak.representations.idm.admin.LogoutAction;
import org.keycloak.services.models.RealmModel;
-import org.keycloak.services.models.ResourceModel;
+import org.keycloak.services.models.ApplicationModel;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
@@ -29,14 +29,14 @@ public class ResourceAdminManager {
.disableTrustManager() // todo fix this, should have a trust manager or a good default
.build();
- List<ResourceModel> resources = realm.getResources();
+ List<ApplicationModel> resources = realm.getApplications();
logger.info("logging out " + resources.size() + " resoures.");
- for (ResourceModel resource : resources) {
+ for (ApplicationModel resource : resources) {
logoutResource(realm, resource, user, client);
}
}
- protected boolean logoutResource(RealmModel realm, ResourceModel resource, String user, ResteasyClient client) {
+ protected boolean logoutResource(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) {
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
String token = new TokenManager().encodeToken(realm, adminAction);
Form form = new Form();
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceManager.java
index e1c2ebb..85c2a7a 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceManager.java
@@ -1,18 +1,13 @@
package org.keycloak.services.managers;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.representations.idm.ResourceRepresentation;
-import org.keycloak.representations.idm.RoleMappingRepresentation;
-import org.keycloak.representations.idm.RoleRepresentation;
-import org.keycloak.representations.idm.ScopeMappingRepresentation;
+import org.keycloak.representations.idm.*;
+import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.services.models.RealmModel;
-import org.keycloak.services.models.ResourceModel;
+import org.keycloak.services.models.ApplicationModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserCredentialModel;
import org.keycloak.services.models.UserModel;
-import java.util.List;
-
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@@ -25,8 +20,8 @@ public class ResourceManager {
this.realmManager = realmManager;
}
- public ResourceModel createResource(RealmModel realm, RoleModel loginRole, ResourceRepresentation resourceRep) {
- ResourceModel resource = realm.addResource(resourceRep.getName());
+ public ApplicationModel createResource(RealmModel realm, RoleModel loginRole, ApplicationRepresentation resourceRep) {
+ ApplicationModel resource = realm.addApplication(resourceRep.getName());
resource.setEnabled(resourceRep.isEnabled());
resource.setManagementUrl(resourceRep.getAdminUrl());
resource.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
@@ -78,12 +73,12 @@ public class ResourceManager {
return resource;
}
- public ResourceModel createResource(RealmModel realm, ResourceRepresentation resourceRep) {
+ public ApplicationModel createResource(RealmModel realm, ApplicationRepresentation resourceRep) {
RoleModel loginRole = realm.getRole(RealmManager.RESOURCE_ROLE);
return createResource(realm, loginRole, resourceRep);
}
- public void updateResource(ResourceRepresentation rep, ResourceModel resource) {
+ public void updateResource(ApplicationRepresentation rep, ApplicationModel resource) {
resource.setName(rep.getName());
resource.setEnabled(rep.isEnabled());
resource.setManagementUrl(rep.getAdminUrl());
@@ -92,13 +87,13 @@ public class ResourceManager {
}
- public ResourceRepresentation toRepresentation(ResourceModel resourceModel) {
- ResourceRepresentation rep = new ResourceRepresentation();
- rep.setId(resourceModel.getId());
- rep.setName(resourceModel.getName());
- rep.setEnabled(resourceModel.isEnabled());
- rep.setAdminUrl(resourceModel.getManagementUrl());
- rep.setSurrogateAuthRequired(resourceModel.isSurrogateAuthRequired());
+ public ApplicationRepresentation toRepresentation(ApplicationModel applicationModel) {
+ ApplicationRepresentation rep = new ApplicationRepresentation();
+ rep.setId(applicationModel.getId());
+ rep.setName(applicationModel.getName());
+ rep.setEnabled(applicationModel.isEnabled());
+ rep.setAdminUrl(applicationModel.getManagementUrl());
+ rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired());
return rep;
}
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index 6072c51..9557cb7 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -6,7 +6,7 @@ import org.jboss.resteasy.jwt.JsonSerialization;
import org.keycloak.representations.SkeletonKeyScope;
import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.services.models.RealmModel;
-import org.keycloak.services.models.ResourceModel;
+import org.keycloak.services.models.ApplicationModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserModel;
@@ -66,7 +66,7 @@ public class TokenManager {
}
}
}
- for (ResourceModel resource : realm.getResources()) {
+ for (ApplicationModel resource : realm.getApplications()) {
Set<String> mapping = resource.getRoleMappings(user);
if (mapping != null && mapping.size() > 0 && (scopeMap == null || scopeMap.containsKey(resource.getName()))) {
Set<String> scope = resource.getScope(client);
@@ -131,9 +131,9 @@ public class TokenManager {
}
if (accessCodeEntry.getResourceRolesRequested().size() > 0) {
- Map<String, ResourceModel> resourceMap = realm.getResourceNameMap();
+ Map<String, ApplicationModel> resourceMap = realm.getResourceNameMap();
for (String resourceName : accessCodeEntry.getResourceRolesRequested().keySet()) {
- ResourceModel resource = resourceMap.get(resourceName);
+ ApplicationModel resource = resourceMap.get(resourceName);
SkeletonKeyToken.Access access = token.addAccess(resourceName).verifyCaller(resource.isSurrogateAuthRequired());
for (RoleModel role : accessCodeEntry.getResourceRolesRequested().get(resourceName)) {
access.addRole(role.getName());
@@ -166,7 +166,7 @@ public class TokenManager {
public SkeletonKeyToken createAccessToken(RealmModel realm, UserModel user) {
- List<ResourceModel> resources = realm.getResources();
+ List<ApplicationModel> resources = realm.getApplications();
SkeletonKeyToken token = new SkeletonKeyToken();
token.id(RealmManager.generateId());
token.issuedNow();
@@ -186,7 +186,7 @@ public class TokenManager {
token.setRealmAccess(access);
}
if (resources != null) {
- for (ResourceModel resource : resources) {
+ for (ApplicationModel resource : resources) {
Set<String> mapping = resource.getRoleMappings(user);
if (mapping == null) continue;
SkeletonKeyToken.Access access = token.addAccess(resource.getName())
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmData.java b/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmData.java
index 9d791d5..0dc72b9 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmData.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmData.java
@@ -14,6 +14,7 @@ public class RealmData extends AbstractPartition {
private boolean sslNotRequired;
private boolean cookieLoginAllowed;
private boolean registrationAllowed;
+ private boolean social;
private int tokenLifespan;
private int accessCodeLifespan;
private String publicKeyPem;
@@ -45,6 +46,15 @@ public class RealmData extends AbstractPartition {
}
@AttributeProperty
+ public boolean isSocial() {
+ return social;
+ }
+
+ public void setSocial(boolean social) {
+ this.social = social;
+ }
+
+ @AttributeProperty
public boolean isSslNotRequired() {
return sslNotRequired;
}
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmEntity.java b/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmEntity.java
index 7d203be..e4b83c4 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmEntity.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/mappings/RealmEntity.java
@@ -35,6 +35,8 @@ public class RealmEntity implements Serializable {
@AttributeValue
private boolean registrationAllowed;
@AttributeValue
+ private boolean social;
+ @AttributeValue
private int tokenLifespan;
@AttributeValue
private int accessCodeLifespan;
@@ -94,6 +96,14 @@ public class RealmEntity implements Serializable {
this.registrationAllowed = registrationAllowed;
}
+ public boolean isSocial() {
+ return social;
+ }
+
+ public void setSocial(boolean social) {
+ this.social = social;
+ }
+
public int getTokenLifespan() {
return tokenLifespan;
}
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/picketlink/RealmAdapter.java
index fe023f7..2e7371a 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/RealmAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/RealmAdapter.java
@@ -1,24 +1,21 @@
package org.keycloak.services.models.picketlink;
import org.bouncycastle.openssl.PEMWriter;
+import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.security.PemUtils;
-import org.keycloak.representations.idm.RequiredCredentialRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.KeycloakSession;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RequiredCredentialModel;
-import org.keycloak.services.models.ResourceModel;
+import org.keycloak.services.models.ApplicationModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserCredentialModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.models.picketlink.mappings.RealmData;
-import org.keycloak.services.models.picketlink.mappings.ResourceData;
-import org.keycloak.services.models.picketlink.relationships.OAuthClientRequiredCredentialRelationship;
-import org.keycloak.services.models.picketlink.relationships.RealmAdminRelationship;
-import org.keycloak.services.models.picketlink.relationships.RequiredCredentialRelationship;
-import org.keycloak.services.models.picketlink.relationships.ResourceRelationship;
-import org.keycloak.services.models.picketlink.relationships.ResourceRequiredCredentialRelationship;
-import org.keycloak.services.models.picketlink.relationships.ScopeRelationship;
+import org.keycloak.services.models.picketlink.mappings.ApplicationData;
+import org.keycloak.services.models.picketlink.relationships.*;
+import org.keycloak.services.models.picketlink.relationships.RequiredApplicationCredentialRelationship;
import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.RelationshipManager;
@@ -29,6 +26,7 @@ import org.picketlink.idm.credential.TOTPCredentials;
import org.picketlink.idm.credential.UsernamePasswordCredentials;
import org.picketlink.idm.credential.X509CertificateCredentials;
import org.picketlink.idm.model.IdentityType;
+import org.picketlink.idm.model.annotation.AttributeProperty;
import org.picketlink.idm.model.sample.Grant;
import org.picketlink.idm.model.sample.Role;
import org.picketlink.idm.model.sample.SampleModel;
@@ -55,6 +53,7 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public class RealmAdapter implements RealmModel {
+ protected static final Logger logger = Logger.getLogger(RealmManager.class);
protected RealmData realm;
protected volatile transient PublicKey publicKey;
@@ -113,6 +112,16 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public boolean isSocial() {
+ return realm.isSocial();
+ }
+
+ @Override
+ public void setSocial(boolean social) {
+ realm.setSocial(social);
+ }
+
+ @Override
public boolean isSslNotRequired() {
return realm.isSslNotRequired();
}
@@ -251,44 +260,51 @@ public class RealmAdapter implements RealmModel {
@Override
public List<RequiredCredentialModel> getRequiredCredentials() {
+ List<RequiredCredentialRelationship> results = getRequiredCredentialRelationships();
+ return getRequiredCredentialModels(results);
+ }
+
+ protected List<RequiredCredentialRelationship> getRequiredCredentialRelationships() {
RelationshipQuery<RequiredCredentialRelationship> query = getRelationshipManager().createRelationshipQuery(RequiredCredentialRelationship.class);
query.setParameter(RequiredCredentialRelationship.REALM, realm.getName());
- List<RequiredCredentialRelationship> results = query.getResultList();
- return getRequiredCredentialModels(results);
+ return query.getResultList();
}
- @Override
- public void addResourceRequiredCredential(RequiredCredentialModel cred) {
- ResourceRequiredCredentialRelationship relationship = new ResourceRequiredCredentialRelationship();
+ public void addRequiredApplicationCredential(RequiredCredentialModel cred) {
+ RequiredApplicationCredentialRelationship relationship = new RequiredApplicationCredentialRelationship();
addRequiredCredential(cred, relationship);
}
@Override
- public List<RequiredCredentialModel> getResourceRequiredCredentials() {
- RelationshipQuery<ResourceRequiredCredentialRelationship> query = getRelationshipManager().createRelationshipQuery(ResourceRequiredCredentialRelationship.class);
- query.setParameter(ResourceRequiredCredentialRelationship.REALM, realm.getName());
- List<ResourceRequiredCredentialRelationship> results = query.getResultList();
+ public List<RequiredCredentialModel> getRequiredApplicationCredentials() {
+ List<RequiredApplicationCredentialRelationship> results = getResourceRequiredCredentialRelationships();
return getRequiredCredentialModels(results);
}
- @Override
- public void addOAuthClientRequiredCredential(RequiredCredentialModel cred) {
+ protected List<RequiredApplicationCredentialRelationship> getResourceRequiredCredentialRelationships() {
+ RelationshipQuery<RequiredApplicationCredentialRelationship> query = getRelationshipManager().createRelationshipQuery(RequiredApplicationCredentialRelationship.class);
+ query.setParameter(RequiredApplicationCredentialRelationship.REALM, realm.getName());
+ return query.getResultList();
+ }
+
+ public void addRequiredOAuthClientCredential(RequiredCredentialModel cred) {
OAuthClientRequiredCredentialRelationship relationship = new OAuthClientRequiredCredentialRelationship();
addRequiredCredential(cred, relationship);
}
@Override
- public List<RequiredCredentialModel> getOAuthClientRequiredCredentials() {
- RelationshipQuery<OAuthClientRequiredCredentialRelationship> query = getRelationshipManager().createRelationshipQuery(OAuthClientRequiredCredentialRelationship.class);
- query.setParameter(ResourceRequiredCredentialRelationship.REALM, realm.getName());
- List<OAuthClientRequiredCredentialRelationship> results = query.getResultList();
+ public List<RequiredCredentialModel> getRequiredOAuthClientCredentials() {
+ List<OAuthClientRequiredCredentialRelationship> results = getOAuthClientRequiredCredentialRelationships();
return getRequiredCredentialModels(results);
}
+ protected List<OAuthClientRequiredCredentialRelationship> getOAuthClientRequiredCredentialRelationships() {
+ RelationshipQuery<OAuthClientRequiredCredentialRelationship> query = getRelationshipManager().createRelationshipQuery(OAuthClientRequiredCredentialRelationship.class);
+ query.setParameter(RequiredApplicationCredentialRelationship.REALM, realm.getName());
+ return query.getResultList();
+ }
-
- @Override
public void addRequiredCredential(RequiredCredentialModel cred) {
RequiredCredentialRelationship relationship = new RequiredCredentialRelationship();
addRequiredCredential(cred, relationship);
@@ -302,6 +318,7 @@ public class RealmAdapter implements RealmModel {
model.setInput(relationship.isInput());
model.setSecret(relationship.isSecret());
model.setType(relationship.getCredentialType());
+ model.setFormLabel(relationship.getFormLabel());
rtn.add(model);
}
return rtn;
@@ -311,9 +328,94 @@ public class RealmAdapter implements RealmModel {
relationship.setInput(cred.isInput());
relationship.setSecret(cred.isSecret());
relationship.setRealm(realm.getName());
+ relationship.setFormLabel(cred.getFormLabel());
getRelationshipManager().add(relationship);
}
+ @Override
+ public void updateRequiredCredentials(Set<String> creds) {
+ List<RequiredCredentialRelationship> relationships = getRequiredCredentialRelationships();
+ RelationshipManager rm = getRelationshipManager();
+ Set<String> already = new HashSet<String>();
+ for (RequiredCredentialRelationship rel : relationships) {
+ if (!creds.contains(rel.getCredentialType())) {
+ rm.remove(rel);
+ } else {
+ already.add(rel.getCredentialType());
+ }
+ }
+ for (String cred : creds) {
+ logger.info("updating cred: " + cred);
+ if (!already.contains(cred)) {
+ addRequiredCredential(cred);
+ }
+ }
+ }
+
+ @Override
+ public void updateRequiredOAuthClientCredentials(Set<String> creds) {
+ List<OAuthClientRequiredCredentialRelationship> relationships = getOAuthClientRequiredCredentialRelationships();
+ RelationshipManager rm = getRelationshipManager();
+ Set<String> already = new HashSet<String>();
+ for (RequiredCredentialRelationship rel : relationships) {
+ if (!creds.contains(rel.getCredentialType())) {
+ rm.remove(rel);
+ } else {
+ already.add(rel.getCredentialType());
+ }
+ }
+ for (String cred : creds) {
+ if (!already.contains(cred)) {
+ addRequiredOAuthClientCredential(cred);
+ }
+ }
+ }
+
+ @Override
+ public void updateRequiredApplicationCredentials(Set<String> creds) {
+ List<RequiredApplicationCredentialRelationship> relationships = getResourceRequiredCredentialRelationships();
+ RelationshipManager rm = getRelationshipManager();
+ Set<String> already = new HashSet<String>();
+ for (RequiredCredentialRelationship rel : relationships) {
+ if (!creds.contains(rel.getCredentialType())) {
+ rm.remove(rel);
+ } else {
+ already.add(rel.getCredentialType());
+ }
+ }
+ for (String cred : creds) {
+ if (!already.contains(cred)) {
+ addRequiredResourceCredential(cred);
+ }
+ }
+ }
+
+
+ @Override
+ public void addRequiredCredential(String type) {
+ RequiredCredentialModel model = initRequiredCredentialModel(type);
+ addRequiredCredential(model);
+ }
+
+ @Override
+ public void addRequiredOAuthClientCredential(String type) {
+ RequiredCredentialModel model = initRequiredCredentialModel(type);
+ addRequiredOAuthClientCredential(model);
+ }
+
+ @Override
+ public void addRequiredResourceCredential(String type) {
+ RequiredCredentialModel model = initRequiredCredentialModel(type);
+ addRequiredApplicationCredential(model);
+ }
+
+ protected RequiredCredentialModel initRequiredCredentialModel(String type) {
+ RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
+ if (model == null) {
+ throw new RuntimeException("Unknown credential type " + type);
+ }
+ return model;
+ }
@Override
public boolean validatePassword(UserModel user, String password) {
@@ -335,13 +437,14 @@ public class RealmAdapter implements RealmModel {
@Override
public void updateCredential(UserModel user, UserCredentialModel cred) {
IdentityManager idm = getIdm();
- if (cred.getType().equals(RequiredCredentialRepresentation.PASSWORD)) {
+ if (cred.getType().equals(CredentialRepresentation.PASSWORD)) {
Password password = new Password(cred.getValue());
idm.updateCredential(((UserAdapter)user).getUser(), password);
- } else if (cred.getType().equals(RequiredCredentialRepresentation.TOTP)) {
+ } else if (cred.getType().equals(CredentialRepresentation.TOTP)) {
TOTPCredential totp = new TOTPCredential(cred.getValue());
+ totp.setDevice(cred.getDevice());
idm.updateCredential(((UserAdapter)user).getUser(), totp);
- } else if (cred.getType().equals(RequiredCredentialRepresentation.CLIENT_CERT)) {
+ } else if (cred.getType().equals(CredentialRepresentation.CLIENT_CERT)) {
X509Certificate cert = null;
try {
cert = org.keycloak.PemUtils.decodeCertificate(cred.getValue());
@@ -416,9 +519,9 @@ public class RealmAdapter implements RealmModel {
* @return
*/
@Override
- public Map<String, ResourceModel> getResourceNameMap() {
- Map<String, ResourceModel> resourceMap = new HashMap<String, ResourceModel>();
- for (ResourceModel resource : getResources()) {
+ public Map<String, ApplicationModel> getResourceNameMap() {
+ Map<String, ApplicationModel> resourceMap = new HashMap<String, ApplicationModel>();
+ for (ApplicationModel resource : getApplications()) {
resourceMap.put(resource.getName(), resource);
}
return resourceMap;
@@ -430,27 +533,27 @@ public class RealmAdapter implements RealmModel {
* @return
*/
@Override
- public ResourceModel getResourceById(String id) {
+ public ApplicationModel getApplicationById(String id) {
RelationshipQuery<ResourceRelationship> query = getRelationshipManager().createRelationshipQuery(ResourceRelationship.class);
query.setParameter(ResourceRelationship.REALM, realm.getName());
query.setParameter(ResourceRelationship.RESOURCE, id);
List<ResourceRelationship> results = query.getResultList();
if (results.size() == 0) return null;
- ResourceData resource = partitionManager.getPartition(ResourceData.class, id);
- ResourceModel model = new ResourceAdapter(resource, this, partitionManager);
+ ApplicationData resource = partitionManager.getPartition(ApplicationData.class, id);
+ ApplicationModel model = new ApplicationAdapter(resource, this, partitionManager);
return model;
}
@Override
- public List<ResourceModel> getResources() {
+ public List<ApplicationModel> getApplications() {
RelationshipQuery<ResourceRelationship> query = getRelationshipManager().createRelationshipQuery(ResourceRelationship.class);
query.setParameter(ResourceRelationship.REALM, realm.getName());
List<ResourceRelationship> results = query.getResultList();
- List<ResourceModel> resources = new ArrayList<ResourceModel>();
+ List<ApplicationModel> resources = new ArrayList<ApplicationModel>();
for (ResourceRelationship relationship : results) {
- ResourceData resource = partitionManager.getPartition(ResourceData.class, relationship.getResource());
- ResourceModel model = new ResourceAdapter(resource, this, partitionManager);
+ ApplicationData resource = partitionManager.getPartition(ApplicationData.class, relationship.getResource());
+ ApplicationModel model = new ApplicationAdapter(resource, this, partitionManager);
resources.add(model);
}
@@ -458,19 +561,19 @@ public class RealmAdapter implements RealmModel {
}
@Override
- public ResourceModel addResource(String name) {
- ResourceData resourceData = new ResourceData(RealmManager.generateId());
+ public ApplicationModel addApplication(String name) {
+ ApplicationData applicationData = new ApplicationData(RealmManager.generateId());
User resourceUser = new User(name);
idm.add(resourceUser);
- resourceData.setResourceUser(resourceUser);
- resourceData.setResourceName(name);
- resourceData.setResourceUser(resourceUser);
- partitionManager.add(resourceData);
+ applicationData.setResourceUser(resourceUser);
+ applicationData.setResourceName(name);
+ applicationData.setResourceUser(resourceUser);
+ partitionManager.add(applicationData);
ResourceRelationship resourceRelationship = new ResourceRelationship();
resourceRelationship.setRealm(realm.getName());
- resourceRelationship.setResource(resourceData.getName());
+ resourceRelationship.setResource(applicationData.getName());
getRelationshipManager().add(resourceRelationship);
- ResourceModel resource = new ResourceAdapter(resourceData, this, partitionManager);
+ ApplicationModel resource = new ApplicationAdapter(applicationData, this, partitionManager);
resource.addRole("*");
resource.addScope(new UserAdapter(resourceUser, idm), "*");
return resource;
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/relationships/RequiredCredentialRelationship.java b/services/src/main/java/org/keycloak/services/models/picketlink/relationships/RequiredCredentialRelationship.java
index 4434a19..c24b128 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/relationships/RequiredCredentialRelationship.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/relationships/RequiredCredentialRelationship.java
@@ -68,4 +68,14 @@ public class RequiredCredentialRelationship extends AbstractAttributedType imple
public void setSecret(boolean secret) {
setAttribute(new Attribute<Boolean>("secret", secret));
}
+
+ @AttributeProperty
+ public String getFormLabel() {
+ return (String)getAttribute("formLabel").getValue();
+ }
+
+ public void setFormLabel(String label) {
+ setAttribute(new Attribute<String>("formLabel", label));
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/models/picketlink/UserAdapter.java b/services/src/main/java/org/keycloak/services/models/picketlink/UserAdapter.java
index 324baf5..be85c88 100755
--- a/services/src/main/java/org/keycloak/services/models/picketlink/UserAdapter.java
+++ b/services/src/main/java/org/keycloak/services/models/picketlink/UserAdapter.java
@@ -3,6 +3,7 @@ package org.keycloak.services.models.picketlink;
import org.keycloak.services.models.UserModel;
import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.model.Attribute;
+import org.picketlink.idm.model.annotation.AttributeProperty;
import org.picketlink.idm.model.sample.User;
import java.util.HashMap;
@@ -42,6 +43,39 @@ public class UserAdapter implements UserModel {
}
@Override
+ public String getFirstName() {
+ return user.getFirstName();
+ }
+
+ @Override
+ public void setFirstName(String firstName) {
+ user.setFirstName(firstName);
+ idm.update(user);
+ }
+
+ @Override
+ public String getLastName() {
+ return user.getLastName();
+ }
+
+ @Override
+ public void setLastName(String lastName) {
+ user.setLastName(lastName);
+ idm.update(user);
+ }
+
+ @Override
+ public String getEmail() {
+ return user.getEmail();
+ }
+
+ @Override
+ public void setEmail(String email) {
+ user.setEmail(email);
+ idm.update(user);
+ }
+
+ @Override
public void setAttribute(String name, String value) {
user.setAttribute(new Attribute<String>(name, value));
idm.update(user);
diff --git a/services/src/main/java/org/keycloak/services/models/RealmModel.java b/services/src/main/java/org/keycloak/services/models/RealmModel.java
index 404f837..605de11 100755
--- a/services/src/main/java/org/keycloak/services/models/RealmModel.java
+++ b/services/src/main/java/org/keycloak/services/models/RealmModel.java
@@ -61,7 +61,7 @@ public interface RealmModel {
List<RequiredCredentialModel> getRequiredCredentials();
- void addRequiredCredential(RequiredCredentialModel cred);
+ void addRequiredCredential(String cred);
boolean validatePassword(UserModel user, String password);
@@ -79,11 +79,11 @@ public interface RealmModel {
List<RoleModel> getRoles();
- Map<String, ResourceModel> getResourceNameMap();
+ Map<String, ApplicationModel> getResourceNameMap();
- List<ResourceModel> getResources();
+ List<ApplicationModel> getApplications();
- ResourceModel addResource(String name);
+ ApplicationModel addApplication(String name);
boolean hasRole(UserModel user, RoleModel role);
@@ -101,15 +101,27 @@ public interface RealmModel {
RoleModel getRoleById(String id);
- void addResourceRequiredCredential(RequiredCredentialModel cred);
- List<RequiredCredentialModel> getResourceRequiredCredentials();
+ List<RequiredCredentialModel> getRequiredApplicationCredentials();
- void addOAuthClientRequiredCredential(RequiredCredentialModel cred);
- List<RequiredCredentialModel> getOAuthClientRequiredCredentials();
+ List<RequiredCredentialModel> getRequiredOAuthClientCredentials();
boolean hasRole(UserModel user, String role);
- ResourceModel getResourceById(String id);
+ ApplicationModel getApplicationById(String id);
+
+ void addRequiredOAuthClientCredential(String type);
+
+ void addRequiredResourceCredential(String type);
+
+ void updateRequiredCredentials(Set<String> creds);
+
+ void updateRequiredOAuthClientCredentials(Set<String> creds);
+
+ void updateRequiredApplicationCredentials(Set<String> creds);
+
+ boolean isSocial();
+
+ void setSocial(boolean social);
}
diff --git a/services/src/main/java/org/keycloak/services/models/RequiredCredentialModel.java b/services/src/main/java/org/keycloak/services/models/RequiredCredentialModel.java
index c8ca584..a72bcba 100755
--- a/services/src/main/java/org/keycloak/services/models/RequiredCredentialModel.java
+++ b/services/src/main/java/org/keycloak/services/models/RequiredCredentialModel.java
@@ -1,6 +1,10 @@
package org.keycloak.services.models;
-import org.keycloak.representations.idm.RequiredCredentialRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -10,16 +14,11 @@ public class RequiredCredentialModel {
protected String type;
protected boolean input;
protected boolean secret;
+ protected String formLabel;
public RequiredCredentialModel() {
}
- public RequiredCredentialModel(String type, boolean input, boolean secret) {
- this.type = type;
- this.input = input;
- this.secret = secret;
- }
-
public String getType() {
return type;
}
@@ -44,5 +43,39 @@ public class RequiredCredentialModel {
this.secret = secret;
}
- public static final RequiredCredentialModel PASSWORD = new RequiredCredentialModel(RequiredCredentialRepresentation.PASSWORD, true, true);
+ public String getFormLabel() {
+ return formLabel;
+ }
+
+ public void setFormLabel(String formLabel) {
+ this.formLabel = formLabel;
+ }
+
+ public static final Map<String, RequiredCredentialModel> BUILT_IN;
+ public static final RequiredCredentialModel PASSWORD;
+ public static final RequiredCredentialModel TOTP;
+ public static final RequiredCredentialModel CLIENT_CERT;
+
+ static {
+ Map<String, RequiredCredentialModel> map = new HashMap<String, RequiredCredentialModel>();
+ PASSWORD = new RequiredCredentialModel();
+ PASSWORD.setType(CredentialRepresentation.PASSWORD);
+ PASSWORD.setInput(true);
+ PASSWORD.setSecret(true);
+ PASSWORD.setFormLabel("Password");
+ map.put(PASSWORD.getType(), PASSWORD);
+ TOTP = new RequiredCredentialModel();
+ TOTP.setType(CredentialRepresentation.TOTP);
+ TOTP.setInput(true);
+ TOTP.setSecret(false);
+ TOTP.setFormLabel("Authenticator Code");
+ map.put(TOTP.getType(), TOTP);
+ CLIENT_CERT = new RequiredCredentialModel();
+ CLIENT_CERT.setType(CredentialRepresentation.CLIENT_CERT);
+ CLIENT_CERT.setInput(false);
+ CLIENT_CERT.setSecret(false);
+ CLIENT_CERT.setFormLabel("Client Certificate");
+ map.put(CLIENT_CERT.getType(), CLIENT_CERT);
+ BUILT_IN = Collections.unmodifiableMap(map);
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/models/UserCredentialModel.java b/services/src/main/java/org/keycloak/services/models/UserCredentialModel.java
index 5807319..1ccf8be 100755
--- a/services/src/main/java/org/keycloak/services/models/UserCredentialModel.java
+++ b/services/src/main/java/org/keycloak/services/models/UserCredentialModel.java
@@ -8,6 +8,7 @@ public class UserCredentialModel {
protected String type;
protected String value;
+ protected String device;
public String getType() {
return type;
@@ -24,4 +25,12 @@ public class UserCredentialModel {
public void setValue(String value) {
this.value = value;
}
+
+ public String getDevice() {
+ return device;
+ }
+
+ public void setDevice(String device) {
+ this.device = device;
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/models/UserModel.java b/services/src/main/java/org/keycloak/services/models/UserModel.java
index bac22c7..f6eccf2 100755
--- a/services/src/main/java/org/keycloak/services/models/UserModel.java
+++ b/services/src/main/java/org/keycloak/services/models/UserModel.java
@@ -20,4 +20,16 @@ public interface UserModel {
String getAttribute(String name);
Map<String, String> getAttributes();
+
+ String getFirstName();
+
+ void setFirstName(String firstName);
+
+ String getLastName();
+
+ void setLastName(String lastName);
+
+ String getEmail();
+
+ void setEmail(String email);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index bf2a7ca..539d34b 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -9,14 +9,11 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserModel;
-import org.keycloak.services.resources.PublicRealmResource;
import org.keycloak.services.resources.Transaction;
import javax.ws.rs.Consumes;
-import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
-import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
@@ -24,15 +21,10 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -48,9 +40,9 @@ public class RealmAdminResource {
this.realm = realm;
}
- @Path("resources")
- public RealmResourcesResource getResources() {
- return new RealmResourcesResource(admin, realm);
+ @Path("applications")
+ public ApplicationsResource getResources() {
+ return new ApplicationsResource(admin, realm);
}
@GET
@@ -60,21 +52,12 @@ public class RealmAdminResource {
return new Transaction() {
@Override
protected RealmRepresentation callImpl() {
- RealmRepresentation rep = new RealmRepresentation();
- rep.setId(realm.getId());
- rep.setRealm(realm.getName());
- rep.setEnabled(realm.isEnabled());
- rep.setSslNotRequired(realm.isSslNotRequired());
- rep.setCookieLoginAllowed(realm.isCookieLoginAllowed());
- rep.setPublicKey(realm.getPublicKeyPem());
- rep.setTokenLifespan(realm.getTokenLifespan());
- rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
- return rep;
+ return new RealmManager(session).toRepresentation(realm);
}
}.call();
-
}
+
@Path("roles")
@GET
@NoCache
@@ -94,6 +77,19 @@ public class RealmAdminResource {
}.call();
}
+ @PUT
+ @Consumes("application/json")
+ public void updateRealm(final RealmRepresentation rep) {
+ new Transaction() {
+ @Override
+ protected void runImpl() {
+ logger.info("updating realm: " + rep.getRealm());
+ new RealmManager(session).updateRealm(rep, realm);
+ }
+ }.run();
+
+ }
+
@Path("roles/{id}")
@GET
@NoCache
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
index 015f150..fd5485a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
@@ -28,6 +28,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -52,18 +53,18 @@ public class RealmsAdminResource {
@GET
@NoCache
@Produces("application/json")
- public Response getRealms() {
+ public List<RealmRepresentation> getRealms() {
return new Transaction() {
@Override
- protected Response callImpl() {
+ protected List<RealmRepresentation> callImpl() {
logger.info(("getRealms()"));
+ RealmManager realmManager = new RealmManager(session);
List<RealmModel> realms = session.getRealms(admin);
- Map<String, String> map = new HashMap<String, String>();
+ List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
for (RealmModel realm : realms) {
- map.put(realm.getId(), realm.getName());
+ reps.add(realmManager.toRepresentation(realm));
}
- return Response.ok(new GenericEntity<Map<String, String>>(map){})
- .cacheControl(noCache).build();
+ return reps;
}
}.call();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index c16e420..980487c 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -6,8 +6,8 @@ import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.models.KeycloakSessionFactory;
import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession;
import org.keycloak.services.models.picketlink.PicketlinkKeycloakSessionFactory;
+import org.keycloak.services.models.picketlink.mappings.ApplicationEntity;
import org.keycloak.services.models.picketlink.mappings.RealmEntity;
-import org.keycloak.services.models.picketlink.mappings.ResourceEntity;
import org.keycloak.social.SocialRequestManager;
import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.config.IdentityConfigurationBuilder;
@@ -98,7 +98,7 @@ public class KeycloakApplication extends Application {
OTPCredentialTypeEntity.class,
AttributeTypeEntity.class,
RealmEntity.class,
- ResourceEntity.class
+ ApplicationEntity.class
)
.supportGlobalRelationship(org.picketlink.idm.model.Relationship.class)
.addContextInitializer(new JPAContextInitializer(null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/SaasService.java b/services/src/main/java/org/keycloak/services/resources/SaasService.java
index 6146636..3665c53 100755
--- a/services/src/main/java/org/keycloak/services/resources/SaasService.java
+++ b/services/src/main/java/org/keycloak/services/resources/SaasService.java
@@ -6,7 +6,6 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.NotImplementedYetException;
import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.representations.idm.RequiredCredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
@@ -19,6 +18,7 @@ import org.keycloak.services.resources.admin.RealmsAdminResource;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.net.URI;
+import java.util.StringTokenizer;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -109,7 +109,7 @@ public class SaasService {
if (user == null) {
return Response.status(401).build();
}
- return Response.ok(new WhoAmI(user.getLoginName(), user.getLoginName())).build();
+ return Response.ok(new WhoAmI(user.getLoginName(), user.getFirstName() + " " + user.getLastName())).build();
}
}.call();
}
@@ -295,7 +295,7 @@ public class SaasService {
@Path("registrations")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- public Response processRegister(final @FormParam("name") String name,
+ public Response processRegister(final @FormParam("name") String fullname,
final @FormParam("email") String email,
final @FormParam("username") String username,
final @FormParam("password") String password,
@@ -312,7 +312,29 @@ public class SaasService {
RealmModel defaultRealm = realmManager.defaultRealm();
UserRepresentation newUser = new UserRepresentation();
newUser.setUsername(username);
- newUser.credential(RequiredCredentialRepresentation.PASSWORD, password, false);
+ newUser.setEmail(email);
+ if (fullname != null) {
+ StringTokenizer tokenizer = new StringTokenizer(fullname, " ");
+ StringBuffer first = null;
+ String last = "";
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ if (tokenizer.hasMoreTokens()) {
+ if (first == null) {
+ first = new StringBuffer();
+ } else {
+ first.append(" ");
+ }
+ first.append(token);
+ } else {
+ last = token;
+ }
+ }
+ if (first == null) first = new StringBuffer();
+ newUser.setFirstName(first.toString());
+ newUser.setLastName(last);
+ }
+ newUser.credential(CredentialRepresentation.PASSWORD, password);
UserModel user = registerMe(defaultRealm, newUser);
if (user == null) {
request.setAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE", "Username already exists.");
@@ -340,6 +362,9 @@ public class SaasService {
}
user = defaultRealm.addUser(newUser.getUsername());
+ user.setFirstName(newUser.getFirstName());
+ user.setLastName(newUser.getLastName());
+ user.setEmail(newUser.getEmail());
for (CredentialRepresentation cred : newUser.getCredentials()) {
UserCredentialModel credModel = new UserCredentialModel();
credModel.setType(cred.getType());
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 68c7be8..ad191dd 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -6,7 +6,7 @@ import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
-import org.keycloak.representations.idm.RequiredCredentialRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.KeycloakSession;
import org.keycloak.services.models.KeycloakSessionFactory;
@@ -18,7 +18,10 @@ import org.keycloak.services.models.UserCredentialModel;
import org.keycloak.services.resources.KeycloakApplication;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -53,6 +56,16 @@ public class AdapterTest {
}
@Test
+ public void testMe() {
+ String hello = "Bill Burke";
+ StringTokenizer tokenizer = new StringTokenizer(hello, " ");
+ while (tokenizer.hasMoreTokens()) {
+ System.out.println("token: " + tokenizer.nextToken());
+ }
+ }
+
+
+ @Test
public void test1CreateRealm() throws Exception {
realmModel = adapter.createRealm("JUGGLER");
realmModel.setAccessCodeLifespan(100);
@@ -77,25 +90,28 @@ public class AdapterTest {
@Test
public void test2RequiredCredential() throws Exception {
test1CreateRealm();
- RequiredCredentialModel creds = new RequiredCredentialModel();
- creds.setSecret(true);
- creds.setType(RequiredCredentialRepresentation.PASSWORD);
- creds.setInput(true);
- realmModel.addRequiredCredential(creds);
- creds = new RequiredCredentialModel();
- creds.setSecret(true);
- creds.setType(RequiredCredentialRepresentation.TOTP);
- creds.setInput(true);
- realmModel.addRequiredCredential(creds);
+ realmModel.addRequiredCredential(CredentialRepresentation.PASSWORD);
List<RequiredCredentialModel> storedCreds = realmModel.getRequiredCredentials();
+ Assert.assertEquals(1, storedCreds.size());
+
+ Set<String> creds = new HashSet<String>();
+ creds.add(CredentialRepresentation.PASSWORD);
+ creds.add(CredentialRepresentation.TOTP);
+ realmModel.updateRequiredCredentials(creds);
+ storedCreds = realmModel.getRequiredCredentials();
Assert.assertEquals(2, storedCreds.size());
boolean totp = false;
boolean password = false;
for (RequiredCredentialModel cred : storedCreds) {
- if (cred.getType().equals(RequiredCredentialRepresentation.PASSWORD)) password = true;
- else if (cred.getType().equals(RequiredCredentialRepresentation.TOTP)) totp = true;
Assert.assertTrue(cred.isInput());
- Assert.assertTrue(cred.isSecret());
+ if (cred.getType().equals(CredentialRepresentation.PASSWORD)) {
+ password = true;
+ Assert.assertTrue(cred.isSecret());
+ }
+ else if (cred.getType().equals(CredentialRepresentation.TOTP)) {
+ totp = true;
+ Assert.assertFalse(cred.isSecret());
+ }
}
Assert.assertTrue(totp);
Assert.assertTrue(password);
@@ -106,7 +122,7 @@ public class AdapterTest {
test1CreateRealm();
UserModel user = realmModel.addUser("bburke");
UserCredentialModel cred = new UserCredentialModel();
- cred.setType(RequiredCredentialRepresentation.PASSWORD);
+ cred.setType(CredentialRepresentation.PASSWORD);
cred.setValue("geheim");
realmModel.updateCredential(user, cred);
Assert.assertTrue(realmModel.validatePassword(user, "geheim"));
diff --git a/services/src/test/java/org/keycloak/test/ImportTest.java b/services/src/test/java/org/keycloak/test/ImportTest.java
index 373041e..727aa08 100755
--- a/services/src/test/java/org/keycloak/test/ImportTest.java
+++ b/services/src/test/java/org/keycloak/test/ImportTest.java
@@ -6,18 +6,18 @@ import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.KeycloakSession;
import org.keycloak.services.models.KeycloakSessionFactory;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RequiredCredentialModel;
-import org.keycloak.services.models.ResourceModel;
+import org.keycloak.services.models.ApplicationModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.SaasService;
-import org.keycloak.services.resources.SaasService;
import java.util.List;
import java.util.Set;
@@ -59,7 +59,7 @@ public class ImportTest {
defaultRealm.setCookieLoginAllowed(true);
defaultRealm.setRegistrationAllowed(true);
manager.generateRealmKeys(defaultRealm);
- defaultRealm.addRequiredCredential(RequiredCredentialModel.PASSWORD);
+ defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
UserModel admin = defaultRealm.addUser("admin");
defaultRealm.grantRole(admin, role);
@@ -70,19 +70,44 @@ public class ImportTest {
realm.addRealmAdmin(admin);
List<RequiredCredentialModel> creds = realm.getRequiredCredentials();
Assert.assertEquals(1, creds.size());
+ RequiredCredentialModel cred = creds.get(0);
+ Assert.assertEquals("Password", cred.getFormLabel());
UserModel user = realm.getUser("loginclient");
Assert.assertNotNull(user);
Set<String> scopes = realm.getScope(user);
System.out.println("Scopes size: " + scopes.size());
Assert.assertTrue(scopes.contains("*"));
- List<ResourceModel> resources = realm.getResources();
+ List<ApplicationModel> resources = realm.getApplications();
Assert.assertEquals(2, resources.size());
List<RealmModel> realms = identitySession.getRealms(admin);
Assert.assertEquals(1, realms.size());
}
+ @Test
+ public void install2() throws Exception {
+ RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
+ defaultRealm.setName(RealmModel.DEFAULT_REALM);
+ defaultRealm.setEnabled(true);
+ defaultRealm.setTokenLifespan(300);
+ defaultRealm.setAccessCodeLifespan(60);
+ defaultRealm.setSslNotRequired(false);
+ defaultRealm.setCookieLoginAllowed(true);
+ defaultRealm.setRegistrationAllowed(true);
+ manager.generateRealmKeys(defaultRealm);
+ defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
+ RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
+ UserModel admin = defaultRealm.addUser("admin");
+ defaultRealm.grantRole(admin, role);
+
+ RealmRepresentation rep = KeycloakTestBase.loadJson("testrealm-demo.json");
+ RealmModel realm = manager.createRealm("demo", rep.getRealm());
+ manager.importRealm(rep, realm);
+ realm.addRealmAdmin(admin);
+ }
+
+
diff --git a/services/src/test/java/org/keycloak/test/InstallationManager.java b/services/src/test/java/org/keycloak/test/InstallationManager.java
index 63e5d1d..18a2cc4 100755
--- a/services/src/test/java/org/keycloak/test/InstallationManager.java
+++ b/services/src/test/java/org/keycloak/test/InstallationManager.java
@@ -1,5 +1,6 @@
package org.keycloak.test;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RequiredCredentialModel;
@@ -21,7 +22,7 @@ public class InstallationManager {
defaultRealm.setCookieLoginAllowed(true);
defaultRealm.setRegistrationAllowed(true);
manager.generateRealmKeys(defaultRealm);
- defaultRealm.addRequiredCredential(RequiredCredentialModel.PASSWORD);
+ defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
}
diff --git a/services/src/test/java/org/keycloak/test/RealmCreationTest.java b/services/src/test/java/org/keycloak/test/RealmCreationTest.java
index 9fb4aa0..0d446ff 100755
--- a/services/src/test/java/org/keycloak/test/RealmCreationTest.java
+++ b/services/src/test/java/org/keycloak/test/RealmCreationTest.java
@@ -8,8 +8,8 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.SkeletonKeyContextResolver;
import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.RequiredCredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
@@ -61,7 +61,7 @@ public class RealmCreationTest {
public void testRegisterLoginAndCreate() throws Exception {
UserRepresentation user = new UserRepresentation();
user.setUsername("bburke");
- user.credential(RequiredCredentialRepresentation.PASSWORD, "geheim", false);
+ user.credential(CredentialRepresentation.PASSWORD, "geheim");
WebTarget target = client.target(generateURL("/"));
Response response = target.path("saas/registrations").request().post(Entity.json(user));
@@ -73,14 +73,14 @@ public class RealmCreationTest {
try {
Form form = new Form();
form.param(AuthenticationManager.FORM_USERNAME, "bburke");
- form.param(RequiredCredentialRepresentation.PASSWORD, "badpassword");
+ form.param(CredentialRepresentation.PASSWORD, "badpassword");
tokenResponse = target.path("realms").path(RealmModel.DEFAULT_REALM).path("tokens/grants/identity-token").request().post(Entity.form(form), AccessTokenResponse.class);
Assert.fail();
} catch (NotAuthorizedException e) {
}
Form form = new Form();
form.param(AuthenticationManager.FORM_USERNAME, "bburke");
- form.param(RequiredCredentialRepresentation.PASSWORD, "geheim");
+ form.param(CredentialRepresentation.PASSWORD, "geheim");
tokenResponse = target.path("realms").path(RealmModel.DEFAULT_REALM).path("tokens/grants/identity-token").request().post(Entity.form(form), AccessTokenResponse.class);
Assert.assertNotNull(tokenResponse);
System.out.println(tokenResponse.getToken());
diff --git a/services/src/test/resources/META-INF/persistence.xml b/services/src/test/resources/META-INF/persistence.xml
index e89967b..0c8ed9e 100755
--- a/services/src/test/resources/META-INF/persistence.xml
+++ b/services/src/test/resources/META-INF/persistence.xml
@@ -19,7 +19,7 @@
<class>org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>
<class>org.keycloak.services.models.picketlink.mappings.RealmEntity</class>
- <class>org.keycloak.services.models.picketlink.mappings.ResourceEntity</class>
+ <class>org.keycloak.services.models.picketlink.mappings.ApplicationEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
services/src/test/resources/testrealm.json 214(+106 -108)
diff --git a/services/src/test/resources/testrealm.json b/services/src/test/resources/testrealm.json
index 7239086..6ec29bf 100755
--- a/services/src/test/resources/testrealm.json
+++ b/services/src/test/resources/testrealm.json
@@ -1,123 +1,121 @@
{
- "realm" : "test-realm",
- "enabled" : true,
- "tokenLifespan" : 6000,
- "accessCodeLifespan" : 30,
- "requiredCredentials" : [
+ "realm": "test-realm",
+ "enabled": true,
+ "tokenLifespan": 6000,
+ "accessCodeLifespan": 30,
+ "requiredCredentials": [ "password" ],
+ "requiredApplicationCredentials": [ "password" ],
+ "requiredOAuthClientCredentials": [ "password" ],
+ "users": [
{
- "type" : "Password",
- "input" : true,
- "secret" : true
+ "username": "wburke",
+ "enabled": true,
+ "attributes": {
+ "email": "bburke@redhat.com"
+ },
+ "credentials": [
+ {
+ "type": "password",
+ "value": "userpassword"
+ }
+ ]
+ },
+ {
+ "username": "loginclient",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "clientpassword"
+ }
+ ]
+ },
+ {
+ "username": "admin",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "adminpassword"
+ }
+ ]
+ },
+ {
+ "username": "oauthclient",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "clientpassword"
+ }
+ ]
}
- ],
- "requiredResourceCredentials" : [
+ ],
+ "roleMappings": [
{
- "type" : "Password",
- "input" : true,
- "secret" : true
+ "username": "admin",
+ "roles": ["admin"]
}
],
- "requiredOAuthClientCredentials" : [
+ "scopeMappings": [
{
- "type" : "Password",
- "input" : true,
- "secret" : true
+ "username": "loginclient",
+ "roles": ["*"]
}
],
- "users" : [
- {
- "username" : "wburke",
- "enabled" : true,
- "attributes" : {
- "email" : "bburke@redhat.com"
- },
- "credentials" : [
- { "type" : "Password",
- "value" : "userpassword" }
- ]
- },
- {
- "username" : "loginclient",
- "enabled" : true,
- "credentials" : [
- { "type" : "Password",
- "value" : "clientpassword" }
- ]
- },
- {
- "username" : "admin",
- "enabled" : true,
- "credentials" : [
- { "type" : "Password",
- "value" : "adminpassword" }
+ "applications": [
+ {
+ "name": "Application",
+ "enabled": true,
+ "roles": [
+ {
+ "name": "admin"
+ },
+ {
+ "name": "user"
+ }
+ ],
+ "roleMappings": [
+ {
+ "username": "wburke",
+ "roles": ["user"]
+ },
+ {
+ "username": "admin",
+ "roles": ["admin"]
+ }
+ ],
+ "scopeMappings": [
+ {
+ "username": "oauthclient",
+ "roles": ["user"]
+ }
]
- },
- {
- "username" : "oauthclient",
- "enabled" : true,
- "credentials" : [
- { "type" : "Password",
- "value" : "clientpassword" }
+ },
+ {
+ "name": "OtherApp",
+ "enabled": true,
+ "roles": [
+ {
+ "name": "admin"
+ },
+ {
+ "name": "user"
+ }
+ ],
+ "roleMappings": [
+ {
+ "username": "wburke",
+ "roles": ["user"]
+ },
+ {
+ "username": "admin",
+ "roles": ["admin"]
+ }
]
- }
- ],
- "roleMappings" : [
- {
- "username" : "admin",
- "roles" : ["admin"]
- }
- ],
- "scopeMappings" : [
- {
- "username" : "loginclient",
- "roles" : ["*"]
- }
- ],
- "resources" : [
- {
- "name" : "Application",
- "enabled" : true,
- "roles" : [
- { "name" : "admin" },
- { "name" : "user" }
- ],
- "roleMappings" : [
- {
- "username" : "wburke",
- "roles" : ["user"]
- },
- {
- "username" : "admin",
- "roles" : ["admin"]
- }
- ],
- "scopeMappings" : [
- {
- "username" : "oauthclient",
- "roles" : ["user"]
- }
- ]
- },
- {
- "name" : "OtherApp",
- "enabled" : true,
- "roles" : [
- { "name" : "admin" },
- { "name" : "user" }
- ],
- "roleMappings" : [
- {
- "username" : "wburke",
- "roles" : ["user"]
- },
- {
- "username" : "admin",
- "roles" : ["admin"]
- }
- ]
- }
+ }
- ]
+ ]
}
\ No newline at end of file
diff --git a/services/src/test/resources/testrealm-demo.json b/services/src/test/resources/testrealm-demo.json
new file mode 100755
index 0000000..d8497a8
--- /dev/null
+++ b/services/src/test/resources/testrealm-demo.json
@@ -0,0 +1,86 @@
+{
+ "realm": "demo",
+ "enabled": true,
+ "tokenLifespan": 300,
+ "accessCodeLifespan": 10,
+ "sslNotRequired": true,
+ "cookieLoginAllowed": true,
+ "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",
+ "requiredCredentials": [ "password" ],
+ "requiredApplicationCredentials": [ "password" ],
+ "requiredOAuthClientCredentials": [ "password" ],
+ "users" : [
+ {
+ "username" : "bburke@redhat.com",
+ "enabled" : true,
+ "attributes" : {
+ "email" : "bburke@redhat.com"
+ },
+ "credentials" : [
+ { "type" : "Password",
+ "value" : "password" }
+ ]
+ },
+ {
+ "username" : "third-party",
+ "enabled" : true,
+ "credentials" : [
+ { "type" : "Password",
+ "value" : "password" }
+ ]
+ }
+ ],
+ "roles": [
+ {
+ "name": "user",
+ "description": "Have User privileges"
+ },
+ {
+ "name": "admin",
+ "description": "Have Administrator privileges"
+ }
+ ],
+ "roleMappings": [
+ {
+ "username": "bburke@redhat.com",
+ "roles": ["user"]
+ },
+ {
+ "username": "third-party",
+ "roles": ["KEYCLOAK_IDENTITY_REQUESTER"]
+ }
+ ],
+ "scopeMappings": [
+ {
+ "username": "third-party",
+ "roles": ["user"]
+ }
+ ],
+ "applications": [
+ {
+ "name": "customer-portal",
+ "enabled": true,
+ "adminUrl": "http://localhost:8080/customer-portal/j_admin_request",
+ "useRealmMappings": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "password"
+ }
+ ]
+ },
+ {
+ "name": "product-portal",
+ "enabled": true,
+ "adminUrl": "http://localhost:8080/product-portal/j_admin_request",
+ "useRealmMappings": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "password"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file