diff options
author | Leif Åstrand <leif@vaadin.com> | 2014-03-27 11:14:55 +0200 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2014-04-01 08:15:56 +0000 |
commit | 7bfb2347ad32187444c194e7ebadae6a6d3597ca (patch) | |
tree | c94081d4cb9d2eba433c4cec3ddbcc27da57c643 /client | |
parent | a636bb70c20277228468345017a2f8d42d39857c (diff) | |
download | vaadin-framework-7bfb2347ad32187444c194e7ebadae6a6d3597ca.tar.gz vaadin-framework-7bfb2347ad32187444c194e7ebadae6a6d3597ca.zip |
Implement @OnStateChange (#12958)
Change-Id: I8ea2b781fab42458bf55a751c1229e391365e965
Diffstat (limited to 'client')
5 files changed, 293 insertions, 48 deletions
diff --git a/client/src/com/vaadin/client/annotations/OnStateChange.java b/client/src/com/vaadin/client/annotations/OnStateChange.java new file mode 100644 index 0000000000..80c1095458 --- /dev/null +++ b/client/src/com/vaadin/client/annotations/OnStateChange.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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 com.vaadin.client.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.vaadin.client.communication.StateChangeEvent; + +/** + * Marks a method in Connector classes that should be used to handle changes to + * specific properties in the connector's shared state. + * <p> + * The annotated method will by called whenever at least one of the named state + * properties have changed. If multiple listened properties are changed by the + * same {@link StateChangeEvent}, the method will only be called once. + * <p> + * If there is no state variable with the provided name, the widgetset + * compilation will fail. + * + * @since 7.2 + * @author Vaadin Ltd + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface OnStateChange { + /** + * Defines a list of property names to listen for. + * + * @return an array of property names, should contain at least one item + */ + public String[] value(); +} diff --git a/client/src/com/vaadin/client/metadata/OnStateChangeMethod.java b/client/src/com/vaadin/client/metadata/OnStateChangeMethod.java new file mode 100644 index 0000000000..2ba06fd4eb --- /dev/null +++ b/client/src/com/vaadin/client/metadata/OnStateChangeMethod.java @@ -0,0 +1,111 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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 com.vaadin.client.metadata; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.communication.StateChangeEvent; + +/** + * Encapsulates the data that the widgetset compiler generates for supporting a + * connector method annotated with {@link OnStateChange} + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class OnStateChangeMethod { + + private final String methodName; + private final List<String> properties; + private final Class<?> declaringClass; + + /** + * Creates a new instance based on a method name, a list of parameters names + * and a list of properties to listen for. + * + * @param methodName + * the name of the method to call + * @param properties + * an array of state property names to listen to + */ + public OnStateChangeMethod(String methodName, String[] properties) { + this(null, methodName, properties); + } + + /** + * Creates a new instance based on declaring class, a method name, a list of + * parameters names and a list of properties to listen for. + * <p> + * If the declaring class is <code>null</code>, the method is found based on + * the type of the connector that fired the state change event. + * + * @param declaringClass + * the class in which the target method is declared, or + * <code>null</code> to use the class of the connector firing the + * event + * @param methodName + * the name of the method to call + * @param properties + * an array of state property names to listen to + */ + public OnStateChangeMethod(Class<?> declaringClass, String methodName, + String[] properties) { + + this.methodName = methodName; + + this.properties = Collections.unmodifiableList(Arrays + .asList(properties)); + + this.declaringClass = declaringClass; + } + + /** + * Invokes the listener method for a state change. + * + * @param stateChangeEvent + * the state change event + */ + public void invoke(StateChangeEvent stateChangeEvent) { + ServerConnector connector = (ServerConnector) stateChangeEvent + .getSource(); + + Class<?> declaringClass = this.declaringClass; + if (declaringClass == null) { + declaringClass = connector.getClass(); + } + Type declaringType = TypeDataStore.getType(declaringClass); + + try { + declaringType.getMethod(methodName).invoke(connector); + } catch (NoDataException e) { + throw new RuntimeException("Couldn't invoke @OnStateChange method " + + declaringType.getSignature() + "." + methodName, e); + } + } + + /** + * Gets the list of state property names to listen for. + * + * @return the list of state property names to listen for + */ + public List<String> getProperties() { + return properties; + } +} diff --git a/client/src/com/vaadin/client/metadata/TypeDataStore.java b/client/src/com/vaadin/client/metadata/TypeDataStore.java index d5fbc94823..bc6610a6ff 100644 --- a/client/src/com/vaadin/client/metadata/TypeDataStore.java +++ b/client/src/com/vaadin/client/metadata/TypeDataStore.java @@ -23,6 +23,7 @@ import com.google.gwt.core.client.JsArrayString; import com.vaadin.client.FastStringMap; import com.vaadin.client.FastStringSet; import com.vaadin.client.JsArrayObject; +import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.JSONSerializer; public class TypeDataStore { @@ -37,6 +38,12 @@ public class TypeDataStore { private final FastStringMap<JsArrayString> delegateToWidgetProperties = FastStringMap .create(); + /** + * Maps connector class -> state property name -> hander method data + */ + private final FastStringMap<FastStringMap<JsArrayObject<OnStateChangeMethod>>> onStateChangeMethods = FastStringMap + .create(); + private final FastStringSet delayedMethods = FastStringSet.create(); private final FastStringSet lastOnlyMethods = FastStringSet.create(); @@ -368,4 +375,47 @@ public class TypeDataStore { return typeData[beanName] !== undefined ; }-*/; + /** + * Gets data for all methods annotated with {@link OnStateChange} in the + * given connector type. + * + * @since 7.2 + * @param type + * the connector type + * @return a map of state property names to handler method data + */ + public static FastStringMap<JsArrayObject<OnStateChangeMethod>> getOnStateChangeMethods( + Class<?> type) { + return get().onStateChangeMethods.get(getType(type).getSignature()); + } + + /** + * Adds data about a method annotated with {@link OnStateChange} for the + * given connector type. + * + * @since 7.2 + * @param clazz + * the connector type + * @param method + * the state change method data + */ + public void addOnStateChangeMethod(Class<?> clazz, + OnStateChangeMethod method) { + FastStringMap<JsArrayObject<OnStateChangeMethod>> handlers = getOnStateChangeMethods(clazz); + if (handlers == null) { + handlers = FastStringMap.create(); + onStateChangeMethods.put(getType(clazz).getSignature(), handlers); + } + + for (String property : method.getProperties()) { + JsArrayObject<OnStateChangeMethod> propertyHandlers = handlers + .get(property); + if (propertyHandlers == null) { + propertyHandlers = JsArrayObject.createArray().cast(); + handlers.put(property, propertyHandlers); + } + + propertyHandlers.add(method); + } + } } diff --git a/client/src/com/vaadin/client/ui/AbstractConnector.java b/client/src/com/vaadin/client/ui/AbstractConnector.java index 6855c5cd2d..bd499ac4bc 100644 --- a/client/src/com/vaadin/client/ui/AbstractConnector.java +++ b/client/src/com/vaadin/client/ui/AbstractConnector.java @@ -18,6 +18,7 @@ package com.vaadin.client.ui; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -27,6 +28,7 @@ import com.google.gwt.event.shared.HandlerManager; import com.google.web.bindery.event.shared.HandlerRegistration; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.FastStringMap; +import com.vaadin.client.JsArrayObject; import com.vaadin.client.Profiler; import com.vaadin.client.ServerConnector; import com.vaadin.client.Util; @@ -35,8 +37,10 @@ import com.vaadin.client.communication.RpcProxy; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.metadata.NoDataException; +import com.vaadin.client.metadata.OnStateChangeMethod; import com.vaadin.client.metadata.Type; import com.vaadin.client.metadata.TypeData; +import com.vaadin.client.metadata.TypeDataStore; import com.vaadin.shared.communication.ClientRpc; import com.vaadin.shared.communication.ServerRpc; import com.vaadin.shared.communication.SharedState; @@ -290,6 +294,37 @@ public abstract class AbstractConnector implements ServerConnector, } updateEnabledState(isEnabled()); + + FastStringMap<JsArrayObject<OnStateChangeMethod>> handlers = TypeDataStore + .getOnStateChangeMethods(getClass()); + if (handlers != null) { + Profiler.enter("AbstractConnector.onStateChanged @OnStateChange"); + + HashSet<OnStateChangeMethod> invokedMethods = new HashSet<OnStateChangeMethod>(); + + JsArrayString propertyNames = handlers.getKeys(); + for (int i = 0; i < propertyNames.length(); i++) { + String propertyName = propertyNames.get(i); + + if (stateChangeEvent.hasPropertyChanged(propertyName)) { + JsArrayObject<OnStateChangeMethod> propertyMethods = handlers + .get(propertyName); + + for (int j = 0; j < propertyMethods.size(); j++) { + OnStateChangeMethod method = propertyMethods.get(j); + + if (invokedMethods.add(method)) { + + method.invoke(stateChangeEvent); + + } + } + } + } + + Profiler.leave("AbstractConnector.onStateChanged @OnStateChange"); + } + Profiler.leave("AbstractConnector.onStateChanged"); } diff --git a/client/src/com/vaadin/client/ui/button/ButtonConnector.java b/client/src/com/vaadin/client/ui/button/ButtonConnector.java index 94c2841c3c..32a457c1f1 100644 --- a/client/src/com/vaadin/client/ui/button/ButtonConnector.java +++ b/client/src/com/vaadin/client/ui/button/ButtonConnector.java @@ -26,8 +26,8 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.vaadin.client.EventHelper; import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.ui.AbstractComponentConnector; import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.VButton; @@ -56,44 +56,38 @@ public class ButtonConnector extends AbstractComponentConnector implements super.init(); getWidget().addClickHandler(this); getWidget().client = getConnection(); - addStateChangeHandler("errorMessage", new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - if (null != getState().errorMessage) { - if (getWidget().errorIndicatorElement == null) { - getWidget().errorIndicatorElement = DOM.createSpan(); - getWidget().errorIndicatorElement - .setClassName("v-errorindicator"); - } - getWidget().wrapper.insertBefore( - getWidget().errorIndicatorElement, - getWidget().captionElement); - - } else if (getWidget().errorIndicatorElement != null) { - getWidget().wrapper - .removeChild(getWidget().errorIndicatorElement); - getWidget().errorIndicatorElement = null; - } - } - }); - - addStateChangeHandler("resources", new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - if (getWidget().icon != null) { - getWidget().wrapper.removeChild(getWidget().icon - .getElement()); - getWidget().icon = null; - } - Icon icon = getIcon(); - if (icon != null) { - getWidget().icon = icon; - icon.setAlternateText(getState().iconAltText); - getWidget().wrapper.insertBefore(icon.getElement(), - getWidget().captionElement); - } + } + + @OnStateChange("errorMessage") + void setErrorMessage() { + if (null != getState().errorMessage) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createSpan(); + getWidget().errorIndicatorElement + .setClassName("v-errorindicator"); } - }); + getWidget().wrapper.insertBefore(getWidget().errorIndicatorElement, + getWidget().captionElement); + + } else if (getWidget().errorIndicatorElement != null) { + getWidget().wrapper.removeChild(getWidget().errorIndicatorElement); + getWidget().errorIndicatorElement = null; + } + } + + @OnStateChange("resources") + void onResourceChange() { + if (getWidget().icon != null) { + getWidget().wrapper.removeChild(getWidget().icon.getElement()); + getWidget().icon = null; + } + Icon icon = getIcon(); + if (icon != null) { + getWidget().icon = icon; + icon.setAlternateText(getState().iconAltText); + getWidget().wrapper.insertBefore(icon.getElement(), + getWidget().captionElement); + } } @Override @@ -103,22 +97,27 @@ public class ButtonConnector extends AbstractComponentConnector implements focusHandlerRegistration); blurHandlerRegistration = EventHelper.updateBlurHandler(this, blurHandlerRegistration); + } - if (stateChangeEvent.hasPropertyChanged("caption") - || stateChangeEvent.hasPropertyChanged("htmlContentAllowed")) { - // Set text - if (getState().htmlContentAllowed) { - getWidget().setHtml(getState().caption); - } else { - getWidget().setText(getState().caption); - } + @OnStateChange({ "caption", "htmlContentAllowed" }) + void setCaption() { + String caption = getState().caption; + if (getState().htmlContentAllowed) { + getWidget().setHtml(caption); + } else { + getWidget().setText(caption); } + } - if (getWidget().icon != null - && stateChangeEvent.hasPropertyChanged("iconAltText")) { + @OnStateChange("iconAltText") + void setIconAltText() { + if (getWidget().icon != null) { getWidget().icon.setAlternateText(getState().iconAltText); } + } + @OnStateChange("clickShortcutKeyCode") + void setClickShortcut() { getWidget().clickShortcut = getState().clickShortcutKeyCode; } |