/* * Copyright (C) 2012 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.jboss.errai.common.client.ui; import java.util.HashMap; import java.util.Map; import org.jboss.errai.common.client.dom.HTMLElement; import org.jboss.errai.common.client.ui.NativeHasValueAccessors.Accessor; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.InputElement; import com.google.gwt.dom.client.TextAreaElement; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.ui.HasHTML; import com.google.gwt.user.client.ui.HasValue; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; /** * A widget that wraps an {@link Element} to support the registration of event listeners and data * binding. * * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a> * @author Christian Sadilek <csadilek@redhat.com> */ public abstract class ElementWrapperWidget<T> extends Widget { private static Map<Object, ElementWrapperWidget<?>> widgetMap = new HashMap<>(); public static ElementWrapperWidget<?> getWidget(final Element element) { return getWidget(element, null); } public static ElementWrapperWidget<?> getWidget(final HTMLElement element) { return getWidget(element, null); } public static ElementWrapperWidget<?> getWidget(final Element element, final Class<?> valueType) { return getWidget((Object) element, valueType); } public static ElementWrapperWidget<?> getWidget(final Object obj, final Class<?> valueType) { final Element element = asElement(obj); ElementWrapperWidget<?> widget = widgetMap.get(element); if (widget == null) { widget = createElementWrapperWidget(element, valueType); // Always call onAttach so that events propogatge even if this has no widget parent. widget.onAttach(); RootPanel.detachOnWindowClose(widget); widgetMap.put(element, widget); } else if (valueType != null && !valueType.equals(widget.getValueType())) { throw new RuntimeException( "There already exists a widget for the given element with a different value type. Expected " + widget.getValueType().getName() + " but was passed in " + valueType.getName()); } return widget; } private static native Element asElement(Object obj) /*-{ return obj; }-*/; @SuppressWarnings({ "rawtypes", "unchecked" }) private static ElementWrapperWidget<?> createElementWrapperWidget(final Element element, final Class<?> valueType) { if (valueType != null) { final Accessor accessor; if (NativeHasValueAccessors.hasValueAccessor(element)) { accessor = NativeHasValueAccessors.getAccessor(element); } else { accessor = new DefaultAccessor((org.jboss.errai.common.client.ui.HasValue) element); } return new JsTypeHasValueElementWrapperWidget<>(element, accessor, valueType); } else if (InputElement.is(element) || TextAreaElement.is(element)) { return new InputElementWrapperWidget<>(element); } else { return new DefaultElementWrapperWidget<>(element); } } public static Class<?> getValueClassForInputType(final String inputType) { if ("checkbox".equalsIgnoreCase(inputType) || "radio".equalsIgnoreCase(inputType)) { return Boolean.class; } else { return String.class; } } private static boolean different(final Object oldValue, final Object newValue) { return (oldValue == null ^ newValue == null) || (oldValue != null && !oldValue.equals(newValue)); } public static ElementWrapperWidget<?> removeWidget(final Element element) { return widgetMap.remove(element); } public static ElementWrapperWidget<?> removeWidget(final ElementWrapperWidget<?> widget) { return widgetMap.remove(widget.getElement()); } private static abstract class HasValueElementWrapperWidget<T> extends ElementWrapperWidget<T> implements HasValue<T> { private final ValueChangeManager<T, HasValueElementWrapperWidget<T>> valueChangeManager = new ValueChangeManager<>(this); private HasValueElementWrapperWidget(final Element element) { super(element); } @Override public void setValue(final T value, final boolean fireEvents) { final T oldValue = getValue(); setValue(value); if (fireEvents && different(oldValue, value)) { ValueChangeEvent.fire(this, value); } } @Override public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<T> handler) { return valueChangeManager.addValueChangeHandler(handler); } } private static class DefaultAccessor<T> implements Accessor<T> { private final org.jboss.errai.common.client.ui.HasValue<T> instance; private DefaultAccessor(final org.jboss.errai.common.client.ui.HasValue<T> instance) { this.instance = instance; } @Override public T get() { try { return instance.getValue(); } catch (final Throwable t) { throw new RuntimeException("Unable to invoke getValue() on JsType: " + t.getMessage(), t); } } @Override public void set(final T value) { try { instance.setValue(value); } catch (final Throwable t) { throw new RuntimeException("Unable to invoke setValue(T value) on JsType: " + t.getMessage(), t); } } } private static class JsTypeHasValueElementWrapperWidget<T> extends HasValueElementWrapperWidget<T> { private final Class<T> valueType; private final Accessor<T> accessor; private JsTypeHasValueElementWrapperWidget(final Element element, final Accessor<T> accessor, final Class<T> valueType) { super(element); this.accessor = accessor; this.valueType = valueType; } @Override public T getValue() { return accessor.get(); } @Override public void setValue(final T value) { accessor.set(value); } @Override public Class<?> getValueType() { return valueType; } } private static class InputElementWrapperWidget<T> extends HasValueElementWrapperWidget<T> { private InputElementWrapperWidget(final Element element) { super(element); } @Override public void setValue(final T value) { final String inputType = getElement().getPropertyString("type"); final Class<?> valueType = getValueClassForInputType(inputType); if (Boolean.class.equals(valueType)) { getElement().setPropertyBoolean("checked", (Boolean) value); } else if (String.class.equals(valueType)) { getElement().setPropertyObject("value", value != null ? value : ""); } else { throw new IllegalArgumentException("Cannot set value " + value + " to element input[type=\"" + inputType + "\"]."); } } @SuppressWarnings("unchecked") @Override public T getValue() { final String inputType = getElement().getPropertyString("type"); final Class<?> valueType = getValueClassForInputType(inputType); if (Boolean.class.equals(valueType)) { return (T) (Boolean) getElement().getPropertyBoolean("checked"); } else if (String.class.equals(valueType)) { final Object rawValue = getElement().getPropertyObject("value"); return (T) (rawValue != null ? rawValue : ""); } else { throw new RuntimeException("Unrecognized input element type [" + inputType + "]"); } } @Override public Class<?> getValueType() { return getValueClassForInputType(getElement().getPropertyString("type")); } } private static class DefaultElementWrapperWidget<T> extends ElementWrapperWidget<T> implements HasHTML { private DefaultElementWrapperWidget(final Element element) { super(element); } @Override public String getText() { return getElement().getInnerText(); } @Override public void setText(final String text) { getElement().setInnerText(text); } @Override public String getHTML() { return getElement().getInnerHTML(); } @Override public void setHTML(final String html) { getElement().setInnerHTML(html); } @Override public Class<?> getValueType() { return String.class; } } private EventListener listener = new EventListener() { @Override public void onBrowserEvent(final Event event) { ElementWrapperWidget.super.onBrowserEvent(event); } }; private ElementWrapperWidget(final Element wrapped) { if (wrapped == null) { throw new IllegalArgumentException( "Element to be wrapped must not be null - Did you forget to initialize or @Inject a UI field?"); } this.setElement(wrapped); DOM.setEventListener(this.getElement(), this); } public void setEventListener(final int eventsToSink, final EventListener listener) { if (listener == null) { throw new IllegalArgumentException("EventListener cannot be null."); } sinkEvents(eventsToSink); this.listener = listener; } @Override public void onBrowserEvent(final Event event) { listener.onBrowserEvent(event); } public abstract Class<?> getValueType(); }