/*
* Copyright (c) 2009-2011 Lockheed Martin Corporation
*
* 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.eurekastreams.web.client.ui.common.form;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.eurekastreams.web.client.events.EventBus;
import org.eurekastreams.web.client.events.ExceptionResponseEvent;
import org.eurekastreams.web.client.events.Observer;
import org.eurekastreams.web.client.events.PreSwitchedHistoryViewEvent;
import org.eurekastreams.web.client.events.PreventHistoryChangeEvent;
import org.eurekastreams.web.client.events.SubmitFormIfChangedEvent;
import org.eurekastreams.web.client.events.data.ValidationExceptionResponseEvent;
import org.eurekastreams.web.client.jsni.WidgetJSNIFacadeImpl;
import org.eurekastreams.web.client.model.BaseModel;
import org.eurekastreams.web.client.model.Insertable;
import org.eurekastreams.web.client.model.Updateable;
import org.eurekastreams.web.client.ui.Session;
import org.eurekastreams.web.client.ui.common.form.elements.FormElement;
import org.eurekastreams.web.client.ui.pages.master.StaticResourceBundle;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
/**
* The Form Builder.
*
*/
public class FormBuilder extends FlowPanel
{
/**
* Method to call on the BaseModel. (Forms never fetch or delete).
*/
public enum Method
{
/**
* Call the Insert method.
*/
INSERT,
/**
* Call the Update method.
*/
UPDATE
}
/**
* The command to exe on cancel.
*/
private Command onCancelCommand;
/**
* The base model.
*/
private final BaseModel baseModel;
/**
* The method.
*/
private final Method method;
/**
* The title of the form.
*/
private final Label formTitle = new Label();
/**
* Contains the form.
*/
private final FlowPanel formContainer = new FlowPanel();
/**
* Contains the inner elements of the form.
*/
private final FlowPanel formElementsContainer = new FlowPanel();
/**
* The error box for evil.
*/
private final FlowPanel errorBox = new FlowPanel();
/**
* The fade panel to disable controls while submitting.
*/
private final FlowPanel fadePanel = new FlowPanel();
/**
* The submit button of the form.
*/
private final Anchor submitButton = new Anchor("");
/**
* The cancel button of the form.
*/
private final Hyperlink cancelButton = new Hyperlink("Cancel", History.getToken());
/**
* The processing spinner.
*/
private final Label processingSpinny = new Label("Processing...");
/**
* The data of the form.
*/
private final List<FormElement> data = new LinkedList<FormElement>();
/**
* The original values of the form, to see if anything has changed.
*/
private final HashMap<String, Serializable> originalValues = new HashMap<String, Serializable>();
/**
* Did the user add a "last form element".
*/
private boolean addedLastFormElement = false;
/**
* Is the form inactive.
*/
private boolean inactive = false;
/**
* If should scroll to top of window on ValidationExceptionResponseEvent.
*/
private boolean scrollToTopOnValidationError = true;
/** If events are currently wired to the event bus. */
private boolean eventsWired;
/** Event handler. */
private Observer<ValidationExceptionResponseEvent> validationExceptionResponseHandler;
/** Event handler. */
private Observer<ExceptionResponseEvent> exceptionResponseHandler;
/** Event handler. */
private Observer<PreSwitchedHistoryViewEvent> preSwitchedHistoryViewHandler;
/** Event handler. */
private Observer<SubmitFormIfChangedEvent> submitFormIfChangedHandler;
/**
* Constructor.
*
* @param title
* the form title.
* @param inBaseModel
* the base model to use to persist data.
* @param inMethod
* the method to call on the base model.
*/
public FormBuilder(final String title, final BaseModel inBaseModel, final Method inMethod)
{
this(title, inBaseModel, inMethod, true);
}
/**
* Constructor.
*
* @param title
* the form title.
* @param inBaseModel
* the base model to use to persist data.
* @param inMethod
* the method to call on the base model.
* @param inScrollToTopOnValidationError
* scroll to top on ValidationExceptionResponseEvent.
*/
public FormBuilder(final String title, final BaseModel inBaseModel, final Method inMethod,
final boolean inScrollToTopOnValidationError)
{
scrollToTopOnValidationError = inScrollToTopOnValidationError;
baseModel = inBaseModel;
method = inMethod;
submitButton.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formSubmitButton());
cancelButton.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formCancelButton());
errorBox.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formErrorBox());
formContainer.add(errorBox);
formContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formContainer());
formContainer.add(formElementsContainer);
fadePanel.setVisible(false);
fadePanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formDisable());
formContainer.add(fadePanel);
formContainer.add(submitButton);
processingSpinny.setVisible(false);
processingSpinny.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formSubmitSpinny());
processingSpinny.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formProcessingSpinny());
formContainer.add(processingSpinny);
formContainer.add(cancelButton);
if (!title.equals(""))
{
formTitle.setText(title);
formTitle.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formTitle());
this.add(formTitle);
}
this.add(formContainer);
errorBox.setVisible(false);
submitButton.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
submit();
}
});
cancelButton.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent arg0)
{
inactive = true;
if (onCancelCommand != null)
{
onCancelCommand.execute();
}
}
});
createEventHandlers();
// wireEventHandlers();
}
/**
* {@inheritDoc}
*/
@Override
protected void onAttach()
{
super.onAttach();
wireEventHandlers();
}
/**
* {@inheritDoc}
*/
@Override
protected void onDetach()
{
super.onDetach();
unwireEventHandlers();
}
/**
* Creates the event handlers used with the event bus.
*/
private void createEventHandlers()
{
validationExceptionResponseHandler = new Observer<ValidationExceptionResponseEvent>()
{
public void update(final ValidationExceptionResponseEvent event)
{
List<String> errors = new ArrayList<String>();
for (FormElement element : data)
{
String error = event.getResponse().getErrors().get(element.getKey());
if (error != null)
{
errors.add(error);
}
}
if (!errors.isEmpty())
{
errorBox.clear();
resetSubmitButton();
String errorBoxText = new String("Please correct the following errors:<ul>");
for (FormElement element : data)
{
String error = event.getResponse().getErrors().get(element.getKey());
if (error != null)
{
errorBox.setVisible(true);
errorBoxText += "<li>" + error + "</li>";
element.onError(error);
}
else
{
element.onSuccess();
}
}
errorBoxText += "</ul>";
errorBox.add(new HTML(errorBoxText));
if (scrollToTopOnValidationError)
{
Window.scrollTo(0, 0);
}
}
}
};
exceptionResponseHandler = new Observer<ExceptionResponseEvent>()
{
public void update(final ExceptionResponseEvent event)
{
if (event.getModel() == baseModel)
{
resetSubmitButton();
}
}
};
preSwitchedHistoryViewHandler = new Observer<PreSwitchedHistoryViewEvent>()
{
public void update(final PreSwitchedHistoryViewEvent arg1)
{
if (hasFormChanged())
{
if (new WidgetJSNIFacadeImpl().confirm("The form has been changed. Do you wish to save changes?"))
{
EventBus eventBus = Session.getInstance().getEventBus();
eventBus.notifyObservers(new PreventHistoryChangeEvent());
eventBus.notifyObservers(new SubmitFormIfChangedEvent());
}
}
}
};
submitFormIfChangedHandler = new Observer<SubmitFormIfChangedEvent>()
{
public void update(final SubmitFormIfChangedEvent arg1)
{
if (hasFormChanged())
{
submit();
}
}
};
}
/**
* Attaches the event handlers to the event bus.
*/
public void wireEventHandlers()
{
if (!eventsWired)
{
final EventBus eventBus = Session.getInstance().getEventBus();
eventBus.addObserver(ValidationExceptionResponseEvent.class, validationExceptionResponseHandler);
eventBus.addObserver(ExceptionResponseEvent.class, exceptionResponseHandler);
eventBus.addObserver(PreSwitchedHistoryViewEvent.class, preSwitchedHistoryViewHandler);
eventBus.addObserver(SubmitFormIfChangedEvent.class, submitFormIfChangedHandler);
eventsWired = true;
}
}
/**
* Removes the event handlers from the event bus.
*/
public void unwireEventHandlers()
{
if (eventsWired)
{
final EventBus eventBus = Session.getInstance().getEventBus();
eventBus.removeObserver(ValidationExceptionResponseEvent.class, validationExceptionResponseHandler);
eventBus.removeObserver(ExceptionResponseEvent.class, exceptionResponseHandler);
eventBus.removeObserver(PreSwitchedHistoryViewEvent.class, preSwitchedHistoryViewHandler);
eventBus.removeObserver(SubmitFormIfChangedEvent.class, submitFormIfChangedHandler);
eventsWired = false;
}
}
/**
* Cancel the form manually.
*/
public void turnOffChangeCheck()
{
inactive = true;
}
/**
* Has the form changed?
*
* @return has the form changed?
*/
private boolean hasFormChanged()
{
boolean changed = false;
for (FormElement element : data)
{
if (originalValues.containsKey(element.getKey())
&& (originalValues.get(element.getKey()) != null && !originalValues.get(element.getKey()).equals(
element.getValue()))
|| (element.getValue() != null && !element.getValue().equals(originalValues.get(element.getKey()))))
{
changed = true;
}
}
return changed && !inactive;
}
/**
* Submit the form.
*/
private void submit()
{
processingSpinny.setVisible(true);
fadePanel.setVisible(true);
submitButton.setVisible(false);
HashMap<String, Serializable> dataValues = new HashMap<String, Serializable>();
for (FormElement element : data)
{
dataValues.put(element.getKey(), element.getValue());
originalValues.put(element.getKey(), element.getValue());
}
if (method.equals(Method.INSERT))
{
((Insertable) baseModel).insert(dataValues);
}
else
{
((Updateable) baseModel).update(dataValues);
}
}
/**
* Happens on success of the form.
*/
public void onSuccess()
{
resetSubmitButton();
errorBox.clear();
errorBox.setVisible(false);
for (FormElement element : data)
{
element.onSuccess();
}
}
/**
* Sets the CSS class on the Submit button. Used if you're changing it to say, an update button.
*
* @param cssClass
* the css class.
*/
public void setSubmitButtonClass(final String cssClass)
{
submitButton.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().formSubmitButton());
submitButton.addStyleName(cssClass);
}
/**
* Used to inject widgets in the form container itself. Useful if you need to add something after the submit and
* cancel.
*
* @param widget
* the widget.
*/
public void addWidgetToFormContainer(final Widget widget)
{
formContainer.add(widget);
}
/**
* Sets the token for when the user clicks cancel.
*
* @param token
* the token.
*/
public void setOnCancelHistoryToken(final String token)
{
cancelButton.setTargetHistoryToken(token);
}
/**
* Gives a command to execute on cancel.
*
* @param inOnCancelCommand
* the command.
*/
public void addOnCancelCommand(final Command inOnCancelCommand)
{
onCancelCommand = inOnCancelCommand;
}
/**
* Adds a form element to the form.
*
* @param element
* the form element.
*/
public void addFormElement(final FormElement element)
{
if (element instanceof Widget)
{
((Widget) element).addStyleName(StaticResourceBundle.INSTANCE.coreCss().formElement());
if (addedLastFormElement)
{
formElementsContainer.insert((Widget) element, formElementsContainer.getWidgetCount() - 1);
}
else
{
formElementsContainer.add((Widget) element);
}
}
data.add(element);
originalValues.put(element.getKey(), element.getValue());
}
/**
* Adds a "last form element". This is a form element that will ALWAYS stay at the bottom of the form Regardless of
* others added.
*
* @param element
* the element.
*/
public void addLastFormElement(final FormElement element)
{
if (element instanceof Widget)
{
((Widget) element).addStyleName(StaticResourceBundle.INSTANCE.coreCss().formElement());
formElementsContainer.insert((Widget) element, formElementsContainer.getWidgetCount());
addedLastFormElement = true;
}
data.add(element);
originalValues.put(element.getKey(), element.getValue());
}
/**
* Gets the form value from the key.
*
* @param key
* the key.
* @return the value.
*/
public Serializable getFormValue(final String key)
{
for (FormElement formElement : data)
{
if (formElement.getKey().equals(key))
{
return formElement.getValue();
}
}
return null;
}
/**
* Adds a form divider.
*/
public void addFormDivider()
{
Label divider = new Label();
divider.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formDivider());
formElementsContainer.add(divider);
}
/**
* Reset the submit button.
*/
private void resetSubmitButton()
{
processingSpinny.setVisible(false);
fadePanel.setVisible(false);
submitButton.setVisible(true);
}
/**
* Adds a clearing divider to the form.
*/
public void addClear()
{
FlowPanel clear = new FlowPanel();
clear.addStyleName(StaticResourceBundle.INSTANCE.coreCss().clear());
formElementsContainer.add(clear);
}
/**
* Adds a form label to the form.
*
* @param header
* the label.
* @return the label.
*/
public Label addFormLabel(final String header)
{
Label subHeader = new Label(header);
subHeader.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formLabel());
subHeader.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formStandaloneLabel());
formElementsContainer.add(subHeader);
return subHeader;
}
/**
* Adds a widget to the form panel.
*
* @param w
* the widget to add.
*/
public void addWidget(final Widget w)
{
formElementsContainer.add(w);
}
}