diff --git a/functional-test/src/main/java/org/zanata/page/account/InactiveAccountPage.java b/functional-test/src/main/java/org/zanata/page/account/InactiveAccountPage.java
index 466d25e105..765f63f251 100644
--- a/functional-test/src/main/java/org/zanata/page/account/InactiveAccountPage.java
+++ b/functional-test/src/main/java/org/zanata/page/account/InactiveAccountPage.java
@@ -43,12 +43,13 @@ public HomePage clickResendActivationEmail() {
}
public InactiveAccountPage enterNewEmail(String email) {
- enterText(readyElement(By.id("inactiveAccountForm:emailField:email")), email);
+ enterText(readyElement(By.id("inactiveAccountForm:email:input:email"))
+ ,email);
return new InactiveAccountPage(getDriver());
}
public HomePage clickUpdateEmail() {
- clickElement(By.id("inactiveAccountForm:emailField:updateEmail"));
+ clickElement(By.id("inactiveAccountForm:email:input:updateEmail"));
return new HomePage(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/account/RegisterPage.java b/functional-test/src/main/java/org/zanata/page/account/RegisterPage.java
index 9e5c81b7b9..b48dbb6cf5 100644
--- a/functional-test/src/main/java/org/zanata/page/account/RegisterPage.java
+++ b/functional-test/src/main/java/org/zanata/page/account/RegisterPage.java
@@ -49,10 +49,10 @@ public class RegisterPage extends CorePage {
public static final String USERNAME_LENGTH_ERROR =
"size must be between 3 and 20";
- private By nameField = By.id("loginForm:name");
- private By emailField = By.id("loginForm:emailField:email");
- public By usernameField = By.id("loginForm:usernameField:username");
- private By passwordField = By.id("loginForm:passwordField:password");
+ private By nameField = By.id("loginForm:nameField:input:name");
+ private By emailField = By.id("loginForm:email:input:email");
+ public By usernameField = By.id("loginForm:username:input:username");
+ private By passwordField = By.id("loginForm:passwordField:input:password");
private By signUpButton = By.xpath("//input[@value='Sign Up']");
private By showHideToggleButton = By.className("js-form-password-toggle");
private By loginLink = By.linkText("Log In");
diff --git a/functional-test/src/main/java/org/zanata/page/account/ResetPasswordPage.java b/functional-test/src/main/java/org/zanata/page/account/ResetPasswordPage.java
index 8f861dd874..5bd6984814 100644
--- a/functional-test/src/main/java/org/zanata/page/account/ResetPasswordPage.java
+++ b/functional-test/src/main/java/org/zanata/page/account/ResetPasswordPage.java
@@ -33,7 +33,7 @@
@Slf4j
public class ResetPasswordPage extends BasePage {
- private By usernameEmailField = By.id("passwordResetRequestForm:usernameEmailField:usernameEmail");
+ private By usernameEmailField = By.id("passwordResetRequestForm:usernameEmail:input:usernameEmail");
private By submitButton = By.id("passwordResetRequestForm:submitRequest");
public ResetPasswordPage(WebDriver driver) {
diff --git a/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java b/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java
index d2c6f9ce3b..f3f59caa3e 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java
@@ -32,9 +32,9 @@
@Slf4j
public class EditRoleAssignmentPage extends BasePage {
- private By policySelect = By.id("role-rule-form:policyNameField:policyName");
- private By patternField = By.id("role-rule-form:identityPatternField:identityPattern");
- private By roleSelect = By.id("role-rule-form:roleField:roles");
+ private By policySelect = By.id("role-rule-form:policyName:input:policyName");
+ private By patternField = By.id("role-rule-form:identityPattern:input:identityPattern");
+ private By roleSelect = By.id("role-rule-form:role:input:roles");
private By saveButton = By.id("role-rule-form:save");
private By cancelButton = By.id("role-rule-form:cancel");
diff --git a/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java b/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java
index c70630cb6e..50f7ce1187 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java
@@ -38,8 +38,8 @@ public class ManageUserAccountPage extends BasePage {
public static String PASSWORD_ERROR = "Passwords do not match";
- private By passwordField = By.id("userdetailForm:passwordField:password");
- private By passwordConfirmField = By.id("userdetailForm:passwordConfirmField:confirm");
+ private By passwordField = By.id("userdetailForm:password:input:password");
+ private By passwordConfirmField = By.id("userdetailForm:passwordConfirm:input:confirm");
private By enabledField = By.id("userdetailForm:enabled");
private By saveButton = By.id("userdetailForm:userdetailSave");
private By cancelButton = By.id("userdetailForm:userdetailCancel");
@@ -76,7 +76,7 @@ public ManageUserAccountPage clickEnabled() {
public ManageUserAccountPage clickRole(String role) {
log.info("Click role {}", role);
- clickElement(readyElement(By.id("userdetailForm:rolesField:roles:"
+ clickElement(readyElement(By.id("userdetailForm:roles:input:roles:"
.concat(roleMap.get(role)))));
return new ManageUserAccountPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java b/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java
index 56803e0dbc..93520b9377 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java
@@ -34,20 +34,20 @@
@Slf4j
public class ServerConfigurationPage extends BasePage {
- private By urlField = By.id("serverConfigForm:urlField:url");
- public static By registerUrlField = By.id("serverConfigForm:registerField:registerUrl");
+ private By urlField = By.id("serverConfigForm:url:input:url");
+ public static By registerUrlField = By.id("serverConfigForm:register:input:registerUrl");
private By emailDomainField = By.id("serverConfigForm:emailDomainField:emailDomain");
- private By adminEmailField = By.id("serverConfigForm:adminEmailField:adminEml");
- public static By fromEmailField = By.id("serverConfigForm:fromEmailField:fromEml");
+ private By adminEmailField = By.id("serverConfigForm:adminEmail:input:adminEml");
+ public static By fromEmailField = By.id("serverConfigForm:fromEmail:input:fromEml");
private By enableLogCheck = By.id("serverConfigForm:enableLogCheck");
private By logLevelSelect = By.id("serverConfigForm:logEmailLvl");
- private By emailDestinationField = By.id("serverConfigForm:logDestEmailField:logDestEml");
- private By helpUrlField = By.id("serverConfigForm:helpUrlField:helpInput");
- private By termsUrlField = By.id("serverConfigForm:termsOfUseUrlField:termsOfUseUrlEml");
- private By piwikUrl = By.id("serverConfigForm:piwikUrlField:piwikUrlEml");
+ private By emailDestinationField = By.id("serverConfigForm:logDestEmail:input:logDestEml");
+ private By helpUrlField = By.id("serverConfigForm:helpUrl:input:helpInput");
+ private By termsUrlField = By.id("serverConfigForm:termsOfUseUrl:input:termsOfUseUrlEml");
+ private By piwikUrl = By.id("serverConfigForm:piwikUrl:input:piwikUrlEml");
private By piwikId = By.id("serverConfigForm:piwikIdSiteEml");
- private By maxConcurrentField = By.id("serverConfigForm:maxConcurrentPerApiKeyField:maxConcurrentPerApiKeyEml");
- private By maxActiveField = By.id("serverConfigForm:maxActiveRequestsPerApiKeyField:maxActiveRequestsPerApiKeyEml");
+ private By maxConcurrentField = By.id("serverConfigForm:maxConcurrentPerApiKey:input:maxConcurrentPerApiKeyEml");
+ private By maxActiveField = By.id("serverConfigForm:maxActiveRequestsPerApiKey:input:maxActiveRequestsPerApiKeyEml");
private By saveButton = By.id("serverConfigForm:save");
public ServerConfigurationPage(WebDriver driver) {
diff --git a/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java b/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java
index 87d9609820..49ecde3217 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java
@@ -32,8 +32,8 @@
@Slf4j
public class TranslationMemoryEditPage extends BasePage {
- private By idField = By.id("tmForm:slugField:slug");
- private By descriptionField = By.id("tmForm:descriptionField:description");
+ private By idField = By.id("tmForm:slug:input:slug");
+ private By descriptionField = By.id("tmForm:description:input:description");
private By saveButton = By.id("tmForm:save");
private By cancelButton = By.id("tmForm:cancel");
diff --git a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardAccountTab.java b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardAccountTab.java
index 719d328d37..63f0fc313e 100644
--- a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardAccountTab.java
+++ b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardAccountTab.java
@@ -42,10 +42,10 @@ public class DashboardAccountTab extends DashboardBasePage {
public static final String EMAIL_TAKEN_ERROR =
"This email address is already taken";
- private By emailField = By.id("email-update-form:emailField:email");
+ private By emailField = By.id("email-update-form:emailField:input:email");
private By updateEmailButton = By.id("email-update-form:updateEmailButton");
- private By oldPasswordField = By.id("passwordChangeForm:oldPasswordField:oldPassword");
- private By newPasswordField = By.id("passwordChangeForm:newPasswordField:newPassword");
+ private By oldPasswordField = By.id("passwordChangeForm:oldPasswordField:input:oldPassword");
+ private By newPasswordField = By.id("passwordChangeForm:newPasswordField:input:newPassword");
private By changePasswordButton = By.id("passwordChangeForm:changePasswordButton");
public DashboardAccountTab(WebDriver driver) {
diff --git a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardProfileTab.java b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardProfileTab.java
index 0f26c84361..8db908a478 100644
--- a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardProfileTab.java
+++ b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardProfileTab.java
@@ -32,7 +32,7 @@
@Slf4j
public class DashboardProfileTab extends DashboardBasePage {
- private By accountNameField = By.id("profileForm:nameField:accountName");
+ private By accountNameField = By.id("profileForm:nameField:input:accountName");
private By updateProfileButton = By.id("updateProfileButton");
public DashboardProfileTab(WebDriver driver) {
diff --git a/functional-test/src/main/java/org/zanata/page/groups/CreateVersionGroupPage.java b/functional-test/src/main/java/org/zanata/page/groups/CreateVersionGroupPage.java
index d55ea3d673..30dc79bc1e 100644
--- a/functional-test/src/main/java/org/zanata/page/groups/CreateVersionGroupPage.java
+++ b/functional-test/src/main/java/org/zanata/page/groups/CreateVersionGroupPage.java
@@ -41,9 +41,9 @@ public class CreateVersionGroupPage extends BasePage {
"must start and end with letter or number, and contain only " +
"letters, numbers, periods, underscores and hyphens.";
- private By groupIdField = By.id("group-form:slugField:slug");
- public By groupNameField = By.id("group-form:nameField:name");
- private By groupDescriptionField = By.id("group-form:descriptionField:description");
+ private By groupIdField = By.id("group-form:slug:input:slug");
+ public By groupNameField = By.id("group-form:name:input:name");
+ private By groupDescriptionField = By.id("group-form:description:input:description");
private By saveButton = By.id("group-form:group-create-new");
private By createNewButton = By.id("group-form:group-create-new");
diff --git a/functional-test/src/main/java/org/zanata/page/languages/ContactTeamPage.java b/functional-test/src/main/java/org/zanata/page/languages/ContactTeamPage.java
index d405b5cb29..f9c5459b70 100644
--- a/functional-test/src/main/java/org/zanata/page/languages/ContactTeamPage.java
+++ b/functional-test/src/main/java/org/zanata/page/languages/ContactTeamPage.java
@@ -31,7 +31,7 @@
@Slf4j
public class ContactTeamPage extends BasePage {
- private By messageField = By.id("contactCoordinatorForm:messageField:contact-coordinator-message");
+ private By messageField = By.id("contactCoordinatorForm:messageField:input:contact-coordinator-message");
private By sendButton = By.id("contact-coordinator-send-button");
public ContactTeamPage(WebDriver driver) {
diff --git a/functional-test/src/main/java/org/zanata/page/projects/CreateProjectPage.java b/functional-test/src/main/java/org/zanata/page/projects/CreateProjectPage.java
index af2c150e9d..75483b5485 100644
--- a/functional-test/src/main/java/org/zanata/page/projects/CreateProjectPage.java
+++ b/functional-test/src/main/java/org/zanata/page/projects/CreateProjectPage.java
@@ -31,9 +31,9 @@
@Slf4j
public class CreateProjectPage extends BasePage {
- private By idField = By.id("project-form:slugField:slug");
- private By nameField = By.id("project-form:nameField:name");
- private By descriptionField = By.id("project-form:descriptionField:description");
+ private By idField = By.id("project-form:slug:input:slug");
+ private By nameField = By.id("project-form:name:input:name");
+ private By descriptionField = By.id("project-form:description:input:description");
private By projectTypeList = By.id("project-types");
private By createButton = By.id("project-form:create-new");
diff --git a/functional-test/src/main/java/org/zanata/page/projects/ProjectsPage.java b/functional-test/src/main/java/org/zanata/page/projects/ProjectsPage.java
index 576f67bc04..7792994435 100644
--- a/functional-test/src/main/java/org/zanata/page/projects/ProjectsPage.java
+++ b/functional-test/src/main/java/org/zanata/page/projects/ProjectsPage.java
@@ -41,7 +41,7 @@ public class ProjectsPage extends BasePage {
private By createProjectButton = By.id("createProjectLink");
private By mainContentDiv = By.id("main_body_content");
- private By projectTable = By.id("main_content:form:projectList");
+ private By projectTable = By.id("form:projectList");
private By activeCheckBox = By.xpath("//*[@data-original-title='Filter active projects']");
private By readOnlyCheckBox = By.xpath("//*[@data-original-title='Filter read-only projects']");
diff --git a/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectGeneralTab.java b/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectGeneralTab.java
index 53ed50ed36..0298636e1e 100644
--- a/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectGeneralTab.java
+++ b/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectGeneralTab.java
@@ -38,12 +38,12 @@
@Slf4j
public class ProjectGeneralTab extends ProjectBasePage {
- private By projectIdField = By.id("settings-general-form:slugField:slug");
- private By projectNameField = By.id("settings-general-form:nameField:name");
- private By descriptionField = By.id("settings-general-form:descriptionField:description");
+ private By projectIdField = By.id("settings-general-form:slug:input:slug");
+ private By projectNameField = By.id("settings-general-form:name:input:name");
+ private By descriptionField = By.id("settings-general-form:description:input:description");
private By projectTypeList = By.id("project-types");
- private By homepageField = By.id("settings-general-form:homePageField:homePage");
- private By repoField = By.id("settings-general-form:repoField:repo");
+ private By homepageField = By.id("settings-general-form:homePage:input:homePage");
+ private By repoField = By.id("settings-general-form:repo:input:repo");
private By deleteButton = By.id("button-archive-project");
private By confirmDeleteButton = By.id("deleteButton");
private By confirmDeleteInput = By.id("confirmDeleteInput");
diff --git a/functional-test/src/main/java/org/zanata/page/projectversion/CreateVersionPage.java b/functional-test/src/main/java/org/zanata/page/projectversion/CreateVersionPage.java
index 992638ef47..f873f35a59 100644
--- a/functional-test/src/main/java/org/zanata/page/projectversion/CreateVersionPage.java
+++ b/functional-test/src/main/java/org/zanata/page/projectversion/CreateVersionPage.java
@@ -37,7 +37,7 @@ public class CreateVersionPage extends BasePage {
"must start and end with letter or number, and contain only " +
"letters, numbers, periods, underscores and hyphens.";
- public By projectVersionID = By.id("create-version-form:slugField:slug");
+ public By projectVersionID = By.id("create-version-form:slug:input:slug");
private By projectTypeSelection = By.id("create-version-form:project-type");
private By saveButton = By.id("create-version-form:button-create");
private By copyFromPreviousVersionChk = By.id("create-version-form:copy-from-version");
diff --git a/functional-test/src/main/java/org/zanata/page/utility/ContactAdminFormPage.java b/functional-test/src/main/java/org/zanata/page/utility/ContactAdminFormPage.java
index 72fc963246..a8a80f9a6c 100644
--- a/functional-test/src/main/java/org/zanata/page/utility/ContactAdminFormPage.java
+++ b/functional-test/src/main/java/org/zanata/page/utility/ContactAdminFormPage.java
@@ -34,7 +34,7 @@
public class ContactAdminFormPage extends BasePage {
private By subjectField = By.id("contactAdminForm:subjectField:subject");
- private By messageField = By.id("contactAdminForm:messageField:contact-admin-message");
+ private By messageField = By.id("contactAdminForm:messageField:input:contact-admin-message");
private By sendButton = By.id("contact-admin-send-button");
public ContactAdminFormPage(WebDriver driver) {
diff --git a/functional-test/src/test/java/org/zanata/feature/versionGroup/VersionGroupTest.java b/functional-test/src/test/java/org/zanata/feature/versionGroup/VersionGroupTest.java
index b7e7569084..6d2ac4ceda 100644
--- a/functional-test/src/test/java/org/zanata/feature/versionGroup/VersionGroupTest.java
+++ b/functional-test/src/test/java/org/zanata/feature/versionGroup/VersionGroupTest.java
@@ -46,9 +46,6 @@
@Category(DetailedTest.class)
public class VersionGroupTest extends ZanataTestCase {
-// @ClassRule
-// public static AddUsersRule addUsersRule = new AddUsersRule();
-
@ClassRule
public static SampleProjectRule sampleProjectRule = new SampleProjectRule();
diff --git a/zanata-war/pom.xml b/zanata-war/pom.xml
index 229bfd102b..193283b929 100644
--- a/zanata-war/pom.xml
+++ b/zanata-war/pom.xml
@@ -160,7 +160,6 @@
com.google.guava:guava-gwt
- org.jboss.spec.javax.el:jboss-el-api_2.2_spec
javax.servlet.jsp:jsp-api
org.apache.solr:solr-core
diff --git a/zanata-war/src/main/java/org/zanata/action/UserAction.java b/zanata-war/src/main/java/org/zanata/action/UserAction.java
index 44547890a3..cefecd934d 100644
--- a/zanata-war/src/main/java/org/zanata/action/UserAction.java
+++ b/zanata-war/src/main/java/org/zanata/action/UserAction.java
@@ -188,7 +188,7 @@ public String save() {
private String saveNewUser() {
if (password == null || !password.equals(confirm)) {
- StatusMessages.instance().addToControl("password", "Passwords do not match");
+ facesMessages.addToControl("password", "Passwords do not match");
return "failure";
}
@@ -211,7 +211,7 @@ private String saveExistingUser() {
// Check if a new password has been entered
if (password != null && !"".equals(password)) {
if (!password.equals(confirm)) {
- StatusMessages.instance().addToControl("password", "Passwords do not match");
+ facesMessages.addToControl("password", "Passwords do not match");
return "failure";
} else {
identityManager.changePassword(username, password);
diff --git a/zanata-war/src/main/java/org/zanata/ui/UIInputContainer.java b/zanata-war/src/main/java/org/zanata/ui/UIInputContainer.java
new file mode 100644
index 0000000000..fb535bd934
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/ui/UIInputContainer.java
@@ -0,0 +1,469 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2011, Red Hat, Inc., and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zanata.ui;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.faces.FacesException;
+import javax.faces.application.FacesMessage;
+import javax.faces.component.EditableValueHolder;
+import javax.faces.component.FacesComponent;
+import javax.faces.component.NamingContainer;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIComponentBase;
+import javax.faces.component.UIMessage;
+import javax.faces.component.UINamingContainer;
+import javax.faces.component.UIViewRoot;
+import javax.faces.component.html.HtmlOutputLabel;
+import javax.faces.context.FacesContext;
+import javax.faces.validator.BeanValidator;
+import javax.validation.Validation;
+import javax.validation.ValidationException;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+/**
+ * N.B. This class is copied from seam's migration demo project at https://github.com/seam/migration. Modified to suit us.
+ *
+ * UIInputContainer is a supplemental component for a JSF 2.0 composite component encapsulating one or more
+ * input components (EditableValueHolder), their corresponding message components (UIMessage)
+ * and a label (HtmlOutputLabel). This component takes care of wiring the label to the first input and the
+ * messages to each input in sequence. It also assigns two implicit attribute values, "required" and "invalid" to indicate that
+ * a required input field is present and whether there are any validation errors, respectively. To determine if a input field is
+ * required, both the required attribute is consulted. Finally, if the
+ * "label" attribute is not provided on the composite component, the label value will be derived from the id of the composite
+ * component, for convenience.
+ *
+ *
+ * Composite component definition example (minus layout):
+ *
+ *
+ *
+ *
+ * <cc:interface componentType="org.jboss.seam.faces.InputContainer"/>
+ * <cc:implementation>
+ * <h:outputLabel id="label" value="#{cc.attrs.label}:" styleClass="#{cc.attrs.invalid ? 'invalid' : ''}">
+ * <h:ouputText styleClass="required" rendered="#{cc.attrs.required}" value="*"/>
+ * </h:outputLabel>
+ * <cc:insertChildren/>
+ * <h:message id="message" errorClass="invalid message" rendered="#{cc.attrs.invalid}"/>
+ * </cc:implementation>
+ *
+ *
+ *
+ * Composite component usage example:
+ *
+ *
+ *
+ *
+ * <example:inputContainer id="name">
+ * <h:inputText id="input" value="#{person.name}"/>
+ * </example:inputContainer>
+ *
+ *
+ *
+ * Possible enhancements:
+ *
+ *
+ * - append styleClass "invalid" to label, inputs and messages when invalid
+ *
+ *
+ *
+ * NOTE: Firefox does not properly associate a label with the target input if the input id contains a colon (:), the default
+ * separator character in JSF. JSF 2 allows developers to set the value via an initialization parameter (context-param in
+ * web.xml) keyed to javax.faces.SEPARATOR_CHAR. We recommend that you override this setting to make the separator an underscore
+ * (_).
+ *
+ *
+ * @author Dan Allen
+ * @author Jose Rodolfo freitas
+ */
+@FacesComponent(UIInputContainer.COMPONENT_TYPE)
+public class UIInputContainer extends UIComponentBase implements NamingContainer {
+ /**
+ * The standard component type for this component.
+ */
+ public static final String COMPONENT_TYPE = "org.zanata.faces.InputContainer";
+
+ protected static final String HTML_ID_ATTR_NAME = "id";
+ protected static final String HTML_CLASS_ATTR_NAME = "class";
+ protected static final String HTML_STYLE_ATTR_NAME = "style";
+
+ private boolean beanValidationPresent = false;
+
+ public UIInputContainer() {
+ beanValidationPresent = isClassPresent("javax.validation.Validator");
+ }
+
+ Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
+
+ public Validator getValidator() {
+ return validator;
+ }
+
+ public void setValidator(Validator validator) {
+ this.validator = validator;
+ }
+
+ @Override
+ public String getFamily() {
+ return UINamingContainer.COMPONENT_FAMILY;
+ }
+
+ /**
+ * The name of the auto-generated composite component attribute that holds a boolean indicating whether the the template
+ * contains an invalid input.
+ */
+ public String getInvalidAttributeName() {
+ return "invalid";
+ }
+
+ /**
+ * The name of the auto-generated composite component attribute that holds a boolean indicating whether the template
+ * contains a required input.
+ */
+ public String getRequiredAttributeName() {
+ return "required";
+ }
+
+ /**
+ * The name of the composite component attribute that holds the string label for this set of inputs. If the label attribute
+ * is not provided, one will be generated from the id of the composite component or, if the id is defaulted, the name of the
+ * property bound to the first input.
+ */
+ public String getLabelAttributeName() {
+ return "label";
+ }
+
+ /**
+ * The name of the auto-generated composite component attribute that holds the elements in this input container. The
+ * elements include the label, a list of inputs and a cooresponding list of messages.
+ */
+ public String getElementsAttributeName() {
+ return "elements";
+ }
+
+ /**
+ * The name of the composite component attribute that holds a boolean indicating whether the component template should be
+ * enclosed in an HTML element, so that it be referenced from JavaScript.
+ */
+ public String getEncloseAttributeName() {
+ return "enclose";
+ }
+
+ public String getContainerElementName() {
+ return "div";
+ }
+
+ public String getDefaultLabelId() {
+ return "label";
+ }
+
+ public String getDefaultInputId() {
+ return "input";
+ }
+
+ public String getDefaultMessageId() {
+ return "message";
+ }
+
+ @Override
+ public void encodeBegin(final FacesContext context) throws IOException {
+ if (!isRendered()) {
+ return;
+ }
+
+ getAttributes().put(getInvalidAttributeName(), false);
+ super.encodeBegin(context);
+
+ InputContainerElements elements = scan(getFacet(UIComponent.COMPOSITE_FACET_NAME), null, context);
+ // assignIds(elements, context);
+ wire(elements, context);
+
+ getAttributes().put(getElementsAttributeName(), elements);
+
+ if (elements.hasValidationError()) {
+ getAttributes().put(getInvalidAttributeName(), true);
+ }
+ for (EditableValueHolder input : elements.getInputs()) {
+ // if we use FacesMessages.addToControl(id, message) directly in
+ // code, e.g. testing whether some value is unique in database, we
+ // need to set invalid to true in order to display the message. We
+ // also need to add the message to input client id because that's
+ // what's being set as for for h:message (see
+ // org.zanata.ui.UIInputContainer.InputContainerElements.wire).
+ Iterator messagesForInput = context.getMessages(
+ ((UIComponent) input).getClientId(context));
+ if (messagesForInput.hasNext()) {
+ getAttributes().put(getInvalidAttributeName(), true);
+ }
+ }
+
+ getAttributes().put(getRequiredAttributeName(), elements.hasRequiredInput());
+
+ /*
+ * for some reason, Mojarra is not filling Attribute Map with "label" key if label attr has an EL value, so I added a
+ * labelHasEmptyValue to guarantee that there was no label setted.
+ */
+ if (getValueExpression(getLabelAttributeName()) == null
+ && (!getAttributes().containsKey(getLabelAttributeName()) || labelHasEmptyValue(elements))) {
+ getAttributes().put(getLabelAttributeName(), generateLabel(elements, context));
+ }
+
+ if (Boolean.TRUE.equals(getAttributes().get(getEncloseAttributeName()))) {
+ startContainerElement(context);
+ }
+ }
+
+ @Override
+ public void encodeEnd(final FacesContext context) throws IOException {
+ if (!isRendered()) {
+ return;
+ }
+
+ super.encodeEnd(context);
+
+ if (Boolean.TRUE.equals(getAttributes().get(getEncloseAttributeName()))) {
+ endContainerElement(context);
+ }
+ }
+
+ protected void startContainerElement(final FacesContext context) throws IOException {
+ context.getResponseWriter().startElement(getContainerElementName(), this);
+ String style = (getAttributes().get("style") != null ? getAttributes().get("style").toString().trim() : null);
+ if (style.length() > 0) {
+ context.getResponseWriter().writeAttribute(HTML_STYLE_ATTR_NAME, style, HTML_STYLE_ATTR_NAME);
+ }
+ String styleClass = (getAttributes().get("styleClass") != null ? getAttributes().get("styleClass").toString().trim()
+ : null);
+ if (styleClass.length() > 0) {
+ context.getResponseWriter().writeAttribute(HTML_CLASS_ATTR_NAME, styleClass, HTML_CLASS_ATTR_NAME);
+ }
+ context.getResponseWriter().writeAttribute(HTML_ID_ATTR_NAME, getClientId(context), HTML_ID_ATTR_NAME);
+ }
+
+ protected void endContainerElement(final FacesContext context) throws IOException {
+ context.getResponseWriter().endElement(getContainerElementName());
+ }
+
+ protected String generateLabel(final InputContainerElements elements, final FacesContext context) {
+ String name = getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX) ? elements.getPropertyName(context) : getId();
+ return name.substring(0, 1).toUpperCase() + name.substring(1);
+ }
+
+ /**
+ * Walk the component tree branch built by the composite component and locate the input container elements.
+ *
+ * @return a composite object of the input container elements
+ */
+ protected InputContainerElements scan(final UIComponent component, InputContainerElements elements,
+ final FacesContext context) {
+ if (elements == null) {
+ elements = new InputContainerElements();
+ }
+
+ // NOTE we need to walk the tree ignoring rendered attribute because it's condition
+ // could be based on what we discover
+ if ((elements.getLabel() == null) && (component instanceof HtmlOutputLabel)) {
+ elements.setLabel((HtmlOutputLabel) component);
+ } else if (component instanceof EditableValueHolder) {
+ elements.registerInput((EditableValueHolder) component, getDefaultValidator(context), context);
+ } else if (component instanceof UIMessage) {
+ elements.registerMessage((UIMessage) component);
+ }
+ // may need to walk smarter to ensure "element of least suprise"
+ for (UIComponent child : component.getChildren()) {
+ scan(child, elements, context);
+ }
+
+ return elements;
+ }
+
+ // assigning ids seems to break form submissions, but I don't know why
+ public void assignIds(final InputContainerElements elements, final FacesContext context) {
+ boolean refreshIds = false;
+ if (getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
+ setId(elements.getPropertyName(context));
+ refreshIds = true;
+ }
+ UIComponent label = elements.getLabel();
+ if (label != null) {
+ if (label.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
+ label.setId(getDefaultLabelId());
+ } else if (refreshIds) {
+ label.setId(label.getId());
+ }
+ }
+ for (int i = 0, len = elements.getInputs().size(); i < len; i++) {
+ UIComponent input = (UIComponent) elements.getInputs().get(i);
+ if (input.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
+ input.setId(getDefaultInputId() + (i == 0 ? "" : (i + 1)));
+ } else if (refreshIds) {
+ input.setId(input.getId());
+ }
+ }
+ for (int i = 0, len = elements.getMessages().size(); i < len; i++) {
+ UIComponent msg = elements.getMessages().get(i);
+ if (msg.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
+ msg.setId(getDefaultMessageId() + (i == 0 ? "" : (i + 1)));
+ } else if (refreshIds) {
+ msg.setId(msg.getId());
+ }
+ }
+ }
+
+ /**
+ * Wire the label and messages to the input(s)
+ */
+ protected void wire(final InputContainerElements elements, final FacesContext context) {
+ elements.wire(context);
+ }
+
+ /**
+ * Get the default Bean Validation Validator to read the contraints for a property.
+ */
+ private Validator getDefaultValidator(final FacesContext context) throws FacesException {
+ if (!beanValidationPresent) {
+ return null;
+ }
+
+ ValidatorFactory validatorFactory;
+ Object cachedObject = context.getExternalContext().getApplicationMap().get(BeanValidator.VALIDATOR_FACTORY_KEY);
+ if (cachedObject instanceof ValidatorFactory) {
+ validatorFactory = (ValidatorFactory) cachedObject;
+ } else {
+ try {
+ validatorFactory = Validation.buildDefaultValidatorFactory();
+ } catch (ValidationException e) {
+ throw new FacesException("Could not build a default Bean Validator factory", e);
+ }
+ context.getExternalContext().getApplicationMap().put(BeanValidator.VALIDATOR_FACTORY_KEY, validatorFactory);
+ }
+ return validatorFactory.getValidator();
+ }
+
+ private boolean isClassPresent(final String fqcn) {
+ try {
+ if (Thread.currentThread().getContextClassLoader() != null) {
+ return Thread.currentThread().getContextClassLoader().loadClass(fqcn) != null;
+ } else {
+ return Class.forName(fqcn) != null;
+ }
+ } catch (ClassNotFoundException e) {
+ return false;
+ } catch (NoClassDefFoundError e) {
+ return false;
+ }
+ }
+
+ private boolean labelHasEmptyValue(InputContainerElements elements) {
+ if (elements.getLabel() == null || elements.getLabel().getValue() == null)
+ return false;
+ return (elements.getLabel().getValue().toString().trim().equals(":") || elements.getLabel().getValue().toString()
+ .trim().equals(""));
+ }
+
+ public static class InputContainerElements {
+ private String propertyName;
+ private HtmlOutputLabel label;
+ private final List inputs = new ArrayList();
+ private final List messages = new ArrayList();
+ private boolean validationError = false;
+
+ public HtmlOutputLabel getLabel() {
+ return label;
+ }
+
+ public void setLabel(final HtmlOutputLabel label) {
+ this.label = label;
+ }
+
+ public List getInputs() {
+ return inputs;
+ }
+
+ public void registerInput(final EditableValueHolder input, final Validator validator, final FacesContext context) {
+ inputs.add(input);
+
+ if (!input.isValid()) {
+ validationError = true;
+ } else if (!validationError) {
+ // optimization to avoid loop if already flagged
+ Iterator it = context.getMessages(((UIComponent) input).getClientId(context));
+ while (it.hasNext()) {
+ if (it.next().getSeverity().compareTo(FacesMessage.SEVERITY_WARN) >= 0) {
+ validationError = true;
+ break;
+ }
+ }
+ }
+ }
+
+ public List getMessages() {
+ return messages;
+ }
+
+ public void registerMessage(final UIMessage message) {
+ messages.add(message);
+ }
+
+ public boolean hasValidationError() {
+ return validationError;
+ }
+
+ public boolean hasRequiredInput() {
+ //We have to scan these each time as the value could change in an AJAX request
+ for (EditableValueHolder holder : inputs) {
+ if (holder.isRequired()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String getPropertyName(final FacesContext context) {
+ if (propertyName != null) {
+ return propertyName;
+ }
+
+ if (inputs.size() == 0) {
+ return null;
+ }
+
+ propertyName = (String) new ValueExpressionAnalyzer(((UIComponent) inputs.get(0)).getValueExpression("value"))
+ .getValueReference(context.getELContext()).getProperty();
+ return propertyName;
+ }
+
+ public void wire(final FacesContext context) {
+ int numInputs = inputs.size();
+ if (numInputs > 0) {
+ if (label != null) {
+ label.setFor(((UIComponent) inputs.get(0)).getClientId(context));
+ }
+ for (int i = 0, len = messages.size(); i < len; i++) {
+ if (i < numInputs) {
+ messages.get(i).setFor(((UIComponent) inputs.get(i)).getClientId(context));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/ui/ValueExpressionAnalyzer.java b/zanata-war/src/main/java/org/zanata/ui/ValueExpressionAnalyzer.java
new file mode 100644
index 0000000000..cc7c96012e
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/ui/ValueExpressionAnalyzer.java
@@ -0,0 +1,177 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2011, Red Hat, Inc., and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zanata.ui;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+import java.util.Locale;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.ValueExpression;
+import javax.el.ValueReference;
+import javax.el.VariableMapper;
+import javax.faces.el.CompositeComponentExpressionHolder;
+
+/**
+ * N.B. This class is copied from seam's migration demo project at https://github.com/seam/migration.
+ *
+ * Analyzes a {@link ValueExpression} and provides access to the base object and property name referenced by the expression.
+ *
+ *
+ *
+ * The getValueReference(ELContext) method returns a {@link ValueReference} object, which encapsulates the base object and
+ * property name to which the expression maps. This process works by resolving the expression up until the last segment.
+ *
+ *
+ *
+ * Although access to the ValueReference was added in EL 2.2, the feature does not work correctly, which is why this custom
+ * class is required.
+ *
+ *
+ * @author Dan Allen
+ */
+class ValueExpressionAnalyzer {
+ private ValueExpression expression;
+
+ public ValueExpressionAnalyzer(ValueExpression expression) {
+ this.expression = expression;
+ }
+
+ public ValueReference getValueReference(ELContext elContext) {
+ InterceptingResolver resolver = new InterceptingResolver(elContext.getELResolver());
+ try {
+ expression.setValue(decorateELContext(elContext, resolver), null);
+ } catch (ELException ele) {
+ return null;
+ }
+ ValueReference reference = resolver.getValueReference();
+ if (reference != null) {
+ Object base = reference.getBase();
+ if (base instanceof CompositeComponentExpressionHolder) {
+ ValueExpression ve = ((CompositeComponentExpressionHolder) base)
+ .getExpression((String) reference.getProperty());
+ if (ve != null) {
+ this.expression = ve;
+ reference = getValueReference(elContext);
+ }
+ }
+ }
+ return reference;
+ }
+
+ private ELContext decorateELContext(final ELContext context, final ELResolver resolver) {
+ return new ELContext() {
+ // punch in our new ELResolver
+ @Override
+ public ELResolver getELResolver() {
+ return resolver;
+ }
+
+ // The rest of the methods simply delegate to the existing context
+ @Override
+ public Object getContext(Class key) {
+ return context.getContext(key);
+ }
+
+ @Override
+ public Locale getLocale() {
+ return context.getLocale();
+ }
+
+ @Override
+ public boolean isPropertyResolved() {
+ return context.isPropertyResolved();
+ }
+
+ @Override
+ public void putContext(Class key, Object contextObject) {
+ context.putContext(key, contextObject);
+ }
+
+ @Override
+ public void setLocale(Locale locale) {
+ context.setLocale(locale);
+ }
+
+ @Override
+ public void setPropertyResolved(boolean resolved) {
+ context.setPropertyResolved(resolved);
+ }
+
+ @Override
+ public FunctionMapper getFunctionMapper() {
+ return context.getFunctionMapper();
+ }
+
+ @Override
+ public VariableMapper getVariableMapper() {
+ return context.getVariableMapper();
+ }
+ };
+ }
+
+ private static class InterceptingResolver extends ELResolver {
+ private ELResolver delegate;
+ private ValueReference valueReference;
+
+ public InterceptingResolver(ELResolver delegate) {
+ this.delegate = delegate;
+ }
+
+ public ValueReference getValueReference() {
+ return valueReference;
+ }
+
+ // Capture the base and property rather than write the value
+ @Override
+ public void setValue(ELContext context, Object base, Object property, Object value) {
+ if (base != null && property != null) {
+ context.setPropertyResolved(true);
+ valueReference = new ValueReference(base, property.toString());
+ }
+ }
+
+ // The rest of the methods simply delegate to the existing context
+ @Override
+ public Object getValue(ELContext context, Object base, Object property) {
+ return delegate.getValue(context, base, property);
+ }
+
+ @Override
+ public Class> getType(ELContext context, Object base, Object property) {
+ return delegate.getType(context, base, property);
+ }
+
+ @Override
+ public boolean isReadOnly(ELContext context, Object base, Object property) {
+ return delegate.isReadOnly(context, base, property);
+ }
+
+ @Override
+ public Iterator getFeatureDescriptors(ELContext context, Object base) {
+ return delegate.getFeatureDescriptors(context, base);
+ }
+
+ @Override
+ public Class> getCommonPropertyType(ELContext context, Object base) {
+ return delegate.getCommonPropertyType(context, base);
+ }
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/ui/faces/FacesMessages.java b/zanata-war/src/main/java/org/zanata/ui/faces/FacesMessages.java
index faae0c26c7..3a6c8e1283 100644
--- a/zanata-war/src/main/java/org/zanata/ui/faces/FacesMessages.java
+++ b/zanata-war/src/main/java/org/zanata/ui/faces/FacesMessages.java
@@ -27,11 +27,15 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
+import java.util.Stack;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import org.jboss.seam.ScopeType;
@@ -43,6 +47,7 @@
import org.jboss.seam.core.Interpolator;
import org.zanata.i18n.Messages;
import org.zanata.util.ServiceLocator;
+import com.google.common.collect.Lists;
/**
* Utility to allow for easy handling of JSF messages. Serves as a replacement
@@ -87,26 +92,39 @@ public void beforeRenderResponse() {
*/
private String getClientId(String id) {
FacesContext facesContext = FacesContext.getCurrentInstance();
- return getClientId(facesContext.getViewRoot(), id, facesContext);
- }
- private static String getClientId(UIComponent component, String id,
- FacesContext facesContext) {
- String componentId = component.getId();
- if (componentId != null && componentId.equals(id)) {
- return component.getClientId(facesContext);
- } else {
- Iterator iter = component.getFacetsAndChildren();
- while (iter.hasNext()) {
- UIComponent child = (UIComponent) iter.next();
- String clientId = getClientId(child, id, facesContext);
- if (clientId != null)
- return clientId;
+ // we search from backwards, so for a component tree A->B->C, we search
+ // id from C then B then A for a match of id. If we found
+ // C.getId().equals(id), we will use C.getClientId()
+ Stack uiComponentStack = new Stack<>();
+ addComponentToStack(uiComponentStack, facesContext.getViewRoot());
+
+ while (!uiComponentStack.empty()) {
+ UIComponent pop = uiComponentStack.pop();
+ if (pop.getId() != null && id.equals(pop.getId())) {
+ return pop.getClientId();
}
- return null;
}
+ return null;
}
+ private void addComponentToStack(Stack uiComponentStack,
+ UIComponent component) {
+ uiComponentStack.push(component);
+ Iterator iter = component.getFacetsAndChildren();
+
+ Queue children = new LinkedList<>();
+ while (iter.hasNext()) {
+ UIComponent next = iter.next();
+ uiComponentStack.push(next);
+ children.add(next);
+ }
+ for (UIComponent child : children) {
+ addComponentToStack(uiComponentStack, child);
+ }
+ }
+
+
/**
* Add a status message, looking up the message in the resource bundle using
* the provided key. If the message is found, it is used, otherwise, the
diff --git a/zanata-war/src/main/webapp/WEB-INF/layout/admin/contact_admin_modal.xhtml b/zanata-war/src/main/webapp/WEB-INF/layout/admin/contact_admin_modal.xhtml
index 063e1aea28..713c55e8bc 100644
--- a/zanata-war/src/main/webapp/WEB-INF/layout/admin/contact_admin_modal.xhtml
+++ b/zanata-war/src/main/webapp/WEB-INF/layout/admin/contact_admin_modal.xhtml
@@ -3,6 +3,7 @@
xmlns:s="http://jboss.org/schema/seam/taglib"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
+ xmlns:zanata="http://java.sun.com/jsf/composite/zanata"
class="modal" id="contactAdminDialog" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true">
@@ -57,12 +58,12 @@
oncomplete="getContactAdminForm().find('#contact-admin-cancel-button').click()"/>