/* * Copyright 2011 JBoss Inc * * 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.jbpm.formapi.client.form; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jbpm.formapi.client.CommonGlobals; import org.jbpm.formapi.client.FormBuilderException; import org.jbpm.formapi.client.bus.FormItemSelectionEvent; import org.jbpm.formapi.client.effect.FBFormEffect; import org.jbpm.formapi.client.menu.EffectsPopupPanel; import org.jbpm.formapi.client.validation.FBValidationItem; import org.jbpm.formapi.common.handler.ControlKeyHandler; import org.jbpm.formapi.common.handler.EventHelper; import org.jbpm.formapi.common.handler.RightClickEvent; import org.jbpm.formapi.common.handler.RightClickHandler; import org.jbpm.formapi.common.reflect.ReflectionHelper; import org.jbpm.formapi.shared.api.ExternalData; import org.jbpm.formapi.shared.api.FBScript; import org.jbpm.formapi.shared.api.FBValidation; import org.jbpm.formapi.shared.api.FormItemRepresentation; import org.jbpm.formapi.shared.api.InputData; import org.jbpm.formapi.shared.api.OutputData; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Widget; /** * Base class for UI components. Contains most of the edition definitions: right * click functionality, inplace editor invocation, desired positioning, width, * height, validations, input association and output association. */ public abstract class FBFormItem extends FocusPanel { private List<FBValidationItem> validations = new ArrayList<FBValidationItem>(); private Map<String, FBScript> eventActions = new HashMap<String, FBScript>(); private List<FBFormEffect> effects = new ArrayList<FBFormEffect>(); private int desiredX; private int desiredY; private String widgetWidth; private String widgetHeight; private boolean alreadyEditing = false; private Widget auxiliarWidget = null; private InputData input = null; private OutputData output = null; private ExternalData external = null; public FBFormItem(List<FBFormEffect> formEffects) { this.effects.addAll(formEffects); addStyleName("fbFormItemThinBorder"); EventHelper.addRightClickHandler(this, new RightClickHandler() { @Override public void onRightClick(RightClickEvent event) { EffectsPopupPanel popupPanel = new EffectsPopupPanel( FBFormItem.this, true); if (getFormEffects() != null && !getFormEffects().isEmpty()) { popupPanel.setPopupPosition(event.getX(), event.getY()); popupPanel.show(); } } }); EventHelper.addKeyboardCopyHandler(this, new ControlKeyHandler() { @Override public void onKeyboardControl() { CommonGlobals.getInstance().copy().append(FBFormItem.this) .execute(); } }); EventHelper.addKeyboardCutHandler(this, new ControlKeyHandler() { @Override public void onKeyboardControl() { CommonGlobals.getInstance().cut().append(FBFormItem.this) .execute(); } }); EventHelper.addKeyboardPasteHandler(this, new ControlKeyHandler() { @Override public void onKeyboardControl() { CommonGlobals.getInstance().paste().append(FBFormItem.this) .execute(); } }); EventHelper.addBlurHandler(this, new BlurHandler() { @Override public void onBlur(BlurEvent event) { reset(); } }); EventHelper.addFocusHandler(this, new FocusHandler() { @Override public void onFocus(FocusEvent event) { makeEditor(); } }); } private void makeEditor() { if (!getFormItemPropertiesMap().isEmpty() && !isAlreadyEditing()) { fireSelectionEvent(new FormItemSelectionEvent(this, true)); } FBInplaceEditor inplaceEditor = createInplaceEditor(); if (inplaceEditor != null && !isAlreadyEditing()) { auxiliarWidget = getWidget(); clear(); setWidget(inplaceEditor); setAlreadyEditing(true); inplaceEditor.focus(); } } public boolean isAlreadyEditing() { return alreadyEditing; } public void setAlreadyEditing(boolean alreadyEditing) { this.alreadyEditing = alreadyEditing; } public void reset() { if (auxiliarWidget != null && !getEditor().isFocused()) { clear(); add(auxiliarWidget); setAlreadyEditing(false); fireSelectionEvent(new FormItemSelectionEvent(this, false)); } } private FBInplaceEditor getEditor() { return (FBInplaceEditor) getWidget(); } public final void fireSelectionEvent(FormItemSelectionEvent event) { EventBus bus = CommonGlobals.getInstance().getEventBus(); bus.fireEvent(event); } @Override public void onBrowserEvent(Event event) { EventHelper.onBrowserEvent(this, event); } public void addEffect(FBFormEffect effect) { if (!effects.contains(effect)) { effects.add(effect); } } public void removeEffect(FBFormEffect effect) { if (effects.contains(effect)) { effects.remove(effect); } } protected Integer extractInt(Object obj) { String s = extractString(obj); return s.equals("") ? null : Integer.valueOf(s); } protected Boolean extractBoolean(Object obj) { if (obj != null && obj instanceof Boolean) { return (Boolean) obj; } String s = extractString(obj); return s.equals("") ? Boolean.FALSE : Boolean.valueOf(s); } protected String extractString(Object obj) { return obj == null ? "" : obj.toString(); } protected Double extractDouble(Object obj) { String s = extractString(obj); return s.equals("") ? null : Double.valueOf(s); } public List<FBFormEffect> getFormEffects() { return this.effects; } public int getDesiredX() { return desiredX; } public void setDesiredX(int desiredX) { this.desiredX = desiredX; } public int getDesiredY() { return desiredY; } public void setDesiredY(int desiredY) { this.desiredY = desiredY; } public void setDesiredPosition(int desiredX, int desiredY) { this.desiredX = desiredX; this.desiredY = desiredY; } public String getHeight() { return widgetHeight; } public String getWidth() { return widgetWidth; } @Override public void setWidth(String width) { if (width != null) { super.setWidth(width); this.widgetWidth = width; } } @Override public void setHeight(String height) { if (height != null) { super.setHeight(height); this.widgetHeight = height; } } public void setInput(InputData input) { this.input = input; } public void setOutput(OutputData output) { this.output = output; } public void setExternal(ExternalData external) { this.external = external; } public OutputData getOutput() { return output; } public InputData getInput() { return input; } public ExternalData getExternal() { return external; } protected void setWidgetHeight(String widgetHeight) { this.widgetHeight = widgetHeight; } protected void setWidgetWidth(String widgetWidth) { this.widgetWidth = widgetWidth; } protected void setEffects(List<FBFormEffect> effects) { this.effects = effects; } protected <T extends FBFormItem> T cloneItem(T clone) { clone.setValidations(this.validations); clone.setWidgetHeight(this.widgetHeight); clone.setWidgetWidth(this.widgetWidth); clone.setEffects(this.effects); clone.setInput(this.input); clone.setOutput(this.output); return clone; } protected <T extends FormItemRepresentation> T getRepresentation(T rep) { rep.setInput(getInput()); rep.setOutput(getOutput()); rep.setHeight(getHeight()); rep.setWidth(getWidth()); List<FBValidation> repValidations = new ArrayList<FBValidation>(); for (FBValidationItem item : getValidations()) { repValidations.add(item.createValidation()); } rep.setItemValidations(repValidations); for (FBFormEffect effect : getFormEffects()) { rep.addEffectClass(effect.getClass()); } rep.setEventActions(getEventActions()); rep.setExternal(getExternal()); return rep; } public static FBFormItem createItem(FormItemRepresentation rep) throws FormBuilderException { if (rep == null) { return null; } String className = rep.getItemClassName(); try { FBFormItem item = (FBFormItem) ReflectionHelper .newInstance(className); item.populate(rep); return item; } catch (Exception e) { throw new FormBuilderException("Couldn't instantiate class " + className, e); } } public void setValidations(List<FBValidationItem> validations) { if (validations == null) { validations = new ArrayList<FBValidationItem>(); } this.validations = validations; } public List<FBValidationItem> getValidations() { return validations; } public void setEventActions(Map<String, FBScript> eventActions) { if (eventActions == null) { eventActions = new HashMap<String, FBScript>(); } this.eventActions = eventActions; } public Map<String, FBScript> getEventActions() { return eventActions; } /** * If you wish that on clicking your UI component, it becomes replaced by a * custom editor, this is where you must create it * * @return A custom subclass of {@link FBInplaceEditor} to replace component * and be rechanged after lost of focus. Default returns null */ public FBInplaceEditor createInplaceEditor() { return null; } /** * This method must be defined to tell outside default editors what * properties this UI component has. Outside editors will then provide * functionality to edit these properties and invoke * {@link #saveValues(Map)} * * @return a map of the properties of this UI component */ public abstract Map<String, Object> getFormItemPropertiesMap(); /** * This method must be defined so that outside default editor can tell this * UI component the new value of its properties. It's the entire * responsibility of this UI component to repopulate itself from these * properties * * @param asPropertiesMap * a map of the proeprties to set on this UI component */ public abstract void saveValues(Map<String, Object> asPropertiesMap); /** * This method is used to create a POJO representation of the UI component * that any java service can understand. * * @return a POJO representation of this UI component */ public abstract FormItemRepresentation getRepresentation(); /** * This method must be overriden by each {@link FBFormItem} subclass to * repopulate its properties from an outside POJO representation. * * @param rep * the POJO representation of this UI component. It's the * responsibility of each {@link FBFormItem} instance to validate * the POJO representation for itself, call the superclass * method, and define what and how properties of its UI component * should be updated. * @throws FormBuilderException * in case of error or invalid content */ public void populate(FormItemRepresentation rep) throws FormBuilderException { if (rep.getEffectClasses() != null) { this.effects = new ArrayList<FBFormEffect>(rep.getEffectClasses() .size()); for (String className : rep.getEffectClasses()) { try { FBFormEffect effect = (FBFormEffect) ReflectionHelper .newInstance(className); this.effects.add(effect); } catch (Exception e) { throw new FormBuilderException( "Couldn't instantiate class " + className, e); } } } if (rep.getEventActions() != null) { for (String key : eventActions.keySet()) { eventActions.put(key, null); } this.eventActions.putAll(rep.getEventActions()); } this.validations.clear(); if (rep.getItemValidations() != null) { for (FBValidation validation : rep.getItemValidations()) { FBValidationItem validationItem = FBValidationItem .createValidation(validation); this.validations.add(validationItem); } } setHeight(rep.getHeight()); setWidth(rep.getWidth()); this.input = rep.getInput(); this.output = rep.getOutput(); this.external = rep.getExternal(); this.eventActions = rep.getEventActions(); } /** * This methods is similar to {@link #clone()}, but returns a proper type * and forces implementation * * @return a clone of this very object */ public abstract FBFormItem cloneItem(); /** * Similar to {@link #cloneItem()}, but only clones the underlying UI GWT * component. * * @return */ public abstract Widget cloneDisplay(Map<String, Object> formData); protected void populateActions(Element element) { for (Map.Entry<String, FBScript> entry : getEventActions().entrySet()) { element.setPropertyJSO(entry.getKey(), toJsFunction(entry .getValue().getContent())); } } protected Object getInputValue(Map<String, Object> data) { if (getInput() != null && getInput().getName() != null) { if (data != null && data.containsKey(getInput().getName())) { return data.get(getInput().getName()); } } return null; } private native JavaScriptObject toJsFunction(String value) /*-{ var r = function() { eval(value); } return r; }-*/; public boolean removeEffectOfType(Class<? extends FBFormEffect> effectClass) { FBFormEffect effectToRemove = null; if (getFormEffects() != null) { for (FBFormEffect effect : getFormEffects()) { if (effect.getClass().getName().equals(effectClass.getName())) { effectToRemove = effect; break; } } } if (effectToRemove != null) { return effects.remove(effectToRemove); } return false; } public boolean hasEffectOfType(Class<? extends FBFormEffect> effectClass) { if (getFormEffects() != null) { for (FBFormEffect effect : getFormEffects()) { if (effect.getClass().getName().equals(effectClass.getName())) { return true; } } } return false; } }