/* * Copyright 2014 Red Hat, Inc. and/or its affiliates. * * 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.kie.workbench.common.screens.projecteditor.client.forms.dependencies; import com.google.gwt.cell.client.AbstractEditableCell; import com.google.gwt.cell.client.ValueUpdater; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.InputElement; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.text.shared.SimpleSafeHtmlRenderer; import static com.google.gwt.dom.client.BrowserEvents.*; /** * A EditTextCell that shows a water mark for empty values */ public class WaterMarkEditTextCell extends AbstractEditableCell<String, WaterMarkEditTextCell.ViewData> { interface Template extends SafeHtmlTemplates { @Template("<input type=\"text\" value=\"{0}\" tabindex=\"-1\"></input>") SafeHtml input( String value ); } interface WaterMarkTemplate extends SafeHtmlTemplates { @Template("<div style=\"color: #999999\">{0}</div>") SafeHtml watermark( String value ); } /** * The view data object used by this cell. We need to store both the text and * the state because this cell is rendered differently in edit mode. If we did * not store the edit state, refreshing the cell with view data would always * put us in to edit state, rendering a text box instead of the new text * string. */ static class ViewData { private boolean isEditing; /** * If true, this is not the first edit. */ private boolean isEditingAgain; /** * Keep track of the original value at the start of the edit, which might be * the edited value from the previous edit and NOT the actual value. */ private String original; private String text; /** * Construct a new ViewData in editing mode. * @param text the text to edit */ public ViewData( String text ) { this.original = text; this.text = text; this.isEditing = true; this.isEditingAgain = false; } @Override public boolean equals( Object o ) { if ( o == null ) { return false; } ViewData vd = (ViewData) o; return equalsOrBothNull( original, vd.original ) && equalsOrBothNull( text, vd.text ) && isEditing == vd.isEditing && isEditingAgain == vd.isEditingAgain; } public String getOriginal() { return original; } public String getText() { return text; } @Override public int hashCode() { int result = original.hashCode() + text.hashCode() + Boolean.valueOf( isEditing ).hashCode() * 29 + Boolean.valueOf( isEditingAgain ).hashCode(); result = ~~result; return result; } public boolean isEditing() { return isEditing; } public boolean isEditingAgain() { return isEditingAgain; } public void setEditing( boolean isEditing ) { boolean wasEditing = this.isEditing; this.isEditing = isEditing; // This is a subsequent edit, so start from where we left off. if ( !wasEditing && isEditing ) { isEditingAgain = true; original = text; } } public void setText( String text ) { this.text = text; } private boolean equalsOrBothNull( Object o1, Object o2 ) { return ( o1 == null ) ? o2 == null : o1.equals( o2 ); } } private static Template template; private static WaterMarkTemplate waterMarkTemplate; private final SafeHtmlRenderer<String> renderer; private final String watermark; /** * Construct a new WaterMarkEditTextCell that will use a * {@link SimpleSafeHtmlRenderer}. */ public WaterMarkEditTextCell( String watermark ) { this( SimpleSafeHtmlRenderer.getInstance(), watermark ); } /** * Construct a new WaterMarkEditTextCell that will use a given {@link SafeHtmlRenderer} * to render the value when not in edit mode. * @param renderer a {@link SafeHtmlRenderer SafeHtmlRenderer<String>} * instance */ public WaterMarkEditTextCell( SafeHtmlRenderer<String> renderer, String watermark ) { super( CLICK, KEYUP, KEYDOWN, BLUR ); if ( template == null ) { template = GWT.create( Template.class ); } if ( waterMarkTemplate == null ) { waterMarkTemplate = GWT.create( WaterMarkTemplate.class ); } if ( renderer == null ) { throw new IllegalArgumentException( "renderer == null" ); } this.renderer = renderer; this.watermark = watermark; } @Override public boolean isEditing( Context context, Element parent, String value ) { ViewData viewData = getViewData( context.getKey() ); return viewData == null ? false : viewData.isEditing(); } @Override public void onBrowserEvent( Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater ) { Object key = context.getKey(); ViewData viewData = getViewData( key ); if ( viewData != null && viewData.isEditing() ) { // Handle the edit event. editEvent( context, parent, value, viewData, event, valueUpdater ); } else { String type = event.getType(); int keyCode = event.getKeyCode(); boolean enterPressed = KEYUP.equals( type ) && keyCode == KeyCodes.KEY_ENTER; if ( CLICK.equals( type ) || enterPressed ) { // Go into edit mode. if ( viewData == null ) { viewData = new ViewData( value ); setViewData( key, viewData ); } else { viewData.setEditing( true ); } edit( context, parent, value ); } } } @Override public void render( Context context, String value, SafeHtmlBuilder sb ) { // Get the view data. Object key = context.getKey(); ViewData viewData = getViewData( key ); if ( viewData != null && !viewData.isEditing() && value != null && value.equals( viewData.getText() ) ) { clearViewData( key ); viewData = null; } String toRender = value; if ( viewData != null ) { String text = viewData.getText(); if ( viewData.isEditing() ) { /* * Do not use the renderer in edit mode because the value of a text * input element is always treated as text. SafeHtml isn't valid in the * context of the value attribute. */ sb.append( template.input( text ) ); return; } else { // The user pressed enter, but view data still exists. toRender = text; } } if ( toRender != null && toRender.trim().length() > 0 ) { sb.append( renderer.render( toRender ) ); } else { sb.append( waterMarkTemplate.watermark( watermark ) ); } } @Override public boolean resetFocus( Context context, Element parent, String value ) { if ( isEditing( context, parent, value ) ) { getInputElement( parent ).focus(); return true; } return false; } /** * Convert the cell to edit mode. * @param context the {@link Context} of the cell * @param parent the parent element * @param value the current value */ protected void edit( Context context, Element parent, String value ) { setValue( context, parent, value ); InputElement input = getInputElement( parent ); input.focus(); input.select(); } /** * Convert the cell to non-edit mode. * @param context the context of the cell * @param parent the parent Element * @param value the value associated with the cell */ private void cancel( Context context, Element parent, String value ) { clearInput( getInputElement( parent ) ); setValue( context, parent, value ); } /** * Clear selected from the input element. Both Firefox and IE fire spurious * onblur events after the input is removed from the DOM if selection is not * cleared. * @param input the input element */ private native void clearInput( Element input ) /*-{ if (input.selectionEnd) input.selectionEnd = input.selectionStart; else if ($doc.selection) $doc.selection.clear(); }-*/; /** * Commit the current value. * @param context the context of the cell * @param parent the parent Element * @param viewData the {@link ViewData} object * @param valueUpdater the {@link ValueUpdater} */ private void commit( Context context, Element parent, ViewData viewData, ValueUpdater<String> valueUpdater ) { String value = updateViewData( parent, viewData, false ); clearInput( getInputElement( parent ) ); setValue( context, parent, viewData.getOriginal() ); if ( valueUpdater != null ) { valueUpdater.update( value ); } } private void editEvent( Context context, Element parent, String value, ViewData viewData, NativeEvent event, ValueUpdater<String> valueUpdater ) { String type = event.getType(); boolean keyUp = KEYUP.equals( type ); boolean keyDown = KEYDOWN.equals( type ); if ( keyUp || keyDown ) { int keyCode = event.getKeyCode(); if ( keyUp && keyCode == KeyCodes.KEY_ENTER ) { // Commit the change. commit( context, parent, viewData, valueUpdater ); } else if ( keyUp && keyCode == KeyCodes.KEY_ESCAPE ) { // Cancel edit mode. String originalText = viewData.getOriginal(); if ( viewData.isEditingAgain() ) { viewData.setText( originalText ); viewData.setEditing( false ); } else { setViewData( context.getKey(), null ); } cancel( context, parent, value ); } else { // Update the text in the view data on each key. updateViewData( parent, viewData, true ); } } else if ( BLUR.equals( type ) ) { // Commit the change. Ensure that we are blurring the input element and // not the parent element itself. EventTarget eventTarget = event.getEventTarget(); if ( Element.is( eventTarget ) ) { Element target = Element.as( eventTarget ); if ( "input".equals( target.getTagName().toLowerCase() ) ) { commit( context, parent, viewData, valueUpdater ); } } } } /** * Get the input element in edit mode. */ private InputElement getInputElement( Element parent ) { return parent.getFirstChild().<InputElement>cast(); } /** * Update the view data based on the current value. * @param parent the parent element * @param viewData the {@link ViewData} object to update * @param isEditing true if in edit mode * @return the new value */ private String updateViewData( Element parent, ViewData viewData, boolean isEditing ) { InputElement input = (InputElement) parent.getFirstChild(); String value = input.getValue(); viewData.setText( value ); viewData.setEditing( isEditing ); return value; } }