package org.ovirt.engine.ui.common.widget; import java.util.List; import org.gwtbootstrap3.client.ui.constants.ColumnSize; import org.gwtbootstrap3.client.ui.constants.Styles; import org.ovirt.engine.ui.common.idhandler.HasElementId; import org.ovirt.engine.ui.common.view.popup.FocusableComponentsContainer; import org.ovirt.engine.ui.common.widget.editor.EditorStateUpdateEvent; import org.ovirt.engine.ui.common.widget.editor.EditorWidget; import org.ovirt.engine.ui.common.widget.label.HasWidgetLabels; import org.ovirt.engine.ui.common.widget.label.LabelWithTooltip; import org.ovirt.engine.ui.common.widget.label.WidgetLabel; import org.ovirt.engine.ui.common.widget.tooltip.WidgetTooltip; import org.ovirt.engine.ui.uicommonweb.HasCleanup; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.HasAllKeyHandlers; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.KeyPressHandler; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.logical.shared.HasValueChangeHandlers; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.resources.client.CssResource; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.TakesValue; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Focusable; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; /** * <p> * Base class for validated widgets that have a label associated with them. * </p> * <p> * This widget can run in legacy mode or PatternFly mode. Legacy mode uses absolute positioning * with hardcoded pixels, and should be avoided. PatternFly mode makes use of PatternFly (Bootstrap) * grid positioning, and is preferred. * </p> * <p> * To enable PatternFly mode, call setUsePatternFly(true). You'll also probably want to set * PatternFly grid classes on both the label and the widget container. E.g: <br/> * addLabelStyleName(Styles.SM_2);<br/> * addContentWidgetContainerStyleName(Styles.SM_10);<br/> * </p> * @param <W> * Content widget type. */ public abstract class AbstractValidatedWidgetWithLabel<T, W extends EditorWidget<T, ?> & TakesValue<T> & HasValueChangeHandlers<T>> extends AbstractValidatedWidget implements HasLabel, HasEnabledWithHints, HasWidgetLabels, HasAccess, HasAllKeyHandlers, HasElementId, Focusable, FocusableComponentsContainer, HasCleanup { interface WidgetUiBinder extends UiBinder<Widget, AbstractValidatedWidgetWithLabel<?, ?>> { WidgetUiBinder uiBinder = GWT.create(WidgetUiBinder.class); } interface Style extends CssResource { // TODO: remove these when all usages are PatternFly-based String label_legacy(); String wrapper_legacy(); String contentWidgetContainer_legacy(); String maxWidth(); } //We need to store the valid state of the editor so that when the model validator //runs and the editor is not valid (due to a parsing error), the editor doesn't get //reset by the model. private boolean editorStateValid = true; private final W contentWidget; @UiField FlowPanel wrapperPanel; @UiField WidgetLabel label; @UiField FlowPanel contentWidgetContainer; SimplePanel sizeContainer; @UiField WidgetTooltip contentWidgetContainerTooltip; @UiField Style style; protected String contentWidgetContainerConfiguredTooltip = null; protected boolean removeFormGroup = false; // width in PX -- only used in legacy mode public static final int CONTENT_WIDTH_LEGACY = 230; private VisibilityRenderer renderer; public AbstractValidatedWidgetWithLabel(W contentWidget, VisibilityRenderer renderer) { this.contentWidget = contentWidget; this.renderer = renderer; initWidget(WidgetUiBinder.uiBinder.createAndBindUi(this)); setUsePatternFly(false); addStateUpdateHandler(); } public AbstractValidatedWidgetWithLabel(W contentWidget) { this(contentWidget, new VisibilityRenderer.SimpleVisibilityRenderer()); } @Override protected void initWidget(Widget wrapperWidget) { super.initWidget(wrapperWidget); contentWidgetContainer.add(contentWidget); // Assign ID to content widget element if it's missing or empty Element contentWidgetElement = getContentWidgetElement(); if (contentWidgetElement.getId() == null || contentWidgetElement.getId().isEmpty()) { setElementId(DOM.createUniqueId()); } } protected LabelWithTooltip getFormLabel() { if (label instanceof LabelWithTooltip) { return (LabelWithTooltip)label; } throw new IllegalStateException("No label defined in widget that requires a label"); //$NON-NLS-1$ } /** * set for="" for better accessibility */ protected void updateLabelElementId(String elementId) { label.setFor(elementId); } public void setRemoveFormGroup(final boolean removeFormGroup) { this.removeFormGroup = removeFormGroup; } public void setUsePatternFly(final boolean usePatternfly) { super.setUsePatternFly(usePatternfly); // toggle styles -- remove both PatternFly and non-PatternFly styles removeContentWidgetStyleName(style.maxWidth()); removeContentWidgetStyleName(Styles.FORM_CONTROL); removeContentWidgetContainerStyleName(style.contentWidgetContainer_legacy()); removeContentWidgetContainerStyleName("avw_contentWidgetContainer_pfly_fix"); //$NON-NLS-1$ removeWrapperStyleName(Styles.FORM_GROUP); removeWrapperStyleName(style.wrapper_legacy()); removeWrapperStyleName("avw_wrapper_pfly_fix"); //$NON-NLS-1$ // add the proper styles if (usePatternfly) { addContentWidgetStyleName(Styles.FORM_CONTROL); addContentWidgetContainerStyleName(style.maxWidth()); if (!removeFormGroup) { addWrapperStyleName(Styles.FORM_GROUP); } wrapperPanel.remove(contentWidgetContainer); if (sizeContainer == null) { sizeContainer = new SimplePanel(); sizeContainer.setWidget(contentWidgetContainer); wrapperPanel.insert(sizeContainer, 1); } } else { addContentWidgetStyleName(style.maxWidth()); addContentWidgetContainerStyleName(style.contentWidgetContainer_legacy()); addContentWidgetContainerStyleName("avw_contentWidgetContainer_pfly_fix"); //$NON-NLS-1$ addWrapperStyleName(style.wrapper_legacy()); addWrapperStyleName("avw_wrapper_pfly_fix"); //$NON-NLS-1$ } } public void setUnitString(String unitString) { SimplePanel unitAddOn = new SimplePanel(); unitAddOn.getElement().setInnerHTML(unitString); unitAddOn.addStyleName(Styles.INPUT_GROUP_ADDON); contentWidgetContainer.addStyleName(Styles.INPUT_GROUP); contentWidgetContainer.add(unitAddOn); } public void setLabelColSize(ColumnSize size) { getFormLabel().setAddStyleNames(size.getCssName()); } public void setWidgetColSize(ColumnSize size) { if (sizeContainer != null) { sizeContainer.addStyleName(size.getCssName()); } } /** * Render widget more responsive, by firing {@link ValueChangeEvent} on each {@link KeyDownEvent}. */ public void fireValueChangeOnKeyDown() { getContentWidget().addKeyDownHandler(event -> { // deferring is required to allow the widget's internal value to update according to key press Scheduler.get().scheduleDeferred(() -> ValueChangeEvent.fire(getContentWidget(), getContentWidget().getValue())); }); } protected W getContentWidget() { return contentWidget; } protected Element getContentWidgetElement() { return contentWidget.asWidget().getElement(); } public FlowPanel getContentWidgetContainer() { return contentWidgetContainer; } @Override public void setElementId(String elementId) { getContentWidgetElement().setId(elementId); updateLabelElementId(elementId); } @Override protected Widget getValidatedWidget() { return getContentWidget().asWidget(); } @Override public void setLabel(String labelText) { getFormLabel().setText(labelText); } public void setLabelTooltip(String tooltip) { getFormLabel().setTooltip(tooltip); } public String getLabel() { return getFormLabel().getText(); } @Override public boolean isAccessible() { return wrapperPanel.isVisible(); } @Override public void setAccessible(boolean accessible) { wrapperPanel.setVisible(renderer.render(this, accessible)); } @Override public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { return contentWidget.addKeyDownHandler(handler); } @Override public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) { return contentWidget.addKeyPressHandler(handler); } @Override public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) { return contentWidget.addKeyUpHandler(handler); } @Override public int getTabIndex() { return contentWidget.getTabIndex(); } @Override public void setAccessKey(char key) { contentWidget.setAccessKey(key); } @Override public void setFocus(boolean focused) { contentWidget.setFocus(focused); } @Override public void setTabIndex(int index) { contentWidget.setTabIndex(index); } @Override public int setTabIndexes(int nextTabIndex) { setTabIndex(nextTabIndex++); return nextTabIndex; } @Override public boolean isEnabled() { return contentWidget.isEnabled(); } @Override public void setEnabled(boolean enabled) { contentWidget.setEnabled(enabled); getFormLabel().setEnabled(enabled); if (enabled) { setWidgetTooltip(""); label.setEnabled(true); } } @Override public void disable(String disabilityHint) { setEnabled(false); setWidgetTooltip(disabilityHint); label.disable(disabilityHint); } @Override public void markAsValid() { if (editorStateValid) { super.markAsValid(); } label.setEnabled(true); contentWidgetContainerTooltip.setText(contentWidgetContainerConfiguredTooltip); } @Override public void markAsInvalid(List<String> validationHints) { super.markAsInvalid(validationHints); String tooltipText = getValidationTooltipText(validationHints); label.disable(tooltipText); contentWidgetContainerTooltip.setText(tooltipText); } public void setWidgetTooltip(String text) { setContentWidgetContainerTooltip(text); } @Override public void removeLabel(WidgetLabel label) { label.setFor(null); } @Override public void addLabel(WidgetLabel label) { label.setFor(getContentWidgetElement().getId()); this.label = label; } public void setContentWidgetContainerTooltip(String tooltipText) { contentWidgetContainerConfiguredTooltip = tooltipText; contentWidgetContainerTooltip.setText(tooltipText); } // set styleNames on my components public void addContentWidgetStyleName(String styleName) { getContentWidget().asWidget().addStyleName(styleName); } public void setContentWidgetStyleName(String styleName) { getContentWidget().asWidget().setStyleName(styleName); } public void removeContentWidgetStyleName(String styleName) { getContentWidget().asWidget().removeStyleName(styleName); } public void addContentWidgetContainerStyleName(String styleName) { contentWidgetContainer.addStyleName(styleName); } public void setContentWidgetContainerStyleName(String styleName) { contentWidgetContainer.setStyleName(styleName); } public void removeContentWidgetContainerStyleName(String styleName) { contentWidgetContainer.removeStyleName(styleName); } /** * @param styleNames space or comma-delimited list of style names */ public void addLabelStyleNames(String styleNames) { for (String name : styleNames.split("[,\\s]+")) { //$NON-NLS-1$ getFormLabel().setAddStyleNames(name); } } // UIBinder-capable alias for addLabelStyleNames public void setAddLabelStyleNames(String styleNames) { addLabelStyleNames(styleNames); } public void addWrapperStyleName(String styleName) { wrapperPanel.addStyleName(styleName); } public void setWrapperStyleName(String styleName) { wrapperPanel.setStyleName(styleName); } public void removeWrapperStyleName(String styleName) { wrapperPanel.removeStyleName(styleName); } // end set styleNames on my components public void hideLabel() { getFormLabel().asWidget().setVisible(false); } public VisibilityRenderer getRenderer() { return renderer; } public void setRenderer(VisibilityRenderer renderer) { this.renderer = renderer; } /** * Force fire a change event on this field. This will trigger editor and model * population from the field without a user edit and blur. */ public void fireChangeEvent() { ValueChangeEvent.fire(getContentWidget(), getContentWidget().getValue()); } protected void handleInvalidState() { editorStateValid = false; } private void addStateUpdateHandler() { this.getContentWidget().asWidget().addHandler(event -> { if (event.isValid()) { //Mark the editor as valid. editorStateValid = true; markAsValid(); } else { //Mark the editor as invalid. handleInvalidState(); } }, EditorStateUpdateEvent.getType()); } public boolean isUsePatternfly() { return this.usePatternfly; } @Override public void cleanup() { W contentWidget = getContentWidget(); if (contentWidget instanceof HasCleanup) { ((HasCleanup) contentWidget).cleanup(); } contentWidgetContainerTooltip.cleanup(); } }