/** * Copyright (C) 2014 BonitaSoft S.A. * BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2.0 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.bonitasoft.forms.client.view.controller; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import org.bonitasoft.forms.client.i18n.FormsResourceBundle; import org.bonitasoft.forms.client.model.FormFieldValue; import org.bonitasoft.forms.client.model.FormType; import org.bonitasoft.forms.client.model.FormWidget; import org.bonitasoft.forms.client.model.ReducedFormPage; import org.bonitasoft.forms.client.model.ReducedFormValidator; import org.bonitasoft.forms.client.model.ReducedFormWidget; import org.bonitasoft.forms.client.model.ReducedHtmlTemplate; import org.bonitasoft.forms.client.model.WidgetType; import org.bonitasoft.forms.client.model.exception.AbortedFormException; import org.bonitasoft.forms.client.model.exception.CanceledFormException; import org.bonitasoft.forms.client.model.exception.FileTooBigException; import org.bonitasoft.forms.client.model.exception.FormAlreadySubmittedException; import org.bonitasoft.forms.client.model.exception.FormInErrorException; import org.bonitasoft.forms.client.model.exception.SkippedFormException; import org.bonitasoft.forms.client.rpc.FormsServiceAsync; import org.bonitasoft.forms.client.view.FormsAsyncCallback; import org.bonitasoft.forms.client.view.SupportedFieldTypes; import org.bonitasoft.forms.client.view.common.DOMUtils; import org.bonitasoft.forms.client.view.common.RpcFormsServices; import org.bonitasoft.forms.client.view.common.URLUtils; import org.bonitasoft.forms.client.view.common.URLUtilsFactory; import org.bonitasoft.forms.client.view.widget.ElementAttributeSupport; import org.bonitasoft.forms.client.view.widget.FormButtonWidget; import org.bonitasoft.forms.client.view.widget.FormFieldWidget; import org.bonitasoft.forms.client.view.widget.FormMessageWidget; import org.bonitasoft.forms.client.view.widget.FormValidationMessageWidget; import org.bonitasoft.web.rest.model.user.User; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.http.client.Request; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestCallback; import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Response; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Frame; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; /** * Pages view controller (handles the page flow for processes or tasks) * * @author Anthony Birembaut */ public class FormPagesViewController { /** * action type after a form validation */ protected static enum ACTION_TYPE { PREVIOUS, NEXT, SUBMIT }; /** * mandatory form field symbol */ protected String mandatoryFieldSymbol; /** * mandatory form field label */ protected String mandatoryFieldLabel; /** * mandatory form field symbol classes */ protected String mandatoryFieldClasses; /** * forms RPC service */ protected FormsServiceAsync formsServiceAsync; /** * Utility Class form DOM manipulation */ protected DOMUtils domUtils = DOMUtils.getInstance(); /** * Utility Class form URL manipulation */ protected URLUtils urlUtils = URLUtilsFactory.getInstance(); /** * index of the currently displayed page in the list of pages ids */ protected int currentPageIndex = 0; /** * the pressed submit button */ protected Widget pressedButton; /** * the pressed label buttons */ protected Set<Label> disabledLabelButtons = new HashSet<Label>(); /** * indicates whether the current page is valid or not */ protected boolean isCurrentPageValid; /** * List of submitted pages ids */ protected List<String> followedPagesIds = new ArrayList<String>(); /** * Maintained Map of already displayed pages (allow to diplay the previous * page without reloading everything) */ protected Map<String, ReducedFormPage> formPages = new HashMap<String, ReducedFormPage>(); /** * Map of form fields already displayed in the page flow */ protected Map<String, FormFieldWidget> fieldWidgets = new TreeMap<String, FormFieldWidget>(); /** * Map of form buttons already displayed in the page flow */ protected Map<String, FormButtonWidget> buttonWidgets = new HashMap<String, FormButtonWidget>(); /** * Map of form messages already displayed in the page flow */ protected Map<String, FormMessageWidget> messageWidgets = new HashMap<String, FormMessageWidget>(); /** * Map of form iframes already displayed in the page flow */ protected Map<String, Frame> frameWidgets = new HashMap<String, Frame>(); /** * Map of the flow's widget values */ protected Map<String, FormFieldValue> widgetValues = new HashMap<String, FormFieldValue>(); /** * Handler allowing to display a page after the RPC call retrieving its * definition */ protected FormPageHandler formsPageHandler = new FormPageHandler(); /** * Click Handler dealing with form submission */ protected SubmitClickHandler submitClickHandler = new SubmitClickHandler(); /** * application template panel (can be null in form only mode) */ protected HTMLPanel applicationHTMLPanel; /** * current page template panel */ protected HTMLPanel pageHTMLPanel; /** * The logged in user */ protected User user; /** * The current form ID */ protected String formID; /** * The element ID in which to insert the form */ protected String elementId; /** * The map of URL parameters */ protected Map<String, Object> urlContext; /** * Widget type to focused when loading is hidden * */ protected List<WidgetType> widgetTypesToFocus = Arrays.asList( WidgetType.TEXTAREA, WidgetType.TEXTBOX, WidgetType.SUGGESTBOX, WidgetType.RICH_TEXTAREA, WidgetType.SUGGESTBOX_ASYNC, WidgetType.PASSWORD, WidgetType.FILEUPLOAD ); /** * Constructor */ public FormPagesViewController(final String formID, final Map<String, Object> contextMap, final ReducedFormPage firstPage, final HTMLPanel applicationHTMLPanel, final User user, final String elementId) { this.user = user; this.formID = formID; this.elementId = elementId; urlContext = contextMap; this.applicationHTMLPanel = applicationHTMLPanel; final String pageId = firstPage.getPageId(); followedPagesIds.add(pageId); formPages.put(pageId, firstPage); formsServiceAsync = RpcFormsServices.getFormsService(); } /** * Display the page at the given index * * @param newIndex * index of the page in the page list */ public void displayPage(final int newIndex) throws IndexOutOfBoundsException { if (newIndex >= 0) { if (newIndex < currentPageIndex) { followedPagesIds.remove(currentPageIndex); currentPageIndex = newIndex; final String newPageId = followedPagesIds.get(newIndex); final ReducedFormPage formPage = formPages.get(newPageId); buildPage(formPage, true, false); } else { if (newIndex == 0) { final String newPageId = followedPagesIds.get(currentPageIndex); final ReducedFormPage formPage = formPages.get(newPageId); buildPage(formPage, false, true); } else { final String currentPageId = followedPagesIds.get(currentPageIndex); final String nextPageExpressionId = formPages.get(currentPageId).getNextPageExpressionId(); currentPageIndex = newIndex; final Map<String, FormFieldValue> fieldValues = new HashMap<String, FormFieldValue>(); fieldValues.putAll(widgetValues); if (formID != null) { formsServiceAsync.getFormNextPage(formID, urlContext, nextPageExpressionId, fieldValues, formsPageHandler); } } } } else { enableButtons(true); throw new IndexOutOfBoundsException("No form available"); } } /** * Handler allowing to display the form */ protected class FormPageHandler extends FormsAsyncCallback<ReducedFormPage> { /** * {@inheritDoc} */ @Override public void onSuccess(final ReducedFormPage reducedFormPage) { try { RequestBuilder theRequestBuilder; final String theURL = urlUtils.buildLayoutURL(reducedFormPage.getPageTemplate().getBodyContentId(), (String) urlContext.get(URLUtils.FORM_ID), (String) urlContext.get(URLUtils.TASK_ID_PARAM), true); GWT.log("Calling the Form Layout Download Servlet with query: " + theURL); theRequestBuilder = new RequestBuilder(RequestBuilder.GET, theURL); theRequestBuilder.setCallback(new RequestCallback() { @Override public void onError(final Request aRequest, final Throwable exception) { final String errorMessage = FormsResourceBundle.getErrors().applicationConfigRetrievalError(); formsServiceAsync.getApplicationErrorTemplate(reducedFormPage.getPageId(), urlContext, new ErrorPageHandler(null, reducedFormPage.getPageId(), errorMessage, exception, elementId)); } @Override public void onResponseReceived(final Request request, final Response response) { final String pageId = reducedFormPage.getPageId(); followedPagesIds.add(pageId); boolean hasAlreadyBeenDisplayed = false; if (formPages.get(pageId) != null) { hasAlreadyBeenDisplayed = true; } reducedFormPage.getPageTemplate().setBodyContent(response.getText()); formPages.put(pageId, reducedFormPage); buildPage(reducedFormPage, hasAlreadyBeenDisplayed, true); } }); theRequestBuilder.send(); } catch (final RequestException e) { GWT.log("Error while trying to query the form layout :" + e.getMessage()); } } @Override public void onUnhandledFailure(final Throwable t) { String errorMessage = null; try { throw t; } catch (final CanceledFormException e) { errorMessage = FormsResourceBundle.getMessages().cancelledTaskMessage(); // } catch (SuspendedFormException e) { // errorMessage = FormsResourceBundle.getMessages().suspendedTaskMessage(); } catch (final AbortedFormException e) { errorMessage = FormsResourceBundle.getMessages().abortedFormMessage(); } catch (final FormInErrorException e) { errorMessage = FormsResourceBundle.getMessages().errorTaskMessage(); } catch (final SkippedFormException e) { errorMessage = FormsResourceBundle.getMessages().skippedFormMessage(); } catch (final FormAlreadySubmittedException e) { errorMessage = FormsResourceBundle.getErrors().formAlreadySubmittedError(); } catch (final Throwable e) { errorMessage = FormsResourceBundle.getErrors().pageRetrievalError(); } if (formID != null) { formsServiceAsync.getApplicationErrorTemplate(formID, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, t, elementId)); } enableButtons(true); } } /** * Build the page (template + form fields) * * @param formPage * the page definition * @param hasAlreadyBeenDisplayed * indicates whether the page has already been displayed or not * @param isNextPage * indicate if the page to display is the next page */ protected void buildPage(final ReducedFormPage formPage, final boolean hasAlreadyBeenDisplayed, final boolean isNextPage) { enableButtons(false); final ReducedHtmlTemplate pageTemplate = formPage.getPageTemplate(); if (pageHTMLPanel != null) { if (applicationHTMLPanel != null) { applicationHTMLPanel.remove(pageHTMLPanel); } } pageHTMLPanel = new HTMLPanel(pageTemplate.getBodyContent()); final String onloadAttributeValue = domUtils.insertPageTemplate(pageTemplate.getHeadNodes(), pageHTMLPanel, pageTemplate.getBodyAttributes(), applicationHTMLPanel, elementId); final Element pageLabelElement = DOM.getElementById(DOMUtils.PAGE_LABEL_ELEMENT_ID); if (pageLabelElement != null) { String pageLabel = formPage.getPageLabel(); if (pageLabel.startsWith("#")) { pageLabel = FormsResourceBundle.getMessages().caseStartLabelPrefix() + " " + pageLabel.substring(1); } domUtils.insertInElement(pageHTMLPanel, DOMUtils.PAGE_LABEL_ELEMENT_ID, pageLabel, !formPage.allowHTMLInLabel()); } if (FormType.entry == formPage.getFormType()) { buildEditMode(pageHTMLPanel, formPage, hasAlreadyBeenDisplayed, isNextPage, onloadAttributeValue); final String autoSubmit = (String) urlContext.get(URLUtils.AUTO_SUBMIT_PARAM); if (autoSubmit != null) { final FormButtonWidget autoSubmitButton = buttonWidgets.get(autoSubmit); if (autoSubmitButton != null && autoSubmitButton.getWidgetType().equals(WidgetType.BUTTON_SUBMIT)) { disableButtons(autoSubmitButton.getButton()); validatePage(ACTION_TYPE.SUBMIT); } } } else { buildViewMode(pageHTMLPanel, formPage, hasAlreadyBeenDisplayed, isNextPage, onloadAttributeValue); } domUtils.overrideBrowserNativeInputs(); resizeFrame(); domUtils.hideLoading(); focusFirstField(); } /** * Focus the first field "focusable" */ protected void focusFirstField() { final Iterator<Entry<String, FormFieldWidget>> fieldWidgestIterator = fieldWidgets.entrySet().iterator(); boolean focused = false; while (fieldWidgestIterator.hasNext() && !focused) { final Entry<String, FormFieldWidget> reducedFormWidget = fieldWidgestIterator.next(); final FormFieldWidget formFieldWidget = reducedFormWidget.getValue(); if (formFieldWidget != null && widgetTypesToFocus.contains(formFieldWidget.getType())) { if (formFieldWidget.getType().equals(WidgetType.RICH_TEXTAREA)) { formFieldWidget.getElement().focus(); } else { formFieldWidget.setFocusOn(); } focused = true; } } } /** * Insert the widgets in the page for the view mode * * @param pageHTMLPanel * the HTMLPanel * @param formPage * the page definition * @param hasAlreadyBeenDisplayed * indicates whether the page has already been displayed or not * @param isNextPage * indicate if the page to display is the next page * @param onloadAttributeValue * the onload attribute value if it exists */ protected void buildViewMode(final HTMLPanel pageHTMLPanel, final ReducedFormPage formPage, final boolean hasAlreadyBeenDisplayed, final boolean isNextPage, final String onloadAttributeValue) { final List<ReducedFormWidget> formWidgets = formPage.getFormWidgets(); for (final ReducedFormWidget formWidgetData : formWidgets) { final String widgetId = formWidgetData.getId(); // for buttons if (formWidgetData.getType().name().startsWith("BUTTON") && !formWidgetData.getType().equals(WidgetType.BUTTON_SUBMIT)) { FormButtonWidget formButtonWidget = null; if (hasAlreadyBeenDisplayed) { formButtonWidget = buttonWidgets.get(widgetId); } else { setButtonLabel(formWidgetData); formButtonWidget = new FormButtonWidget(formWidgetData); if (!formWidgetData.getType().equals(WidgetType.BUTTON)) { addClickListener(formButtonWidget, false); } buttonWidgets.put(widgetId, formButtonWidget); } insertWidget(pageHTMLPanel, formWidgetData, formButtonWidget, "bonita_form_button_entry"); // for text messages } else if (formWidgetData.getType().equals(WidgetType.MESSAGE)) { FormMessageWidget formMessageWidget = null; if (getValueFromHistory(hasAlreadyBeenDisplayed, isNextPage, formWidgetData)) { formMessageWidget = messageWidgets.get(widgetId); } else { formMessageWidget = new FormMessageWidget(formWidgetData); messageWidgets.put(widgetId, formMessageWidget); } insertWidget(pageHTMLPanel, formWidgetData, formMessageWidget, "bonita_form_entry"); // for iframes } else if (formWidgetData.getType().equals(WidgetType.IFRAME)) { Frame formFrameWidget = null; if (getValueFromHistory(hasAlreadyBeenDisplayed, isNextPage, formWidgetData)) { formFrameWidget = frameWidgets.get(widgetId); } else { formFrameWidget = new Frame((String) formWidgetData.getInitialFieldValue().getValue()); frameWidgets.put(widgetId, formFrameWidget); } formFrameWidget.setStyleName("bonita_iframe"); final String widgetStyle = formWidgetData.getInputStyle(); if (widgetStyle != null && widgetStyle.length() > 0) { formFrameWidget.addStyleName(widgetStyle); } final Element formFrameElement = formFrameWidget.getElement(); formFrameElement.setAttribute("frameBorder", "0"); formFrameElement.setAttribute("allowTransparency", "true"); addHTMLAttributes(formFrameWidget, formWidgetData); insertWidget(pageHTMLPanel, formWidgetData, formFrameWidget, "bonita_form_entry"); // for form fields (Widgets other that buttons and text) } else { if (formWidgetData.isViewPageWidget()) { FormFieldWidget formFieldWidget = null; if (getValueFromHistory(hasAlreadyBeenDisplayed, isNextPage, formWidgetData)) { formFieldWidget = fieldWidgets.get(widgetId); } else { formFieldWidget = new FormFieldWidget(formWidgetData, urlContext, mandatoryFieldSymbol, mandatoryFieldClasses); fieldWidgets.put(widgetId, formFieldWidget); } insertWidget(pageHTMLPanel, formWidgetData, formFieldWidget, "bonita_form_entry"); } } } if (onloadAttributeValue != null) { domUtils.javascriptEval(onloadAttributeValue); } } protected void addHTMLAttributes(final Widget fieldWidget, final ReducedFormWidget formWidgetData) { final ElementAttributeSupport elementAttributeSupport = new ElementAttributeSupport(); elementAttributeSupport.addHtmlAttributes(fieldWidget, formWidgetData.getHtmlAttributes()); } /** * Insert the widgets in the page for the edit mode * * @param pageHTMLPanel * the HTMLPanel * @param formPage * the page definition * @param hasAlreadyBeenDisplayed * indicates whether the page has already been displayed or not * @param isNextPage * indicate if the page to display is the next page * @param onloadAttributeValue * the onload attribute value if it exists */ protected void buildEditMode(final HTMLPanel pageHTMLPanel, final ReducedFormPage formPage, final boolean hasAlreadyBeenDisplayed, final boolean isNextPage, final String onloadAttributeValue) { final List<ReducedFormWidget> formWidgets = formPage.getFormWidgets(); for (final ReducedFormWidget formWidgetData : formWidgets) { final String widgetId = formWidgetData.getId(); // for buttons if (formWidgetData.getType().name().startsWith("BUTTON")) { FormButtonWidget formButtonWidget = null; if (hasAlreadyBeenDisplayed) { formButtonWidget = buttonWidgets.get(widgetId); } else { setButtonLabel(formWidgetData); formButtonWidget = new FormButtonWidget(formWidgetData); if (!formWidgetData.getType().equals(WidgetType.BUTTON)) { addClickListener(formButtonWidget, true); } buttonWidgets.put(widgetId, formButtonWidget); } insertWidget(pageHTMLPanel, formWidgetData, formButtonWidget, "bonita_form_button_entry"); // for text messages } else if (formWidgetData.getType().equals(WidgetType.MESSAGE)) { FormMessageWidget formMessageWidget = null; if (getValueFromHistory(hasAlreadyBeenDisplayed, isNextPage, formWidgetData)) { formMessageWidget = messageWidgets.get(widgetId); } else { formMessageWidget = new FormMessageWidget(formWidgetData); messageWidgets.put(widgetId, formMessageWidget); } insertWidget(pageHTMLPanel, formWidgetData, formMessageWidget, "bonita_form_entry"); // for iframes } else if (formWidgetData.getType().equals(WidgetType.IFRAME)) { Frame formFrameWidget = null; if (getValueFromHistory(hasAlreadyBeenDisplayed, isNextPage, formWidgetData)) { formFrameWidget = frameWidgets.get(widgetId); } else { formFrameWidget = new Frame((String) formWidgetData.getInitialFieldValue().getValue()); frameWidgets.put(widgetId, formFrameWidget); } formFrameWidget.setStyleName("bonita_iframe"); final String widgetStyle = formWidgetData.getInputStyle(); if (widgetStyle != null && widgetStyle.length() > 0) { formFrameWidget.addStyleName(widgetStyle); } final Element formFrameElement = formFrameWidget.getElement(); formFrameElement.setAttribute("frameBorder", "0"); formFrameElement.setAttribute("allowTransparency", "true"); addHTMLAttributes(formFrameWidget, formWidgetData); insertWidget(pageHTMLPanel, formWidgetData, formFrameWidget, "bonita_form_entry"); // for form fields (Widgets other that buttons and text) } else { // set field value from the URL if (!formWidgetData.isReadOnly()) { setFieldValueFromURL(formWidgetData, widgetId); } FormFieldWidget formFieldWidget = null; if (getValueFromHistory(hasAlreadyBeenDisplayed, isNextPage, formWidgetData)) { formFieldWidget = fieldWidgets.get(widgetId); } else { formFieldWidget = new FormFieldWidget(formWidgetData, urlContext, mandatoryFieldSymbol, mandatoryFieldClasses); fieldWidgets.put(widgetId, formFieldWidget); } insertWidget(pageHTMLPanel, formWidgetData, formFieldWidget, "bonita_form_entry"); } } if (onloadAttributeValue != null) { domUtils.javascriptEval(onloadAttributeValue); } } /** * Check if the value of the field should be retrieved from the history or recalculated * * @param hasAlreadyBeenDisplayed * indicates whether the page has already been displayed or not * @param isNextPage * indicate if the page to display is the next page * @param formWidgetData * the widget definition * @return true if the value of the field should be retrieved from the history, false otherwise */ protected boolean getValueFromHistory(final boolean hasAlreadyBeenDisplayed, final boolean isNextPage, final ReducedFormWidget formWidgetData) { if (isNextPage) { return hasAlreadyBeenDisplayed && !formWidgetData.hasDynamicValue(); } else { return true; } } /** * Insert the widget in the page * * @param pageHTMLPanel * the HTMLPanel * @param formWidgetData * the widget definition * @param widget * the widget to insert * @param containerStyle * the style to apply to the container */ protected void insertWidget(final HTMLPanel pageHTMLPanel, final ReducedFormWidget formWidgetData, final Widget widget, final String containerStyle) { if (formWidgetData.isDisplayCondition()) { final Element widgetParentElement = pageHTMLPanel.getElementById(formWidgetData.getId()); final String widgetStyle; if (formWidgetData.getStyle() != null && formWidgetData.getStyle().length() > 0) { widgetStyle = containerStyle + " " + formWidgetData.getStyle(); } else { widgetStyle = containerStyle; } if (widgetParentElement != null) { pageHTMLPanel.add(widget, widgetParentElement); widgetParentElement.addClassName(widgetStyle); } else { GWT.log("An element with id " + formWidgetData.getId() + " is missing from the page template."); } } } /** * Check in the URL if the initial value of the field is specified and override it * * @param formWidgetData * the widget data * @param widgetId * the id of the widget */ protected void setFieldValueFromURL(final ReducedFormWidget formWidgetData, final String widgetId) { final Object widgetValueObjectInURL = urlContext.get(widgetId); if (widgetValueObjectInURL != null) { final String widgetValueInURL = (String) widgetValueObjectInURL; try { switch (formWidgetData.getType()) { case TEXTBOX: case TEXTAREA: case LISTBOX_SIMPLE: case RADIOBUTTON_GROUP: case PASSWORD: case SUGGESTBOX: case SUGGESTBOX_ASYNC: case HIDDEN: formWidgetData.getInitialFieldValue().setValue(widgetValueInURL); break; case DATE: final Long millisWidgetValueInURL = Long.valueOf(widgetValueInURL); final Date dateFieldValue = new Date(millisWidgetValueInURL); formWidgetData.getInitialFieldValue().setValue(dateFieldValue); break; case DURATION: final Long longWidgetValueInURL = Long.valueOf(widgetValueInURL); formWidgetData.getInitialFieldValue().setValue(longWidgetValueInURL); break; case CHECKBOX: final Boolean booleanWidgetValueInURL = Boolean.valueOf(widgetValueInURL); formWidgetData.getInitialFieldValue().setValue(booleanWidgetValueInURL); break; case LISTBOX_MULTIPLE: case CHECKBOX_GROUP: case TABLE: final String[] arrayWidgetValueInURL = widgetValueInURL.split(","); final List<String> listWidgetValueInURL = Arrays.asList(arrayWidgetValueInURL); formWidgetData.getInitialFieldValue().setValue((Serializable) listWidgetValueInURL); break; default: // file upload widgets are not supported break; } } catch (final Exception e) { // Do nothing (the field will be set with it's original initialvalue) } } } /** * Set a button's label and title * * @param formWidgetData */ protected void setButtonLabel(final ReducedFormWidget formWidgetData) { if (formWidgetData.getLabel().equals("#previousPageButtonLabel")) { final String previousButtonLabel = FormsResourceBundle.getMessages().previousPageButtonLabel(); formWidgetData.setLabel(previousButtonLabel); final String previousButtonTitle = FormsResourceBundle.getMessages().previousPageButtonTitle(); formWidgetData.setTitle(previousButtonTitle); } else if (formWidgetData.getLabel().equals("#nextPageButtonLabel")) { final String nextButtonLabel = FormsResourceBundle.getMessages().nextPageButtonLabel(); formWidgetData.setLabel(nextButtonLabel); final String nextButtonTitle = FormsResourceBundle.getMessages().nextPageButtonTitle(); formWidgetData.setTitle(nextButtonTitle); } else if (formWidgetData.getLabel().equals("#submitButtonLabel")) { final String submitButtonLabel = FormsResourceBundle.getMessages().submitButtonLabel(); formWidgetData.setLabel(submitButtonLabel); final String submitButtonTitle = FormsResourceBundle.getMessages().submitButtonTitle(); formWidgetData.setTitle(submitButtonTitle); } } /** * Associate a button with the correct click handler * * @param formButtonWidget * @param isEditMode */ protected void addClickListener(final FormButtonWidget formButtonWidget, final boolean isEditMode) { final WidgetType buttonType = formButtonWidget.getWidgetType(); switch (buttonType) { case BUTTON_PREVIOUS: formButtonWidget.addClickHandler(new PreviousPageClickHandler(isEditMode)); break; case BUTTON_NEXT: formButtonWidget.addClickHandler(new NextPageClickHandler(isEditMode)); break; case BUTTON_SUBMIT: formButtonWidget.addClickHandler(submitClickHandler); break; default: break; } } /** * Handler for the next page button */ protected class NextPageClickHandler implements ClickHandler { protected boolean editMode; /** * @param editMode */ public NextPageClickHandler(final boolean editMode) { this.editMode = editMode; } /** * hide the current page and display the next page */ @Override public void onClick(final ClickEvent event) { final Object source = event.getSource(); if (!(source instanceof Label && disabledLabelButtons.contains(source))) { disableButtons((Widget) source); if (editMode) { validatePage(ACTION_TYPE.NEXT); } else { final ReducedFormPage formPage = formPages.get(followedPagesIds.get(currentPageIndex)); recordValues(formPage.getFormWidgets()); final int newIndex = currentPageIndex + 1; try { detachPageWidgets(); displayPage(newIndex); } catch (final IndexOutOfBoundsException e) { final String errorMessage = FormsResourceBundle.getErrors().pageIndexError(newIndex); formsServiceAsync.getApplicationErrorTemplate(errorMessage, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); enableButtons(true); } } } } } /** * Handler for the previous page button */ protected class PreviousPageClickHandler implements ClickHandler { protected boolean editMode; /** * @param editMode */ public PreviousPageClickHandler(final boolean editMode) { this.editMode = editMode; } /** * hide the current page and display the previous page */ @Override public void onClick(final ClickEvent event) { final Object source = event.getSource(); if (!(source instanceof Label && disabledLabelButtons.contains(source))) { disableButtons((Widget) source); final ReducedFormPage formPage = formPages.get(followedPagesIds.get(currentPageIndex)); recordValues(formPage.getFormWidgets()); final int newIndex = currentPageIndex - 1; try { detachPageWidgets(); displayPage(newIndex); } catch (final IndexOutOfBoundsException e) { final String errorMessage = FormsResourceBundle.getErrors().pageIndexError(newIndex); formsServiceAsync.getApplicationErrorTemplate(errorMessage, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); enableButtons(true); } } } } /** * Handler for the submission of the form */ protected class SubmitClickHandler implements ClickHandler { /** * submit the form */ @Override public void onClick(final ClickEvent event) { final Object source = event.getSource(); if (!(source instanceof Label && disabledLabelButtons.contains(source))) { disableButtons((Widget) source); validatePage(ACTION_TYPE.SUBMIT); } } } /** * Disable a button * * @param button * the button to disable */ protected void disableButton(final Widget button) { if (button instanceof Button) { ((Button) button).setEnabled(false); } else if (button instanceof Label) { disabledLabelButtons.add((Label) button); } } /** * disable the buttons * * @param pressedButton * the button that was pressed */ protected void disableButtons(final Widget pressedButton) { this.pressedButton = pressedButton; for (final Entry<String, FormButtonWidget> entry : buttonWidgets.entrySet()) { if (isActionButton(entry.getValue())) { disableButton(entry.getValue().getButton()); } } } protected boolean isActionButton(final FormButtonWidget formButtonWidget) { return formButtonWidget.getWidgetType().equals(WidgetType.BUTTON_SUBMIT) || formButtonWidget.getWidgetType().equals(WidgetType.BUTTON_NEXT) || formButtonWidget.getWidgetType().equals(WidgetType.BUTTON_PREVIOUS); } /** * Enable a button * * @param button * to enable */ protected void enableButton(final Widget button) { if (button != null) { if (button instanceof Button) { ((Button) button).setEnabled(true); } else if (button instanceof Label) { disabledLabelButtons.remove(button); } } } /** * Enable the buttons * * @param hideLoader * boolean to specify if we had need to hide the loader */ protected void enableButtons(final boolean hideLoader) { pressedButton = null; for (final Entry<String, FormButtonWidget> entry : buttonWidgets.entrySet()) { if (isActionButton(entry.getValue())) { enableButton(entry.getValue().getButton()); } } if (hideLoader) { domUtils.hideLoading(); focusFirstField(); } } /** * Records a page's fields * * @param formWidgets * the list of form widgets */ protected void recordValues(final List<ReducedFormWidget> formWidgets) { for (final ReducedFormWidget formWidget : formWidgets) { final String widgetId = formWidget.getId(); if (fieldWidgets.containsKey(widgetId)) { final FormFieldValue widgetValue = fieldWidgets.get(widgetId).getValue(); widgetValues.put(widgetId, widgetValue); } } } /** * Records a page's fields and validate it * * @param actionAfterValidation * type of action to execute after the validation step */ protected void validatePage(final ACTION_TYPE actionAfterValidation) { domUtils.displayLoading(); isCurrentPageValid = true; final ReducedFormPage formPage = formPages.get(followedPagesIds.get(currentPageIndex)); // deals with the mandatory fields validateMandatoryFieldWidgets(formPage.getFormWidgets()); // evaluates the number of widget validations to performs and store the // widgets to validate final List<ReducedFormWidget> formWidgetsToValidate = getFormWidgetsToValidate(formPage.getFormWidgets()); final List<ReducedFormValidator> pageValidators = formPage.getPageValidators(); final String submitButtonId = getSubmitButtonID(); //always clean page validators if (!pageValidators.isEmpty()) { cleanValidatorsMessages(pageValidators); } // fields validation if (!formWidgetsToValidate.isEmpty()) { final Map<String, String> validators = new HashMap<String, String>(); for (final ReducedFormWidget formWidget : formWidgetsToValidate) { cleanValidatorsMessages(formWidget.getValidators()); if (formWidget.isDisplayCondition()) { validators.put(formWidget.getId(), formWidget.getValidatorsCacheId()); } } formsServiceAsync.validateFormFields(formID, urlContext, validators, widgetValues, submitButtonId, new FormFieldValidatorHandler( actionAfterValidation, formPage.getPageValidators(), formPage.getPageValidatorsId())); } else if (isCurrentPageValid) { if (!pageValidators.isEmpty()) { formsServiceAsync.validateFormPage(formID, urlContext, formPage.getPageValidatorsId(), widgetValues, submitButtonId, new FormPageValidatorHandler(actionAfterValidation)); } else { submitForm(actionAfterValidation); } } else { enableButtons(true); } } /** * Validate the compliance of a list of widgets with their mandatory attributes * * @param formWidgets */ protected void validateMandatoryFieldWidgets(final List<ReducedFormWidget> formWidgets) { for (final ReducedFormWidget formWidget : formWidgets) { if (formWidget.isMandatory() && !formWidget.isReadOnly() && formWidget.isDisplayCondition()) { final FormFieldWidget mandatoryFieldWidget = fieldWidgets.get(formWidget.getId()); validateMandatoryField(mandatoryFieldWidget); } } } /** * Validate the compliance of a widget with its mandatory attribute * * @param mandatoryFieldWidget */ protected void validateMandatoryField(final FormFieldWidget mandatoryFieldWidget) { if (!mandatoryFieldWidget.isVisible()) { return; } if (mandatoryFieldSymbol == null || mandatoryFieldSymbol.equals("#defaultMandatoryFieldSymbol")) { mandatoryFieldSymbol = FormsResourceBundle.getMessages().defaultMandatoryFieldSymbol(); } mandatoryFieldWidget.setMandatoryLabel(mandatoryFieldSymbol); final FormFieldValue fieldValue = mandatoryFieldWidget.getValue(); if (isEmptyField(fieldValue)) { if (mandatoryFieldLabel.equals("#defaultMandatoryFieldLabel")) { mandatoryFieldLabel = FormsResourceBundle.getMessages().defaultMandatoryFieldLabel(); } mandatoryFieldWidget.setMandatoryLabel(mandatoryFieldLabel); isCurrentPageValid = false; } } private boolean isEmptyField(final FormFieldValue fieldValue) { return isEmptyString(fieldValue) || isEmptyFile(fieldValue) || isEmptyBoolean(fieldValue) || isEmptyCollection(fieldValue) || isEmptyLong(fieldValue) || isEmptyDate(fieldValue); } private boolean isEmptyDate(final FormFieldValue fieldValue) { return fieldValue.getValueType().equals(SupportedFieldTypes.JAVA_DATE_CLASSNAME) && fieldValue.getValue() == null; } private boolean isEmptyLong(final FormFieldValue fieldValue) { return fieldValue.getValueType().equals(SupportedFieldTypes.JAVA_LONG_CLASSNAME) && ((Long) fieldValue.getValue()).equals(Long.valueOf(0L)); } private boolean isEmptyCollection(final FormFieldValue fieldValue) { return fieldValue.getValueType().equals(SupportedFieldTypes.JAVA_COLLECTION_CLASSNAME) && (fieldValue.getValue() == null || ((Collection<?>) fieldValue.getValue()).isEmpty()); } private boolean isEmptyBoolean(final FormFieldValue fieldValue) { return fieldValue.getValueType().equals(SupportedFieldTypes.JAVA_BOOLEAN_CLASSNAME) && ((Boolean) fieldValue.getValue()).equals(Boolean.FALSE); } private boolean isEmptyString(final FormFieldValue fieldValue) { return fieldValue.getValueType().equals(SupportedFieldTypes.JAVA_STRING_CLASSNAME) && (fieldValue.getValue() == null || ((String) fieldValue.getValue()).length() == 0); } private boolean isEmptyFile(final FormFieldValue fieldValue) { return fieldValue.getValueType().equals(SupportedFieldTypes.JAVA_FILE_CLASSNAME) && (fieldValue.getDisplayedValue() == null || fieldValue.getDisplayedValue().length() == 0); } /** * Get the form field widgets to validate * * @param formWidgets * @return the {@link List} of {@link FormWidget} to validate */ protected List<ReducedFormWidget> getFormWidgetsToValidate(final List<ReducedFormWidget> formWidgets) { final List<ReducedFormWidget> formWidgetsToValidate = new ArrayList<ReducedFormWidget>(); for (final ReducedFormWidget formWidget : formWidgets) { final String widgetId = formWidget.getId(); if (fieldWidgets.containsKey(widgetId)) { final FormFieldValue widgetValue = fieldWidgets.get(widgetId).getValue(); widgetValues.put(widgetId, widgetValue); if (!formWidget.getValidators().isEmpty() && !formWidget.isReadOnly()) { formWidgetsToValidate.add(formWidget); } } } return formWidgetsToValidate; } /** * Remove the validation messages of the given validators from the page * * @param validators */ protected void cleanValidatorsMessages(final List<ReducedFormValidator> validators) { for (final ReducedFormValidator formValidator : validators) { final Element validatorEle = DOM.getElementById(formValidator.getId()); if (validatorEle != null) { DOM.setInnerHTML(validatorEle, ""); } } } protected void submitForm(final ACTION_TYPE actionAfterValidation) { if (actionAfterValidation.equals(ACTION_TYPE.SUBMIT)) { final String submitButtonId = getSubmitButtonID(); formsServiceAsync.executeActions(formID, urlContext, widgetValues, followedPagesIds, submitButtonId, new FormSubmissionHandler()); } else { int newIndex = currentPageIndex; if (actionAfterValidation.equals(ACTION_TYPE.PREVIOUS)) { newIndex--; } else if (actionAfterValidation.equals(ACTION_TYPE.NEXT)) { newIndex++; } try { detachPageWidgets(); displayPage(newIndex); } catch (final IndexOutOfBoundsException e) { final String errorMessage = FormsResourceBundle.getErrors().pageIndexError(newIndex); if (formID != null) { formsServiceAsync.getApplicationErrorTemplate(errorMessage, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); } else { formsServiceAsync.getApplicationErrorTemplate(formID, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); } } } } protected String getSubmitButtonID() { return pressedButton.getElement().getParentElement().getParentElement().getId(); } /** * Handler allowing to validate a form field */ protected class FormFieldValidatorHandler extends FormsAsyncCallback<Map<String, List<ReducedFormValidator>>> { protected ACTION_TYPE actionAfterValidation; protected List<ReducedFormValidator> pageValidators; protected String pageValidatorsId; public FormFieldValidatorHandler(final ACTION_TYPE actionAfterValidation, final List<ReducedFormValidator> pageValidators, final String pageValidatorsId) { this.pageValidatorsId = pageValidatorsId; this.actionAfterValidation = actionAfterValidation; this.pageValidators = pageValidators; } /** * {@inheritDoc} */ @Override public void onSuccess(final Map<String, List<ReducedFormValidator>> fieldValidators) { if (!fieldValidators.isEmpty()) { for (final Entry<String, List<ReducedFormValidator>> fieldValidatorsEntry : fieldValidators.entrySet()) { // set the focus on the first invalid form field final FormFieldWidget fieldWidget = fieldWidgets.get(fieldValidatorsEntry.getKey()); if (isCurrentPageValid) { if (fieldWidget != null) { fieldWidget.setFocusOn(); } isCurrentPageValid = false; } for (final ReducedFormValidator fieldValidator : fieldValidatorsEntry.getValue()) { final FormValidationMessageWidget formValidationMessageWidget = new FormValidationMessageWidget(fieldValidator, false); final String validatorId = fieldValidator.getId(); final Element validatorElement = DOM.getElementById(validatorId); if (validatorElement != null) { DOM.appendChild(validatorElement, formValidationMessageWidget.getElement()); } else { GWT.log("An element with id " + validatorId + " is missing from the page template."); } } } } if (isCurrentPageValid) { if (!pageValidators.isEmpty()) { // once the fields validation is over perform the page validation cleanValidatorsMessages(pageValidators); final String submitButtonId = pressedButton.getElement().getParentElement().getParentElement().getId(); formsServiceAsync.validateFormPage(formID, urlContext, pageValidatorsId, widgetValues, submitButtonId, new FormPageValidatorHandler( actionAfterValidation)); } else { submitForm(actionAfterValidation); } } else { enableButtons(true); } } @Override public void onUnhandledFailure(final Throwable caught) { final String errorMessage = FormsResourceBundle.getErrors().fieldValidationError(); if (formID != null) { formsServiceAsync.getApplicationErrorTemplate(errorMessage, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); } else { formsServiceAsync.getApplicationErrorTemplate(formID, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); } enableButtons(true); } } /** * Handler allowing to validate a form page */ protected class FormPageValidatorHandler extends FormsAsyncCallback<List<ReducedFormValidator>> { protected ACTION_TYPE actionAfterValidation; public FormPageValidatorHandler(final ACTION_TYPE actionAfterValidation) { this.actionAfterValidation = actionAfterValidation; } /** * {@inheritDoc} */ @Override public void onSuccess(final List<ReducedFormValidator> pageValidators) { if (!pageValidators.isEmpty()) { isCurrentPageValid = false; for (final ReducedFormValidator pageValidator : pageValidators) { final FormValidationMessageWidget formValidationMessageWidget = new FormValidationMessageWidget(pageValidator, true); final String validatorId = pageValidator.getId(); if (RootPanel.get(validatorId) != null) { RootPanel.get(validatorId).add(formValidationMessageWidget); } else { GWT.log("An element with id " + validatorId + " is missing from the page template."); } } enableButtons(true); } else { submitForm(actionAfterValidation); } } @Override public void onUnhandledFailure(final Throwable caught) { final String errorMessage = FormsResourceBundle.getErrors().pageValidationError(); if (formID != null) { formsServiceAsync.getApplicationErrorTemplate(errorMessage, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); } else { formsServiceAsync.getApplicationErrorTemplate(formID, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); } enableButtons(true); } } /** * Detach the data fields of the page */ protected void detachPageWidgets() { final ReducedFormPage formPage = formPages.get(followedPagesIds.get(currentPageIndex)); for (final ReducedFormWidget formWidget : formPage.getFormWidgets()) { final String widgetId = formWidget.getId(); Widget widget; if (formWidget.getType().name().startsWith("BUTTON")) { widget = buttonWidgets.get(widgetId); } else if (formWidget.getType().equals(WidgetType.MESSAGE)) { widget = messageWidgets.get(widgetId); } else if (formWidget.getType().equals(WidgetType.IFRAME)) { widget = frameWidgets.get(widgetId); } else { widget = fieldWidgets.get(widgetId); } if (widget != null) { widget.removeFromParent(); } } } /** * Handler to deal with what happens after a form submission */ protected class FormSubmissionHandler extends FormsAsyncCallback<Map<String, Object>> { private final String actionForNotif; public FormSubmissionHandler() { super(); actionForNotif = domUtils.getActionForNotif(urlContext); } /** * {@inheritDoc} */ @Override public void onSuccess(final Map<String, Object> newContext) { if (domUtils.isPageInFrame()) { domUtils.notifyParentFrameSuccess(actionForNotif); } if (!"false".equals(urlUtils.getHashParameter(URLUtils.DISPLAY_CONFIRMATION))) { urlContext.clear(); urlContext.putAll(newContext); redirectToConfirmationPage(); } } @Override public void onUnhandledFailure(final Throwable caught) { try { throw caught; } catch (final FormAlreadySubmittedException e) { final String errorMessage = FormsResourceBundle.getErrors().formAlreadySubmittedOrCancelledError(); if (domUtils.isPageInFrame()) { domUtils.notifyParentFrameError("formAlreadySubmittedOrCancelledError", actionForNotif); } if (!"false".equals(urlUtils.getHashParameter(URLUtils.DISPLAY_CONFIRMATION))) { formsServiceAsync.getApplicationErrorTemplate(formID, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); } } catch (final FileTooBigException e) { final String fileName = e.getFileName(); final String maxSize = e.getMaxSize(); String errorMessage; if (fileName != null) { errorMessage = FormsResourceBundle.getErrors().fileTooBigErrorWithNameSize(fileName, maxSize); } else { errorMessage = FormsResourceBundle.getErrors().fileTooBigErrorWithSize(maxSize); } if (domUtils.isPageInFrame()) { domUtils.notifyParentFrameError("fileTooBigError", actionForNotif); } if (!"false".equals(urlUtils.getHashParameter(URLUtils.DISPLAY_CONFIRMATION))) { Window.alert(errorMessage); } } catch (final Throwable t) { final String errorMessage = FormsResourceBundle.getErrors().formSubmissionError(); if (domUtils.isPageInFrame()) { domUtils.notifyParentFrameError("formSubmissionError", actionForNotif); } if (!"false".equals(urlUtils.getHashParameter(URLUtils.DISPLAY_CONFIRMATION))) { formsServiceAsync.getApplicationErrorTemplate(formID, urlContext, new ErrorPageHandler(applicationHTMLPanel, formID, pageHTMLPanel, errorMessage, elementId)); } } enableButtons(true); } } /** * If the page is contained in a form, resize the frame to fit the page height */ protected void resizeFrame() { final Timer timer = new Timer() { @Override public void run() { if (domUtils.isPageInFrame()) { } } }; timer.schedule(300); } public void setMandatoryFieldSymbol(final String mandatoryFieldSymbol) { if (mandatoryFieldSymbol != null && mandatoryFieldSymbol.length() > 0) { this.mandatoryFieldSymbol = mandatoryFieldSymbol; } } public void setMandatoryFieldClasses(final String mandatoryFieldClasses) { this.mandatoryFieldClasses = mandatoryFieldClasses; } public void setMandatoryFieldLabel(final String mandatoryFieldLabel) { this.mandatoryFieldLabel = mandatoryFieldLabel; } private void redirectToConfirmationPage() { String defaultConfirmationMessage = null; if(urlContext.containsKey(URLUtils.INSTANCE_ID_PARAM)) { defaultConfirmationMessage = FormsResourceBundle.getMessages().instanceSubmissionConfirmationMessage((String)urlContext.get(URLUtils.INSTANCE_ID_PARAM)); } else { defaultConfirmationMessage = FormsResourceBundle.getMessages().submissionConfirmationMessage(); } formsServiceAsync.getFormConfirmationTemplate(formID, urlContext, widgetValues, createConfirmationPageHandler(defaultConfirmationMessage)); } private ConfirmationPageHandler createConfirmationPageHandler(final String defaultConfirmationMessage) { return new ConfirmationPageHandler(applicationHTMLPanel, elementId, defaultConfirmationMessage, formID, urlContext) .setCurrentPageHTMLPanel(pageHTMLPanel); } }