/* * Ext GWT - Ext for GWT * Copyright(c) 2007-2009, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ package com.extjs.gxt.ui.client.widget; import com.extjs.gxt.ui.client.GXT; import com.extjs.gxt.ui.client.core.El; import com.extjs.gxt.ui.client.event.EditorEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.FieldEvent; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.widget.form.ComboBox; import com.extjs.gxt.ui.client.widget.form.Field; import com.extjs.gxt.ui.client.widget.form.TriggerField; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.RootPanel; /** * A base editor field that handles displaying/hiding on demand and has some * built-in sizing and event handling logic. * * <dl> * <dt>Events:</dt> * * <dd><b>BeforeCancelEdit</b> : EditorEvent(editor, value, startValue)<br> * <div>Fires before editing is canceled</div> * <ul> * <li>editor : this</li> * <li>value : the current field value</li> * <li>startValue : the original field value</li> * </ul> * </dd> * * <dd><b>BeforeStartEdit</b> : EditorEvent(editor, boundEl, value)<br> * <div>Fires when editing is initiated, but before the value changes.</div> * <ul> * <li>editor : this</li> * <li>boundEl : the underlying element bound to this editor</li> * <li>value : the field value being set</li> * </ul> * </dd> * * <dd><b>StartEdit</b> : EditorEvent(editor, value)<br> * <div>Fires when this editor is displayed.</div> * <ul> * <li>editor : this</li> * <li>value : the starting field value</li> * </ul> * </dd> * * <dd><b>BeforeComplete</b> : EditorEvent(editor, value, startValue)<br> * <div>Fires after a change has been made to the field, but before the change * is reflected in the underlying field.</div> * <ul> * <li>editor : this</li> * <li>value : the current field value</li> * <li>startValue : the original field value</li> * </ul> * </dd> * * <dd><b>CancelEdit</b> : EditorEvent(editor, value, startValue)<br> * <div>Fires after editing is canceled</div> * <ul> * <li>editor : this</li> * <li>value : the current field value</li> * <li>startValue : the original field value</li> * </ul> * </dd> * * <dd><b>Complete</b> : EditorEvent(editor, value, startValue)<br> * <div>Fires after editing is complete and any changed value has been written * to the underlying field.</div> * <ul> * <li>editor : this</li> * <li>value : the current field value</li> * <li>startValue : the original field value</li> * </ul> * </dd> * * <dd><b>SpecialKey</b> : EditorEvent(field)<br> * <div>Fires when any key related to navigation (arrows, tab, enter, esc, etc.) * is pressed.</div> * <ul> * <li>editor : this</li> * </ul> * </dd> * </dl> * * <dl> * <dt>Inherited Events:</dt> * <dd>BoxComponent Move</dd> * <dd>BoxComponent Resize</dd> * <dd>Component Enable</dd> * <dd>Component Disable</dd> * <dd>Component BeforeHide</dd> * <dd>Component Hide</dd> * <dd>Component BeforeShow</dd> * <dd>Component Show</dd> * <dd>Component Attach</dd> * <dd>Component Detach</dd> * <dd>Component BeforeRender</dd> * <dd>Component Render</dd> * <dd>Component BrowserEvent</dd> * <dd>Component BeforeStateRestore</dd> * <dd>Component StateRestore</dd> * <dd>Component BeforeStateSave</dd> * <dd>Component SaveState</dd> * </dl> */ @SuppressWarnings("unchecked") public class Editor extends BoxComponent { private boolean revertInvalid = true; private String alignment = "c-c"; private boolean constrain; private boolean swallowKeys = true; private boolean completeOnEnter; private boolean cancelOnEsc; private boolean editing; private boolean updateEl = false; private Field field; private Object startValue; private Listener<FieldEvent> listener; private El boundEl; private boolean allowBlur = false; /** * Creates a new editor. * * @param field the field */ public Editor(Field field) { this.field = field; setShadow(false); } /** * Cancels the editing process and hides the editor without persisting any * changes. The field value will be reverted to the original starting value. */ public void cancelEdit() { cancelEdit(false); } /** * Ends the editing process, persists the changed value to the underlying * field, and hides the editor. */ public void completeEdit() { completeEdit(false); } /** * Returns the editor's alignment. * * @return the alignment */ public String getAlignment() { return alignment; } /** * Returns the editor's field. * * @return the field */ public Field getField() { return field; } /** * Returns the data value of the editor. * * @return the value */ public Object getValue() { return field.getValue(); } /** * Returns true if blurs are allowed. * * @return the allow blur state */ public boolean isAllowBlur() { return allowBlur; } /** * Returns true if cancel on escape is enabled. * * @return the cancel on escape state */ public boolean isCancelOnEsc() { return cancelOnEsc; } /** * Returns true if complete on enter is enabled. * * @return the complete on enter state */ public boolean isCompleteOnEnter() { return completeOnEnter; } /** * Returns true if the editor is constrained to the viewport. * * @return the constrain state */ public boolean isConstrain() { return constrain; } /** * Returns true of the editor reverts the value to the start value on invalid. * * @return the revert invalid state */ public boolean isRevertInvalid() { return revertInvalid; } /** * Returns true if key presses are being swallowed. * * @return the swallow key state */ public boolean isSwallowKeys() { return swallowKeys; } /** * Returns true if the inner HTML of the bound element is updated when the * update is complete. * * @return the update element state */ public boolean isUpdateEl() { return updateEl; } /** * Called after the editor completes an edit. * * @param value the value from the editor * @return the updated value */ public Object postProcessValue(Object value) { return value; } /** * Called before the editor sets the value on the wrapped field. * * @param value the editor value * @return the updated value */ public Object preProcessValue(Object value) { return value; } /** * Realigns the editor to the bound field based on the current alignment * config value. */ public void realign() { el().alignTo(boundEl.dom, alignment, null); } /** * The position to align to (see {@link El#alignTo} for more details, defaults * to "c-c?"). * * @param alignment the alignment */ public void setAlignment(String alignment) { this.alignment = alignment; } /** * Sets whether editing should be cancelled when the field is blurred * (defaults to false). * * @param allowBlur true to allow blur */ public void setAllowBlur(boolean allowBlur) { this.allowBlur = allowBlur; } /** * True to cancel the edit when the escape key is pressed (defaults to false). * * @param cancelOnEsc true to cancel on escape */ public void setCancelOnEsc(boolean cancelOnEsc) { this.cancelOnEsc = cancelOnEsc; } /** * True to complete the edit when the enter key is pressed (defaults to * false). * * @param completeOnEnter true to complete on enter */ public void setCompleteOnEnter(boolean completeOnEnter) { this.completeOnEnter = completeOnEnter; } /** * True to constrain the editor to the viewport. * * @param constrain true to constrain */ public void setConstrain(boolean constrain) { this.constrain = constrain; } /** * True to revert to start value on invalid value (defaults to true). * * @param revertInvalid true to revert */ public void setRevertInvalid(boolean revertInvalid) { this.revertInvalid = revertInvalid; } @Override public void setSize(int width, int height) { field.setSize(width, height); if (rendered) { el().sync(true); } } /** * Handle the keypress events so they don't propagate (defaults to true). * * @param swallowKeys true to swallow key press events. */ public void setSwallowKeys(boolean swallowKeys) { this.swallowKeys = swallowKeys; } /** * True to update the innerHTML of the bound element when the update completes * (defaults to false). * * @param updateEl true to update the inner HTML */ public void setUpdateEl(boolean updateEl) { this.updateEl = updateEl; } /** * Sets the data value of the editor * * @param value any valid value supported by the underlying field */ public void setValue(Object value) { field.setValue(value); } /** * Starts the editing process and shows the editor. * * @param el the element to edit */ public void startEdit(Element el, Object value) { if (editing) { completeEdit(); } boundEl = new El(el); Object v = value != null ? value : boundEl.getInnerHtml(); if (!rendered) { RootPanel.get().add(this); } ComponentHelper.doAttach(this); EditorEvent e = new EditorEvent(this); e.setBoundEl(boundEl); e.setValue(v); if (!fireEvent(Events.BeforeStartEdit, e)) { return; } // since field may be reused, store may be filtered if (field instanceof ComboBox) { ((ComboBox) field).getStore().clearFilters(); } startValue = preProcessValue(value); field.setValue(startValue); doAutoSize(); editing = true; el().setVisible(true); el().makePositionable(true); el().alignTo(boundEl.dom, alignment, new int[] {0, -1}); show(); field.focus(); } protected void cancelEdit(boolean remainVisible) { Object v = field.getValue(); EditorEvent e = new EditorEvent(this); e.setValue(v); e.setStartValue(startValue); if (editing && fireEvent(Events.BeforeCancelEdit, e)) { setValue(startValue); if (!remainVisible) { hide(); } editing = false; fireEvent(Events.CancelEdit, e); } } protected void completeEdit(boolean remainVisible) { if (!editing) { return; } if (revertInvalid && !field.isValid(true)) { cancelEdit(true); return; } else if (!field.isValid(false)) { return; } Object v = getValue(); if (v == startValue) { // ignore no change editing = false; hide(); return; } field.clearInvalid(); EditorEvent e = new EditorEvent(this); e.setValue(postProcessValue(v)); e.setStartValue(startValue); if (fireEvent(Events.BeforeComplete, e)) { editing = false; if (updateEl && boundEl != null) { boundEl.setInnerHtml(v.toString()); } if (!remainVisible) { hide(); } fireEvent(Events.Complete, e); } } @Override protected void doAttachChildren() { super.doAttachChildren(); ComponentHelper.doAttach(field); } protected void doAutoSize() { setSize(boundEl.getWidth(), boundEl.getHeight()); } @Override protected void doDetachChildren() { super.doDetachChildren(); ComponentHelper.doDetach(field); } protected void onBlur(FieldEvent fe) { if (!allowBlur && editing) { completeEdit(); } } @Override protected void onHide() { if (editing) { completeEdit(); return; } field.blur(); el().setVisible(false); if (rendered) { ComponentHelper.doDetach(this); } } @Override protected void onRender(Element target, int index) { super.onRender(target, index); setElement(DOM.createDiv(), target, index); setStyleName("x-editor"); el().setVisibility(true); setStyleAttribute("overflow", GXT.isGecko ? "auto" : "hidden"); field.setMessageTarget("tooltip"); field.setInEditor(true); field.render(getElement()); if (GXT.isGecko) { field.getElement().setAttribute("autocomplete", "off"); } listener = new Listener<FieldEvent>() { public void handleEvent(FieldEvent fe) { if (fe.getType() == Events.SpecialKey) { onSpecialKey(fe); } else if (fe.getType() == Events.Blur) { onBlur(fe); } } }; this.field.addListener(Events.SpecialKey, listener); this.field.addListener(Events.Blur, listener); field.show(); } @Override protected void onShow() { el().setVisible(true); el().updateZIndex(100); field.show(); EditorEvent e = new EditorEvent(this); e.setBoundEl(boundEl); e.setValue(startValue); fireEvent(Events.StartEdit, e); } protected void onSpecialKey(FieldEvent fe) { int key = fe.getKeyCode(); if (completeOnEnter && key == KeyCodes.KEY_ENTER) { fe.stopEvent(); completeEdit(); } else if (cancelOnEsc && key == KeyCodes.KEY_ESCAPE) { cancelEdit(); } else { fireEvent(Events.SpecialKey, fe); } if (field instanceof TriggerField && (key == KeyCodes.KEY_ENTER || key == KeyCodes.KEY_ESCAPE || key == KeyCodes.KEY_TAB)) { triggerBlur((TriggerField) field); } } protected native void triggerBlur(TriggerField field) /*-{ field.@com.extjs.gxt.ui.client.widget.form.TriggerField::triggerBlur(Lcom/extjs/gxt/ui/client/event/ComponentEvent;)(null); }-*/; }