diff options
author | Johannes Dahlström <johannesd@vaadin.com> | 2012-06-13 17:46:09 +0300 |
---|---|---|
committer | Johannes Dahlström <johannesd@vaadin.com> | 2012-06-13 17:46:09 +0300 |
commit | 52d06a13b9e20cb752a21413e15adc250daa5e1c (patch) | |
tree | ac0e5878490d802960d9463354721ce1820b4aa8 | |
parent | de5fd8e0aefa0c43e2b0fe724102bca92eeaaf96 (diff) | |
parent | adcf03c1b5f05b38cda25819b732745c1a4859fc (diff) | |
download | vaadin-framework-52d06a13b9e20cb752a21413e15adc250daa5e1c.tar.gz vaadin-framework-52d06a13b9e20cb752a21413e15adc250daa5e1c.zip |
Merge remote-tracking branch 'origin/master'
Conflicts:
src/com/vaadin/terminal/gwt/server/DragAndDropService.java
41 files changed, 1907 insertions, 503 deletions
diff --git a/WebContent/statictestfiles/jsconnector.js b/WebContent/statictestfiles/jsconnector.js new file mode 100644 index 0000000000..db8a065e86 --- /dev/null +++ b/WebContent/statictestfiles/jsconnector.js @@ -0,0 +1,12 @@ +window.com_vaadin_tests_components_javascriptcomponent_BasicJavascriptComponent_ExampleWidget = function() { + var connector = this; + + var rootElement = connector.getWidgetElement(); + rootElement.innerHTML = 'Hello world!'; + rootElement.onclick = function() { + connector.getRpcProxyFunction("com.vaadin.tests.components.javascriptcomponent.BasicJavascriptComponent$ExampleClickRpc", "onClick")("message"); + } + connector.onStateChange = function() { + console.log('state change:', this.getState()); + } +}
\ No newline at end of file diff --git a/WebContent/statictestfiles/jsextension.js b/WebContent/statictestfiles/jsextension.js new file mode 100644 index 0000000000..df67db8927 --- /dev/null +++ b/WebContent/statictestfiles/jsextension.js @@ -0,0 +1,13 @@ +window.com_vaadin_tests_features_SimpleJavascriptExtensionTest_SimpleJavascriptExtension = function() { + var state = this.getState(); + var greetBack = this.getRpcProxyFunction('com.vaadin.tests.features.SimpleJavascriptExtensionTest$SimpleJavascriptExtensionServerRpc', 'greet'); + + this.registerRpc("com.vaadin.tests.features.SimpleJavascriptExtensionTest.SimpleJavascriptExtensionClientRpc", { + 'greet': function(greeting) { + var response = window.prompt(state.prefix + greeting); + if (response !== null) { + greetBack(response); + } + } + }); +}
\ No newline at end of file diff --git a/src/com/vaadin/annotations/LoadScripts.java b/src/com/vaadin/annotations/LoadScripts.java new file mode 100644 index 0000000000..f2b72407f7 --- /dev/null +++ b/src/com/vaadin/annotations/LoadScripts.java @@ -0,0 +1,16 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface LoadScripts { + public String[] value(); + +} diff --git a/src/com/vaadin/navigator/Navigator.java b/src/com/vaadin/navigator/Navigator.java index d0bba584f1..9d9acf9ed3 100644 --- a/src/com/vaadin/navigator/Navigator.java +++ b/src/com/vaadin/navigator/Navigator.java @@ -5,10 +5,11 @@ package com.vaadin.navigator; import java.io.Serializable; -import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; import com.vaadin.ui.CustomComponent; @@ -22,7 +23,7 @@ import com.vaadin.ui.Root.FragmentChangedListener; * The view switching can be based e.g. on URI fragments containing the view * name and parameters to the view. There are two types of parameters for views: * an optional parameter string that is included in the fragment (may be - * bookmarkable) and optional internal parameters that are not. + * bookmarkable). * * Views can be explicitly registered or dynamically generated and listening to * view changes is possible. @@ -38,19 +39,21 @@ public class Navigator implements Serializable { // TODO divert navigation e.g. if no permissions? Or just show another view // but keep URL? how best to intercept - // TODO investigate relationship to TouchKit navigation support + // TODO investigate relationship with TouchKit navigation support /** * Empty view component. */ public static class EmptyView extends CssLayout implements View { - public void init() { + /** + * Create minimally sized empty view. + */ + public EmptyView() { setWidth("0px"); setHeight("0px"); } - public void navigateTo(String fragmentParameters, - Object... internalParameters) { + public void navigateTo(String fragmentParameters) { // nothing to do } } @@ -75,7 +78,7 @@ public class Navigator implements Serializable { * root whose URI fragment to get and modify * @param navigator * {@link Navigator} to notify of fragment changes (using - * {@link Navigator#navigateTo(String, Object...)} + * {@link Navigator#navigateTo(String)} */ public UriFragmentManager(Root root, Navigator navigator) { this.root = root; @@ -130,106 +133,105 @@ public class Navigator implements Serializable { } /** - * View provider which uses a map from view name to pre-created and - * registered view instances. + * View provider which supports mapping a single view name to a single + * pre-initialized view instance. + * + * For most cases, ClassBasedViewProvider should be used instead of this. */ - public static class RegisteredViewProvider implements ViewProvider { + public static class StaticViewProvider implements ViewProvider { + private final String viewName; + private final View view; - private HashMap<String, View> viewNameToView = new HashMap<String, View>(); + /** + * Create a new view provider which returns a pre-created view instance. + * + * @param viewName + * name of the view (not null) + * @param view + * view instance to return (not null), reused on every + * request + */ + public StaticViewProvider(String viewName, View view) { + this.viewName = viewName; + this.view = view; + } public String getViewName(String viewAndParameters) { if (null == viewAndParameters) { return null; } - for (String viewName : viewNameToView.keySet()) { - if (viewAndParameters.equals(viewName) - || viewAndParameters.startsWith(viewName + "/")) { - return viewName; - } + if (viewAndParameters.startsWith(viewName)) { + return viewName; } return null; } public View getView(String viewName) { - return viewNameToView.get(viewName); - } - - /** - * Register a view for a view name. - * - * Registering another view with a name that is already registered - * overwrites the old registration. - * - * @param viewName - * String that identifies a view (not null nor empty string) - * @param view - * {@link View} instance (not null) - */ - public void addView(String viewName, View view) { - - // Check parameters - if (viewName == null || view == null || viewName.length() == 0) { - throw new IllegalArgumentException( - "view and viewName must be non-null and not empty"); + if (this.viewName.equals(viewName)) { + return view; } - - viewNameToView.put(viewName, view); + return null; } /** - * Remove view from navigator. + * Get the view name for this provider. * - * @param viewName - * name of the view to remove + * @return view name for this provider */ - public void removeView(String viewName) { - viewNameToView.remove(viewName); + public String getViewName() { + return viewName; } } /** - * View provider which uses a map from view name to the class to instantiate - * for the view. - * - * Views that have been created are cached and reused when the same view - * name is requested again. + * View provider which maps a single view name to a class to instantiate for + * the view. * * Note that the view class must be accessible by the class loader used by * the provider. This may require its visibility to be public. + * + * This class is primarily for internal use by {@link Navigator}. */ public static class ClassBasedViewProvider implements ViewProvider { - private HashMap<String, Class<? extends View>> viewNameToClass = new HashMap<String, Class<? extends View>>(); - private HashMap<Class<? extends View>, String> classToViewName = new HashMap<Class<? extends View>, String>(); + private final String viewName; + private final Class<? extends View> viewClass; + /** - * Already opened (cached) views that can be reopened or reused with new - * parameters. + * Create a new view provider which creates new view instances based on + * a view class. + * + * @param viewName + * name of the views to create (not null) + * @param viewClass + * class to instantiate when a view is requested (not null) */ - private HashMap<Class<? extends View>, View> classToView = new HashMap<Class<? extends View>, View>(); + public ClassBasedViewProvider(String viewName, + Class<? extends View> viewClass) { + if (null == viewName || null == viewClass) { + throw new IllegalArgumentException( + "View name and class should not be null"); + } + this.viewName = viewName; + this.viewClass = viewClass; + } public String getViewName(String viewAndParameters) { if (null == viewAndParameters) { return null; } - for (String viewName : viewNameToClass.keySet()) { - if (viewAndParameters.equals(viewName) - || viewAndParameters.startsWith(viewName + "/")) { - return viewName; - } + if (viewAndParameters.equals(viewName) + || viewAndParameters.startsWith(viewName + "/")) { + return viewName; } return null; } public View getView(String viewName) { - Class<? extends View> newViewClass = viewNameToClass.get(viewName); - if (null == newViewClass) { - return null; - } - if (!classToView.containsKey(newViewClass)) { + if (this.viewName.equals(viewName)) { try { - View view = newViewClass.newInstance(); - view.init(); - classToView.put(newViewClass, view); + View view = viewClass.newInstance(); + return view; } catch (InstantiationException e) { // TODO error handling throw new RuntimeException(e); @@ -238,95 +240,30 @@ public class Navigator implements Serializable { throw new RuntimeException(e); } } - // return already cached view - final View v = classToView.get(newViewClass); - return v; - } - - /** - * Register a view class for a view name. - * - * @param viewName - * String that identifies a view (not null nor empty string) - * @param viewClass - * Component class that implements Navigator.View interface - * (not null) - */ - public void addView(String viewName, Class<? extends View> viewClass) { - - // Check parameters - if (viewName == null || viewClass == null || viewName.length() == 0) { - throw new IllegalArgumentException( - "viewClass and viewName must be non-null and not empty"); - } - - if (!View.class.isAssignableFrom(viewClass)) { - throw new IllegalArgumentException( - "viewClass must implement Navigator.View"); - } - - if (viewNameToClass.containsKey(viewName)) { - if (viewNameToClass.get(viewName) == viewClass) { - return; - } - - throw new IllegalArgumentException(viewNameToClass - .get(viewName).getName() - + " is already mapped to '" - + viewName + "'"); - } - - if (classToViewName.containsKey(viewClass)) { - throw new IllegalArgumentException( - "Each view class can only be added to Navigator with one view name"); - } - - viewNameToClass.put(viewName, viewClass); - classToViewName.put(viewClass, viewName); - } - - /** - * Remove view from navigator. - * - * @param viewName - * name of the view to remove - */ - public void removeView(String viewName) { - Class<? extends View> c = viewNameToClass.get(viewName); - if (c != null) { - viewNameToClass.remove(viewName); - classToViewName.remove(c); - classToView.remove(c); - } + return null; } /** - * Get the view name for given view implementation class. + * Get the view name for this provider. * - * @param viewClass - * Class that implements the view. - * @return view name for which the view class is registered, null if - * none + * @return view name for this provider */ - public String getViewName(Class<? extends View> viewClass) { - return classToViewName.get(viewClass); + public String getViewName() { + return viewName; } /** - * Get the view class for given view name. + * Get the view class for this provider. * - * @param viewName - * view name to get view for - * @return View that corresponds to the name + * @return {@link View} class */ - public Class<? extends View> getViewClass(String viewName) { - return viewNameToClass.get(viewName); + public Class<? extends View> getViewClass() { + return viewClass; } } private final FragmentManager fragmentManager; private final ViewDisplay display; - private String mainViewName = null; private View currentView = null; private List<ViewChangeListener> listeners = new LinkedList<ViewChangeListener>(); private List<ViewProvider> providers = new LinkedList<ViewProvider>(); @@ -345,6 +282,19 @@ public class Navigator implements Serializable { } /** + * Create a navigator that is tracking the active view using URI fragments. + * By default, a {@link SimpleViewDisplay} is used and can be obtained using + * {@link #getDisplay()}. + * + * @param root + * whose URI fragments are used + */ + public Navigator(Root root) { + display = new SimpleViewDisplay(); + fragmentManager = new UriFragmentManager(root, this); + } + + /** * Create a navigator. * * When a custom fragment manager is not needed, use the constructor @@ -369,37 +319,43 @@ public class Navigator implements Serializable { * and (fragment) parameters. ViewProviders are used to find and create the * correct type of view. * - * If the view being left indicates it wants a confirmation for the + * If multiple providers return a matching view, the view with the longest + * name is selected. This way, e.g. hierarchies of subviews can be + * registered like "admin/", "admin/users", "admin/settings" and the longest + * match is used. + * + * If the view being deactivated indicates it wants a confirmation for the * navigation operation, the user is asked for the confirmation. * + * Registered {@link ViewChangeListener}s are called upon successful view + * change. + * * @param viewAndParameters * view name and parameters - * @param internalParameters - * parameters that are only passed when switching views - * explicitly on the server side, not bookmarkable */ - public void navigateTo(String viewAndParameters, - Object... internalParameters) { - if ("".equals(viewAndParameters)) { - viewAndParameters = mainViewName; - } + public void navigateTo(String viewAndParameters) { + String longestViewName = ""; + View viewWithLongestName = null; for (ViewProvider provider : providers) { String viewName = provider.getViewName(viewAndParameters); - if (null != viewName) { - String parameters = null; - if (viewAndParameters.length() > viewName.length() + 1) { - parameters = viewAndParameters - .substring(viewName.length() + 1); - } + if (null != viewName + && viewName.length() > longestViewName.length()) { View view = provider.getView(viewName); if (null != view) { - navigateTo(view, viewName, parameters, internalParameters); - // stop after a view is found - return; + longestViewName = viewName; + viewWithLongestName = view; } } } - // TODO if no view is found, use main view or do nothing? + if (viewWithLongestName != null) { + String parameters = null; + if (viewAndParameters.length() > longestViewName.length() + 1) { + parameters = viewAndParameters.substring(longestViewName + .length() + 1); + } + navigateTo(viewWithLongestName, longestViewName, parameters); + } + // TODO if no view is found, what to do? } /** @@ -415,14 +371,12 @@ public class Navigator implements Serializable { * (optional) name of the view or null not to set the fragment * @param fragmentParameters * parameters passed in the fragment for the view - * @param internalParameters - * parameters that are only passed when switching views - * explicitly on the server side, not bookmarkable */ protected void navigateTo(View view, String viewName, - String fragmentParameters, Object... internalParameters) { - if (!isViewChangeAllowed(currentView, view, viewName, - fragmentParameters, internalParameters)) { + String fragmentParameters) { + ViewChangeEvent event = new ViewChangeEvent(this, currentView, view, + viewName, fragmentParameters); + if (!isViewChangeAllowed(event)) { return; } @@ -436,15 +390,14 @@ public class Navigator implements Serializable { } } - view.navigateTo(fragmentParameters, internalParameters); - View previousView = currentView; + view.navigateTo(fragmentParameters); currentView = view; if (display != null) { display.showView(view); } - fireViewChange(previousView); + fireViewChange(event); } /** @@ -457,26 +410,14 @@ public class Navigator implements Serializable { * and save the parameters to re-initiate the navigation operation upon user * action. * - * @param previous - * the view being deactivated - * @param next - * the view being activated - * @param viewName - * name of the view being activated - * @param fragmentParameters - * parameters passed in the fragment for the view - * @param internalParameters - * parameters that are only passed on the server side, not - * bookmarkable + * @param event + * view change event (not null, view change not yet performed) * @return true if the view change should be allowed, false to silently * block the navigation operation */ - protected boolean isViewChangeAllowed(View previous, View next, - String viewName, String fragmentParameters, - Object[] internalParameters) { + protected boolean isViewChangeAllowed(ViewChangeEvent event) { for (ViewChangeListener l : listeners) { - if (!l.isViewChangeAllowed(previous, next, viewName, - fragmentParameters, internalParameters)) { + if (!l.isViewChangeAllowed(event)) { return false; } } @@ -494,43 +435,101 @@ public class Navigator implements Serializable { } /** - * Get the main view. + * Returns the ViewDisplay used by the navigator. Unless another display is + * specified, a {@link SimpleViewDisplay} (which is a {@link Component}) is + * used by default. * - * Main view is the default view shown to user when he opens application - * without specifying view name. + * @return current ViewDisplay + */ + public ViewDisplay getDisplay() { + return display; + } + + /** + * Fire an event when the current view has changed. * - * @return name of the main view. + * @param event + * view change event (not null) */ - public String getMainView() { - return mainViewName; + protected void fireViewChange(ViewChangeEvent event) { + for (ViewChangeListener l : listeners) { + l.navigatorViewChanged(event); + } } /** - * Set the main view. + * Register a static, pre-initialized view instance for a view name. * - * Main view is the default view shown to user when he opens application - * without specifying view name. If the {@link Navigator} does not have a - * current view, the main view is automatically selected when set. + * Registering another view with a name that is already registered + * overwrites the old registration of the same type. * - * @param mainViewName - * name of the main view. + * @param viewName + * String that identifies a view (not null nor empty string) + * @param view + * {@link View} instance (not null) */ - public void setMainView(String mainViewName) { - this.mainViewName = mainViewName; - // TODO should this be done? - if (currentView == null) { - navigateTo(mainViewName); + public void addView(String viewName, View view) { + + // Check parameters + if (viewName == null || view == null) { + throw new IllegalArgumentException( + "view and viewName must be non-null"); } + + removeView(viewName); + registerProvider(new StaticViewProvider(viewName, view)); } /** - * Fire an event when the current view has changed. + * Register for a view name a view class. + * + * Registering another view with a name that is already registered + * overwrites the old registration of the same type. + * + * A new view instance is created every time a view is requested. + * + * @param viewName + * String that identifies a view (not null nor empty string) + * @param viewClass + * {@link View} class to instantiate when a view is requested + * (not null) + */ + public void addView(String viewName, Class<? extends View> viewClass) { + + // Check parameters + if (viewName == null || viewClass == null) { + throw new IllegalArgumentException( + "view and viewClass must be non-null"); + } + + removeView(viewName); + registerProvider(new ClassBasedViewProvider(viewName, viewClass)); + } + + /** + * Remove view from navigator. + * + * This method only applies to views registered using + * {@link #addView(String, View)} or {@link #addView(String, Class)}. * - * @param previousView + * @param viewName + * name of the view to remove */ - protected void fireViewChange(View previousView) { - for (ViewChangeListener l : listeners) { - l.navigatorViewChanged(previousView, currentView); + public void removeView(String viewName) { + Iterator<ViewProvider> it = providers.iterator(); + while (it.hasNext()) { + ViewProvider provider = it.next(); + if (provider instanceof StaticViewProvider) { + StaticViewProvider staticProvider = (StaticViewProvider) provider; + if (staticProvider.getViewName().equals(viewName)) { + it.remove(); + } + } else if (provider instanceof ClassBasedViewProvider) { + ClassBasedViewProvider classBasedProvider = (ClassBasedViewProvider) provider; + if (classBasedProvider.getViewName().equals(viewName)) { + it.remove(); + } + } } } diff --git a/src/com/vaadin/navigator/View.java b/src/com/vaadin/navigator/View.java index efa13fbac2..4d135b4c0b 100644 --- a/src/com/vaadin/navigator/View.java +++ b/src/com/vaadin/navigator/View.java @@ -20,15 +20,6 @@ import com.vaadin.ui.Component; public interface View extends Serializable { /** - * Init view. - * - * Convenience method which navigator calls just before the view is rendered - * for the first time. This is called only once in the lifetime of each view - * instance. - */ - public void init(); - - /** * This view is navigated to. * * This method is always called before the view is shown on screen. If there @@ -40,11 +31,6 @@ public interface View extends Serializable { * @param fragmentParameters * parameters to the view or null if none given. This is the * string that appears e.g. in URI after "viewname/" - * @param internalParameters - * parameters given directly to - * {@link Navigator#navigateTo(String, Object...)}, not passed - * via the fragment and not preserved in bookmarks */ - public void navigateTo(String fragmentParameters, - Object... internalParameters); + public void navigateTo(String fragmentParameters); }
\ No newline at end of file diff --git a/src/com/vaadin/navigator/ViewChangeListener.java b/src/com/vaadin/navigator/ViewChangeListener.java index 9ac48e27cc..2eb34e6fcf 100644 --- a/src/com/vaadin/navigator/ViewChangeListener.java +++ b/src/com/vaadin/navigator/ViewChangeListener.java @@ -5,6 +5,7 @@ package com.vaadin.navigator; import java.io.Serializable; +import java.util.EventObject; /** * Interface for listening to View changes before and after they occur. @@ -18,6 +19,77 @@ import java.io.Serializable; public interface ViewChangeListener extends Serializable { /** + * Event received by the listener for attempted and executed view changes. + */ + public static class ViewChangeEvent extends EventObject { + private final View oldView; + private final View newView; + private final String viewName; + private final String fragmentParameters; + + /** + * Create a new view change event. + * + * @param navigator + * Navigator that triggered the event, not null + */ + public ViewChangeEvent(Navigator navigator, View oldView, View newView, + String viewName, String fragmentParameters) { + super(navigator); + this.oldView = oldView; + this.newView = newView; + this.viewName = viewName; + this.fragmentParameters = fragmentParameters; + } + + /** + * Returns the navigator that triggered this event. + * + * @return Navigator (not null) + */ + public Navigator getNavigator() { + return (Navigator) getSource(); + } + + /** + * Returns the view being deactivated. + * + * @return old View + */ + public View getOldView() { + return oldView; + } + + /** + * Returns the view being activated. + * + * @return new View + */ + public View getNewView() { + return newView; + } + + /** + * Returns the view name of the view being activated. + * + * @return view name of the new View + */ + public String getViewName() { + return viewName; + } + + /** + * Returns the parameters for the view being activated. + * + * @return fragment parameters (potentially bookmarkable) for the new + * view + */ + public String getFragmentParameters() { + return fragmentParameters; + } + } + + /** * Check whether changing the view is permissible. * * This method may also e.g. open a "save" dialog or question about the @@ -27,36 +99,20 @@ public interface ViewChangeListener extends Serializable { * know the view in question), it should return true. If any listener * returns false, the view change is not allowed. * - * TODO move to separate interface? - * - * @param previous - * view that is being deactivated - * @param next - * view that is being activated - * @param viewName - * name of the new view that is being activated - * @param fragmentParameters - * fragment parameters (potentially bookmarkable) for the new - * view - * @param internalParameters - * internal parameters for the new view, not visible in the - * browser + * @param event + * view change event * @return true if the view change should be allowed or this listener does * not care about the view change, false to block the change */ - public boolean isViewChangeAllowed(View previous, View next, - String viewName, String fragmentParameters, - Object... internalParameters); + public boolean isViewChangeAllowed(ViewChangeEvent event); /** * Invoked after the view has changed. Be careful for deadlocks if you * decide to change the view again in the listener. * - * @param previous - * Preview view before the change. - * @param current - * New view after the change. + * @param event + * view change event */ - public void navigatorViewChanged(View previous, View current); + public void navigatorViewChanged(ViewChangeEvent event); }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/AbstractClientConnector.java b/src/com/vaadin/terminal/AbstractClientConnector.java index 136ed702fc..a2bf4a0be8 100644 --- a/src/com/vaadin/terminal/AbstractClientConnector.java +++ b/src/com/vaadin/terminal/AbstractClientConnector.java @@ -334,7 +334,7 @@ public abstract class AbstractClientConnector implements ClientConnector { public void requestRepaintAll() { requestRepaint(); - for (ClientConnector connector : getAllChildrenIteratable(this)) { + for (ClientConnector connector : getAllChildrenIterable(this)) { connector.requestRepaintAll(); } } @@ -371,7 +371,7 @@ public abstract class AbstractClientConnector implements ClientConnector { } } - public static Iterable<ClientConnector> getAllChildrenIteratable( + public static Iterable<ClientConnector> getAllChildrenIterable( final ClientConnector connector) { return new AllChildrenIterable(connector); } @@ -442,13 +442,13 @@ public abstract class AbstractClientConnector implements ClientConnector { public void attach() { requestRepaint(); - for (ClientConnector connector : getAllChildrenIteratable(this)) { + for (ClientConnector connector : getAllChildrenIterable(this)) { connector.attach(); } } public void detach() { - for (ClientConnector connector : getAllChildrenIteratable(this)) { + for (ClientConnector connector : getAllChildrenIterable(this)) { connector.detach(); } } diff --git a/src/com/vaadin/terminal/AbstractExtension.java b/src/com/vaadin/terminal/AbstractExtension.java index 35123bc488..353db85a18 100644 --- a/src/com/vaadin/terminal/AbstractExtension.java +++ b/src/com/vaadin/terminal/AbstractExtension.java @@ -13,6 +13,10 @@ public abstract class AbstractExtension extends AbstractClientConnector return ClientConnector.class; } + protected void attachTo(AbstractClientConnector parent) { + parent.addExtension(this); + } + @Override public void setParent(ClientConnector parent) { Class<? extends ClientConnector> acceptedParentType = getAcceptedParentType(); diff --git a/src/com/vaadin/terminal/AbstractJavascriptExtension.java b/src/com/vaadin/terminal/AbstractJavascriptExtension.java new file mode 100644 index 0000000000..e741e2af1e --- /dev/null +++ b/src/com/vaadin/terminal/AbstractJavascriptExtension.java @@ -0,0 +1,20 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import com.vaadin.ui.JavascriptCallback; + +public class AbstractJavascriptExtension extends AbstractExtension { + private JavascriptRpcHelper rpcHelper = new JavascriptRpcHelper(this); + + protected void registerCallback(String functionName, + JavascriptCallback javascriptCallback) { + rpcHelper.registerCallback(functionName, javascriptCallback); + } + + protected void invokeCallback(String name, Object... arguments) { + rpcHelper.invokeCallback(name, arguments); + } +} diff --git a/src/com/vaadin/terminal/JavascriptRpcHelper.java b/src/com/vaadin/terminal/JavascriptRpcHelper.java new file mode 100644 index 0000000000..b566462833 --- /dev/null +++ b/src/com/vaadin/terminal/JavascriptRpcHelper.java @@ -0,0 +1,61 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.JavascriptCallback; +import com.vaadin.ui.JavascriptManager.JavascriptCallbackRpc; + +public class JavascriptRpcHelper { + + private static final Method CALL_METHOD = ReflectTools.findMethod( + JavascriptCallbackRpc.class, "call", String.class, JSONArray.class); + private AbstractClientConnector connector; + + private Map<String, JavascriptCallback> callbacks = new HashMap<String, JavascriptCallback>(); + private JavascriptCallbackRpc javascriptCallbackRpc; + + public JavascriptRpcHelper(AbstractClientConnector connector) { + this.connector = connector; + } + + public void registerCallback(String functionName, + JavascriptCallback javascriptCallback) { + callbacks.put(functionName, javascriptCallback); + ensureRpc(); + } + + private void ensureRpc() { + if (javascriptCallbackRpc == null) { + javascriptCallbackRpc = new JavascriptCallbackRpc() { + public void call(String name, JSONArray arguments) { + JavascriptCallback callback = callbacks.get(name); + try { + callback.call(arguments); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + }; + connector.registerRpc(javascriptCallbackRpc); + } + } + + public void invokeCallback(String name, Object... arguments) { + JSONArray args = new JSONArray(Arrays.asList(arguments)); + connector.addMethodInvocationToQueue( + JavascriptCallbackRpc.class.getName(), CALL_METHOD, + new Object[] { name, args }); + connector.requestRepaint(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index e8013ccc72..7d11c6245a 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -40,6 +40,7 @@ import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConfiguration.ErrorMessage; +import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; import com.vaadin.terminal.gwt.client.communication.JsonDecoder; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; import com.vaadin.terminal.gwt.client.communication.MethodInvocation; @@ -1438,12 +1439,19 @@ public class ApplicationConnection { .getConnector(connectorId); if (null != connector) { - JSONObject stateDataAndType = new JSONObject( + JSONObject stateJson = new JSONObject( states.getJavaScriptObject(connectorId)); + if (connector instanceof HasJavascriptConnectorHelper) { + ((HasJavascriptConnectorHelper) connector) + .getJavascriptConnectorHelper() + .setNativeState( + stateJson.getJavaScriptObject()); + } + SharedState state = connector.getState(); JsonDecoder.decodeValue(new Type(state.getClass() - .getName(), null), stateDataAndType, state, + .getName(), null), stateJson, state, ApplicationConnection.this); StateChangeEvent event = GWT @@ -1584,11 +1592,8 @@ public class ApplicationConnection { for (int i = 0; i < rpcLength; i++) { try { JSONArray rpcCall = (JSONArray) rpcCalls.get(i); - MethodInvocation invocation = parseMethodInvocation(rpcCall); - VConsole.log("Server to client RPC call: " - + invocation); - rpcManager.applyInvocation(invocation, - getConnectorMap()); + rpcManager.parseAndApplyInvocation(rpcCall, + ApplicationConnection.this); } catch (final Throwable e) { VConsole.error(e); } @@ -1601,26 +1606,6 @@ public class ApplicationConnection { ApplicationConfiguration.runWhenWidgetsLoaded(c); } - private MethodInvocation parseMethodInvocation(JSONArray rpcCall) { - String connectorId = ((JSONString) rpcCall.get(0)).stringValue(); - String interfaceName = ((JSONString) rpcCall.get(1)).stringValue(); - String methodName = ((JSONString) rpcCall.get(2)).stringValue(); - JSONArray parametersJson = (JSONArray) rpcCall.get(3); - - MethodInvocation methodInvocation = new MethodInvocation(connectorId, - interfaceName, methodName); - Type[] parameterTypes = rpcManager.getParameterTypes(methodInvocation); - - Object[] parameters = new Object[parametersJson.size()]; - for (int j = 0; j < parametersJson.size(); ++j) { - parameters[j] = JsonDecoder.decodeValue(parameterTypes[j], - parametersJson.get(j), null, this); - } - - methodInvocation.setParameters(parameters); - return methodInvocation; - } - // Redirect browser, null reloads current page private static native void redirect(String url) /*-{ @@ -2169,8 +2154,8 @@ public class ApplicationConnection { private ServerConnector createAndRegisterConnector(String connectorId, int connectorType) { // Create and register a new connector with the given type - ServerConnector p = widgetSet - .createConnector(connectorType, configuration); + ServerConnector p = widgetSet.createConnector(connectorType, + configuration); connectorMap.registerConnector(connectorId, p); p.doInit(connectorId, this); diff --git a/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java new file mode 100644 index 0000000000..ab0e62222c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java @@ -0,0 +1,205 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.ArrayList; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.json.client.JSONArray; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; + +public class JavascriptConnectorHelper { + + private final ServerConnector connector; + private final JavaScriptObject nativeState = JavaScriptObject + .createObject(); + private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); + + private JavaScriptObject connectorWrapper; + private int tag; + + public JavascriptConnectorHelper(ServerConnector connector) { + this.connector = connector; + } + + public boolean init() { + ApplicationConfiguration conf = connector.getConnection() + .getConfiguration(); + ArrayList<String> attemptedNames = new ArrayList<String>(); + Integer tag = Integer.valueOf(this.tag); + while (tag != null) { + String serverSideClassName = conf.getServerSideClassNameForTag(tag); + String initFunctionName = serverSideClassName + .replaceAll("\\.", "_"); + if (tryInitJs(initFunctionName, getConnectorWrapper())) { + VConsole.log("Javascript connector initialized using " + + initFunctionName); + return true; + } else { + VConsole.log("No javascript function " + initFunctionName + + " found"); + attemptedNames.add(initFunctionName); + tag = conf.getParentTag(tag.intValue()); + } + } + VConsole.log("No javascript init for connector not found"); + showInitProblem(attemptedNames); + return false; + } + + protected void showInitProblem(ArrayList<String> attemptedNames) { + // Default does nothing + } + + private static native boolean tryInitJs(String initFunctionName, + JavaScriptObject connectorWrapper) + /*-{ + if (typeof $wnd[initFunctionName] == 'function') { + $wnd[initFunctionName].apply(connectorWrapper); + return true; + } else { + return false; + } + }-*/; + + private JavaScriptObject getConnectorWrapper() { + if (connectorWrapper == null) { + connectorWrapper = createConnectorWrapper(); + } + + return connectorWrapper; + } + + protected JavaScriptObject createConnectorWrapper() { + return createConnectorWrapper(this, nativeState, rpcMap, + connector.getConnectorId()); + } + + public void fireNativeStateChange() { + fireNativeStateChange(getConnectorWrapper()); + } + + private static native void fireNativeStateChange( + JavaScriptObject connectorWrapper) + /*-{ + if (typeof connectorWrapper.onStateChange == 'function') { + connectorWrapper.onStateChange(); + } + }-*/; + + private static native JavaScriptObject createConnectorWrapper( + JavascriptConnectorHelper h, JavaScriptObject nativeState, + JavaScriptObject registeredRpc, String connectorId) + /*-{ + return { + 'getConnectorId': function() { + return connectorId; + }, + 'getState': function() { + return nativeState; + }, + 'getRpcProxyFunction': function(iface, method) { + return $entry(function() { + h.@com.vaadin.terminal.gwt.client.JavascriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); + }); + }, + 'getCallback': function(name) { + return $entry(function() { + var args = [name, Array.prototype.slice.call(arguments, 0)]; + var iface = "com.vaadin.ui.JavascriptManager$JavascriptCallbackRpc"; + var method = "call"; + h.@com.vaadin.terminal.gwt.client.JavascriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, args); + }); + }, + 'registerCallback': function(name, callback) { + //TODO maintain separate map + if (!registeredRpc[name]) { + registeredRpc[name] = []; + } + registeredRpc[name].push(callback); + }, + 'registerRpc': function(iface, rpcHandler) { + if (!registeredRpc[iface]) { + registeredRpc[iface] = []; + } + registeredRpc[iface].push(rpcHandler); + }, + }; + }-*/; + + private void fireRpc(String iface, String method, + JsArray<JavaScriptObject> arguments) { + JSONArray argumentsArray = new JSONArray(arguments); + Object[] parameters = new Object[arguments.length()]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = argumentsArray.get(i); + } + connector.getConnection().addMethodInvocationToQueue( + new MethodInvocation(connector.getConnectorId(), iface, method, + parameters), true); + } + + public void setNativeState(JavaScriptObject state) { + updateNativeState(nativeState, state); + } + + private static native void updateNativeState(JavaScriptObject state, + JavaScriptObject input) + /*-{ + // Copy all fields to existing state object + for(var key in state) { + if (state.hasOwnProperty(key)) { + delete state[key]; + } + } + + for(var key in input) { + if (input.hasOwnProperty(key)) { + state[key] = input[key]; + } + } + }-*/; + + public Object[] decodeRpcParameters(JSONArray parametersJson) { + return new Object[] { parametersJson.getJavaScriptObject() }; + } + + public void setTag(int tag) { + this.tag = tag; + } + + public void invokeJsRpc(MethodInvocation invocation, + JSONArray parametersJson) { + if ("com.vaadin.ui.JavascriptManager$JavascriptCallbackRpc" + .equals(invocation.getInterfaceName()) + && "call".equals(invocation.getMethodName())) { + invokeJsRpc(rpcMap, parametersJson.get(0).isString().stringValue(), + null, parametersJson.get(1).isArray().getJavaScriptObject()); + } else { + invokeJsRpc(rpcMap, invocation.getInterfaceName(), + invocation.getMethodName(), + parametersJson.getJavaScriptObject()); + } + } + + private static native void invokeJsRpc(JavaScriptObject rpcMap, + String interfaceName, String methodName, JavaScriptObject parameters) + /*-{ + var targets = rpcMap[interfaceName]; + if (!targets) { + return; + } + for(var i = 0; i < targets.length; i++) { + var target = targets[i]; + if (methodName === null && typeof target === 'function') { + target.apply($wnd, parameters); + } else { + target[methodName].apply(target, parameters); + } + } + }-*/; + +} diff --git a/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java b/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java new file mode 100644 index 0000000000..6c098a52f6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java @@ -0,0 +1,33 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import com.vaadin.terminal.AbstractJavascriptExtension; +import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; + +@Connect(AbstractJavascriptExtension.class) +public class JavascriptExtension extends AbstractConnector implements + HasJavascriptConnectorHelper { + private final JavascriptConnectorHelper helper = new JavascriptConnectorHelper( + this); + + @Override + protected void init() { + helper.init(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + helper.fireNativeStateChange(); + } + + public JavascriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/WidgetSet.java b/src/com/vaadin/terminal/gwt/client/WidgetSet.java index d7cc2df00d..ecbfb0ecc9 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetSet.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetSet.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.client; import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class WidgetSet { @@ -52,7 +53,12 @@ public class WidgetSet { /* * let the auto generated code instantiate this type */ - return widgetMap.instantiate(classType); + ServerConnector connector = widgetMap.instantiate(classType); + if (connector instanceof HasJavascriptConnectorHelper) { + ((HasJavascriptConnectorHelper) connector) + .getJavascriptConnectorHelper().setTag(tag); + } + return connector; } } diff --git a/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java new file mode 100644 index 0000000000..74bc75da66 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import com.vaadin.terminal.gwt.client.JavascriptConnectorHelper; + +public interface HasJavascriptConnectorHelper { + public JavascriptConnectorHelper getJavascriptConnectorHelper(); +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index 657f44896d..cb7dbe5e72 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -59,6 +59,8 @@ public class JsonEncoder { boolean restrictToInternalTypes, ApplicationConnection connection) { if (null == value) { return JSONNull.getInstance(); + } else if (value instanceof JSONValue) { + return (JSONValue) value; } else if (value instanceof String[]) { String[] array = (String[]) value; JSONArray jsonArray = new JSONArray(); diff --git a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java index 1d3447687d..e0ffb40125 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java +++ b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java @@ -9,8 +9,12 @@ import java.util.HashMap; import java.util.Map; import com.google.gwt.core.client.GWT; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONString; +import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.VConsole; /** * Client side RPC manager that can invoke methods based on RPC calls received @@ -41,20 +45,10 @@ public class RpcManager { * * @param invocation * method to invoke - * @param connectorMap - * mapper used to find Connector for the method call and any - * connectors referenced in parameters */ public void applyInvocation(MethodInvocation invocation, - ConnectorMap connectorMap) { - ServerConnector connector = connectorMap.getConnector(invocation - .getConnectorId()); + ServerConnector connector) { String signature = getSignature(invocation); - if (connector == null) { - throw new IllegalStateException("Target connector (" - + invocation.getConnectorId() + ") not found for RCC to " - + signature); - } RpcMethod rpcMethod = getRpcMethod(signature); Collection<ClientRpc> implementations = connector @@ -82,4 +76,47 @@ public class RpcManager { return getRpcMethod(getSignature(invocation)).getParameterTypes(); } + public void parseAndApplyInvocation(JSONArray rpcCall, + ApplicationConnection connection) { + ConnectorMap connectorMap = ConnectorMap.get(connection); + + String connectorId = ((JSONString) rpcCall.get(0)).stringValue(); + String interfaceName = ((JSONString) rpcCall.get(1)).stringValue(); + String methodName = ((JSONString) rpcCall.get(2)).stringValue(); + JSONArray parametersJson = (JSONArray) rpcCall.get(3); + + ServerConnector connector = connectorMap.getConnector(connectorId); + + MethodInvocation invocation = new MethodInvocation(connectorId, + interfaceName, methodName); + if (connector instanceof HasJavascriptConnectorHelper) { + ((HasJavascriptConnectorHelper) connector) + .getJavascriptConnectorHelper().invokeJsRpc(invocation, + parametersJson); + } else { + if (connector == null) { + throw new IllegalStateException("Target connector (" + + connector + ") not found for RCC to " + + getSignature(invocation)); + } + + parseMethodParameters(invocation, parametersJson, connection); + VConsole.log("Server to client RPC call: " + invocation); + applyInvocation(invocation, connector); + } + } + + private void parseMethodParameters(MethodInvocation methodInvocation, + JSONArray parametersJson, ApplicationConnection connection) { + Type[] parameterTypes = getParameterTypes(methodInvocation); + + Object[] parameters = new Object[parametersJson.size()]; + for (int j = 0; j < parametersJson.size(); ++j) { + parameters[j] = JsonDecoder.decodeValue(parameterTypes[j], + parametersJson.get(j), null, connection); + } + + methodInvocation.setParameters(parameters); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java new file mode 100644 index 0000000000..979a6d22c4 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java @@ -0,0 +1,78 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import java.util.HashSet; +import java.util.Set; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.json.client.JSONArray; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.JavascriptManager; + +@Connect(JavascriptManager.class) +public class JavascriptManagerConnector extends AbstractConnector { + private Set<String> currentNames = new HashSet<String>(); + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + Set<String> newNames = getState().getNames(); + + // Current names now only contains orphan callbacks + currentNames.removeAll(newNames); + + for (String name : currentNames) { + removeCallback(name); + } + + currentNames = new HashSet<String>(newNames); + for (String name : newNames) { + addCallback(name); + } + } + + // TODO Ensure we don't overwrite anything (important) in $wnd + private native void addCallback(String name) + /*-{ + var m = this; + $wnd[name] = $entry(function() { + //Must make a copy because arguments is an array-like object (not instanceof Array), causing suboptimal JSON encoding + var args = Array.prototype.slice.call(arguments, 0); + m.@com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavascriptManagerConnector::sendRpc(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); + }); + }-*/; + + // TODO only remove what we actually added + private native void removeCallback(String name) + /*-{ + delete $wnd[name]; + }-*/; + + public void sendRpc(String name, JsArray<JavaScriptObject> arguments) { + Object[] parameters = new Object[] { name, new JSONArray(arguments) }; + + /* + * Must invoke manually as the RPC interface can't be used in GWT + * because of the JSONArray parameter + */ + getConnection() + .addMethodInvocationToQueue( + new MethodInvocation( + getConnectorId(), + "com.vaadin.ui.JavascriptManager$JavascriptCallbackRpc", + "call", parameters), true); + } + + @Override + public JavascriptManagerState getState() { + return (JavascriptManagerState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java new file mode 100644 index 0000000000..77794ffdca --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java @@ -0,0 +1,22 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import java.util.HashSet; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.communication.SharedState; + +public class JavascriptManagerState extends SharedState { + private Set<String> names = new HashSet<String>(); + + public Set<String> getNames() { + return names; + } + + public void setNames(Set<String> names) { + this.names = names; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java new file mode 100644 index 0000000000..57e65e91c6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java @@ -0,0 +1,60 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.user.client.Element; +import com.vaadin.terminal.gwt.client.JavascriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.ui.AbstractJavascriptComponent; + +@Connect(AbstractJavascriptComponent.class) +public class JavascriptComponentConnector extends AbstractComponentConnector + implements HasJavascriptConnectorHelper { + + private final JavascriptConnectorHelper helper = new JavascriptConnectorHelper( + this) { + @Override + protected void showInitProblem( + java.util.ArrayList<String> attemptedNames) { + getWidget().showNoInitFound(attemptedNames); + } + + @Override + protected JavaScriptObject createConnectorWrapper() { + JavaScriptObject connectorWrapper = super.createConnectorWrapper(); + addGetWidgetElement(connectorWrapper, getWidget().getElement()); + return connectorWrapper; + } + }; + + @Override + protected void init() { + helper.init(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + helper.fireNativeStateChange(); + } + + private static native void addGetWidgetElement( + JavaScriptObject connectorWrapper, Element element) + /*-{ + connectorWrapper.getWidgetElement = function() { + return element; + }; + }-*/; + + @Override + public JavascriptWidget getWidget() { + return (JavascriptWidget) super.getWidget(); + } + + public JavascriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java b/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java new file mode 100644 index 0000000000..93a4417b1c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.ArrayList; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.user.client.ui.Widget; + +public class JavascriptWidget extends Widget { + public JavascriptWidget() { + setElement(Document.get().createDivElement()); + } + + public void showNoInitFound(ArrayList<String> attemptedNames) { + String message = "Could not initialize JavascriptConnector because no javascript init function was found. Make sure one of these functions are defined: <ul>"; + for (String name : attemptedNames) { + message += "<li>" + name + "</li>"; + } + message += "</ul>"; + + getElement().setInnerHTML(message); + } +} diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index b77dc961d6..86bbb4e80b 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -899,7 +899,7 @@ public abstract class AbstractCommunicationManager implements Serializable { JSONArray children = new JSONArray(); for (ClientConnector child : AbstractClientConnector - .getAllChildrenIteratable(connector)) { + .getAllChildrenIterable(connector)) { if (isVisible(child)) { children.put(child.getConnectorId()); } diff --git a/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java b/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java index 8a0c700121..ae1fadd91b 100644 --- a/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java +++ b/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java @@ -9,6 +9,10 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Serializable; import java.io.Writer; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; @@ -16,6 +20,7 @@ import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; import com.vaadin.RootRequiresMoreInformationException; import com.vaadin.Version; +import com.vaadin.annotations.LoadScripts; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; import com.vaadin.terminal.DeploymentConfiguration; @@ -467,15 +472,15 @@ public abstract class BootstrapHandler implements RequestHandler { page.write("<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\"/>\n"); page.write("<style type=\"text/css\">" - + "html, body {height:100%;margin:0;}</style>"); + + "html, body {height:100%;margin:0;}</style>\n"); // Add favicon links if (themeName != null) { String themeUri = getThemeUri(context, themeName); page.write("<link rel=\"shortcut icon\" type=\"image/vnd.microsoft.icon\" href=\"" - + themeUri + "/favicon.ico\" />"); + + themeUri + "/favicon.ico\" />\n"); page.write("<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" href=\"" - + themeUri + "/favicon.ico\" />"); + + themeUri + "/favicon.ico\" />\n"); } Root root = context.getRoot(); @@ -484,7 +489,51 @@ public abstract class BootstrapHandler implements RequestHandler { page.write("<title>" + AbstractApplicationServlet.safeEscapeForHtml(title) - + "</title>"); + + "</title>\n"); + + if (root != null) { + List<LoadScripts> loadScriptsAnnotations = getAnnotationsFor( + root.getClass(), LoadScripts.class); + Collections.reverse(loadScriptsAnnotations); + // Begin from the end as a class might requests scripts that depend + // on script loaded by a super class + for (int i = loadScriptsAnnotations.size() - 1; i >= 0; i--) { + LoadScripts loadScripts = loadScriptsAnnotations.get(i); + String[] value = loadScripts.value(); + if (value != null) { + for (String script : value) { + page.write("<script type='text/javascript' src='"); + page.write(script); + page.write("'></script>\n"); + } + } + } + + } + } + + private static <T extends Annotation> List<T> getAnnotationsFor( + Class<?> type, Class<T> annotationType) { + List<T> list = new ArrayList<T>(); + // Find from the class hierarchy + Class<?> currentType = type; + while (currentType != Object.class) { + T annotation = currentType.getAnnotation(annotationType); + if (annotation != null) { + list.add(annotation); + } + currentType = currentType.getSuperclass(); + } + + // Find from an implemented interface + for (Class<?> iface : type.getInterfaces()) { + T annotation = iface.getAnnotation(annotationType); + if (annotation != null) { + list.add(annotation); + } + } + + return list; } /** diff --git a/src/com/vaadin/terminal/gwt/server/ClientConnector.java b/src/com/vaadin/terminal/gwt/server/ClientConnector.java index 6830aa8e14..359e112738 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientConnector.java +++ b/src/com/vaadin/terminal/gwt/server/ClientConnector.java @@ -139,5 +139,5 @@ public interface ClientConnector extends Connector, RpcTarget { public Iterator<Extension> getExtensionIterator(); - public void removeExtension(Extension feature); + public void removeExtension(Extension extension); } diff --git a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java index f5bef0b568..101e7f0cb3 100644 --- a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java +++ b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java @@ -296,7 +296,7 @@ public class DragAndDropService implements VariableOwner, ClientConnector { } @Override - public void removeExtension(Extension feature) { + public void removeExtension(Extension extension) { // TODO Auto-generated method stub } diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index aa61c68338..71e4727164 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -123,6 +123,9 @@ public class JsonCodec implements Serializable { // Try to decode object using fields if (value == JSONObject.NULL) { return null; + } else if (targetType == JSONObject.class + || targetType == JSONArray.class) { + return value; } else { return decodeObject(targetType, (JSONObject) value, application); } @@ -400,7 +403,7 @@ public class JsonCodec implements Serializable { boolean restrictToInternalTypes, JSONArray jsonArray, Application application) throws JSONException { HashSet<Object> set = new HashSet<Object>(); - set.addAll(decodeList(List.class, restrictToInternalTypes, jsonArray, + set.addAll(decodeList(targetType, restrictToInternalTypes, jsonArray, application)); return set; } @@ -514,6 +517,8 @@ public class JsonCodec implements Serializable { return connector.getConnectorId(); } else if (value instanceof Enum) { return encodeEnum((Enum<?>) value, application); + } else if (value instanceof JSONArray || value instanceof JSONObject) { + return value; } else { // Any object that we do not know how to encode we encode by looping // through fields diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java index 6f278f7797..95565c4379 100644 --- a/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcMethodInvocation.java @@ -79,6 +79,12 @@ public class ServerRpcMethodInvocation extends MethodInvocation { } } + if (invocationMethod == null) { + throw new IllegalStateException("Can't find method " + methodName + + " with " + parameterCount + " parameters in " + + targetType.getName()); + } + return invocationMethod; } diff --git a/src/com/vaadin/ui/AbstractJavascriptComponent.java b/src/com/vaadin/ui/AbstractJavascriptComponent.java new file mode 100644 index 0000000000..0a26c10239 --- /dev/null +++ b/src/com/vaadin/ui/AbstractJavascriptComponent.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import com.vaadin.terminal.JavascriptRpcHelper; + +public class AbstractJavascriptComponent extends AbstractComponent { + private JavascriptRpcHelper rpcHelper = new JavascriptRpcHelper(this); + + protected void registerCallback(String functionName, + JavascriptCallback javascriptCallback) { + rpcHelper.registerCallback(functionName, javascriptCallback); + } + + protected void invokeCallback(String name, Object... arguments) { + rpcHelper.invokeCallback(name, arguments); + } +} diff --git a/src/com/vaadin/ui/DirtyConnectorTracker.java b/src/com/vaadin/ui/DirtyConnectorTracker.java index dae334fe4c..ae47d87eae 100644 --- a/src/com/vaadin/ui/DirtyConnectorTracker.java +++ b/src/com/vaadin/ui/DirtyConnectorTracker.java @@ -101,7 +101,7 @@ public class DirtyConnectorTracker implements Serializable { } markDirty(c); for (ClientConnector child : AbstractClientConnector - .getAllChildrenIteratable(c)) { + .getAllChildrenIterable(c)) { markConnectorsDirtyRecursively(child); } } diff --git a/src/com/vaadin/ui/JavascriptCallback.java b/src/com/vaadin/ui/JavascriptCallback.java new file mode 100644 index 0000000000..89700b3faf --- /dev/null +++ b/src/com/vaadin/ui/JavascriptCallback.java @@ -0,0 +1,14 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.io.Serializable; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; + +public interface JavascriptCallback extends Serializable { + public void call(JSONArray arguments) throws JSONException; +} diff --git a/src/com/vaadin/ui/JavascriptManager.java b/src/com/vaadin/ui/JavascriptManager.java new file mode 100644 index 0000000000..72295dce2b --- /dev/null +++ b/src/com/vaadin/ui/JavascriptManager.java @@ -0,0 +1,58 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.AbstractExtension; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavascriptManagerState; + +public class JavascriptManager extends AbstractExtension { + private Map<String, JavascriptCallback> callbacks = new HashMap<String, JavascriptCallback>(); + + // Can not be defined in client package as this JSONArray is not available + // in GWT + public interface JavascriptCallbackRpc extends ServerRpc { + public void call(String name, JSONArray arguments); + } + + public JavascriptManager() { + registerRpc(new JavascriptCallbackRpc() { + public void call(String name, JSONArray arguments) { + JavascriptCallback callback = callbacks.get(name); + // TODO handle situation if name is not registered + try { + callback.call(arguments); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + }); + } + + @Override + public JavascriptManagerState getState() { + return (JavascriptManagerState) super.getState(); + } + + public void addCallback(String name, JavascriptCallback javascriptCallback) { + callbacks.put(name, javascriptCallback); + if (getState().getNames().add(name)) { + requestRepaint(); + } + } + + public void removeCallback(String name) { + callbacks.remove(name); + if (getState().getNames().remove(name)) { + requestRepaint(); + } + } + +} diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java index 8792bf1912..9814084cbc 100644 --- a/src/com/vaadin/ui/Root.java +++ b/src/com/vaadin/ui/Root.java @@ -408,6 +408,8 @@ public abstract class Root extends AbstractComponentContainer implements private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( this); + private JavascriptManager javascriptManager; + private RootServerRpc rpc = new RootServerRpc() { public void click(MouseEventDetails mouseDetails) { fireEvent(new ClickEvent(Root.this, mouseDetails)); @@ -1590,4 +1592,14 @@ public abstract class Root extends AbstractComponentContainer implements return dirtyConnectorTracker; } + public JavascriptManager getJavascriptManager() { + if (javascriptManager == null) { + // Create and attach on first use + javascriptManager = new JavascriptManager(); + addExtension(javascriptManager); + } + + return javascriptManager; + } + } diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index 3b12c097de..a83c9bd87a 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -1653,6 +1653,14 @@ public class Table extends AbstractSelect implements Action.Container, super.requestRepaint(); } + @Override + public void requestRepaintAll() { + super.requestRepaintAll(); + + // Avoid sending a partial repaint (#8714) + refreshRowCache(); + } + private void removeRowsFromCacheAndFillBottom(int firstIndex, int rows) { int totalCachedRows = pageBuffer[CELL_ITEMID].length; int totalRows = size(); diff --git a/tests/server-side/com/vaadin/tests/server/navigator/ClassBasedViewProviderTest.java b/tests/server-side/com/vaadin/tests/server/navigator/ClassBasedViewProviderTest.java index 818229ab2f..fc0d1b60a0 100644 --- a/tests/server-side/com/vaadin/tests/server/navigator/ClassBasedViewProviderTest.java +++ b/tests/server-side/com/vaadin/tests/server/navigator/ClassBasedViewProviderTest.java @@ -12,17 +12,10 @@ import com.vaadin.ui.Label; public class ClassBasedViewProviderTest extends TestCase { - private ClassBasedViewProvider provider; - public static class TestView extends Label implements View { - public boolean initialized = false; public String parameters = null; - public void init() { - initialized = true; - } - - public void navigateTo(String parameters, Object... internalParameters) { + public void navigateTo(String parameters) { this.parameters = parameters; } @@ -32,86 +25,68 @@ public class ClassBasedViewProviderTest extends TestCase { } - @Override - protected void setUp() throws Exception { - super.setUp(); - provider = new ClassBasedViewProvider(); - } - - public void testAddViewWithNullName() throws Exception { + public void testCreateProviderWithNullName() throws Exception { try { - provider.addView(null, TestView.class); - fail("Should not be able to add view with null name"); + new ClassBasedViewProvider(null, TestView.class); + fail("Should not be able to create view provider with null name"); } catch (IllegalArgumentException e) { } } - public void testAddViewWithEmptyStringName() throws Exception { - try { - provider.addView("", TestView.class); - fail("Should not be able to add view with empty name"); - } catch (IllegalArgumentException e) { - } + public void testCreateProviderWithEmptyStringName() throws Exception { + new ClassBasedViewProvider("", TestView.class); } - public void testAddViewNull() throws Exception { + public void testCreateProviderNullViewClass() throws Exception { try { - provider.addView("test", null); - fail("Should not be able to add null view"); + new ClassBasedViewProvider("test", null); + fail("Should not be able to create view provider with null view class"); } catch (IllegalArgumentException e) { } } - public void testAddViewSameName() throws Exception { - try { - provider.addView("test", TestView.class); - provider.addView("test", TestView2.class); - fail("Should not be able to add two views with same name"); - } catch (IllegalArgumentException e) { - } + public void testViewNameGetter() throws Exception { + ClassBasedViewProvider provider1 = new ClassBasedViewProvider("", + TestView.class); + assertEquals("View name should be empty", "", provider1.getViewName()); + + ClassBasedViewProvider provider2 = new ClassBasedViewProvider("test", + TestView.class); + assertEquals("View name does not match", "test", + provider2.getViewName()); } - public void testAddViewSameClass() throws Exception { - try { - provider.addView("test", TestView.class); - provider.addView("test2", TestView.class); - fail("Should not be able to add same view class with two different names"); - } catch (IllegalArgumentException e) { - } + public void testViewClassGetter() throws Exception { + ClassBasedViewProvider provider = new ClassBasedViewProvider("test", + TestView.class); + assertEquals("Incorrect view class returned by getter", TestView.class, + provider.getViewClass()); } public void testGetViewNameForNullString() throws Exception { - assertNull( - "Found view name for null view string in an empty view provider", - provider.getViewName((String) null)); - - provider.addView("test", TestView.class); - assertNull("Found view name for null view string", + ClassBasedViewProvider provider = new ClassBasedViewProvider("test", + TestView.class); + assertNull("Received view name for null view string", provider.getViewName((String) null)); } - public void testGetViewNameForNullClass() throws Exception { - assertNull("Found view name for null class", - provider.getViewName((Class<View>) null)); - } - public void testGetViewNameForEmptyString() throws Exception { - assertNull( - "Found view name for empty view string in an empty provider", - provider.getViewName("")); - provider.addView("test", TestView.class); - assertNull("Found view name for empty view string", - provider.getViewName("")); - } + ClassBasedViewProvider provider1 = new ClassBasedViewProvider("", + TestView.class); + assertEquals( + "Did not find view name for empty view string in a provider with empty string registered", + "", provider1.getViewName("")); - public void testGetViewNameForClass() throws Exception { - provider.addView("test", TestView.class); - assertEquals("No view name found for view class", "test", - provider.getViewName(TestView.class)); + ClassBasedViewProvider provider2 = new ClassBasedViewProvider("test", + TestView.class); + assertNull( + "Found view name for empty view string when none registered", + provider2.getViewName("")); } public void testGetViewNameWithParameters() throws Exception { - provider.addView("test", TestView.class); + ClassBasedViewProvider provider = new ClassBasedViewProvider("test", + TestView.class); assertEquals("Incorrect view name found for view string", "test", provider.getViewName("test")); assertEquals( @@ -122,87 +97,21 @@ public class ClassBasedViewProviderTest extends TestCase { "test", provider.getViewName("test/params/are/here")); } - public void testGetViewNameMultipleRegisteredWithParameters() - throws Exception { - provider.addView("test", TestView.class); - provider.addView("test2", TestView2.class); - assertEquals("Incorrect view name found for view string", "test", - provider.getViewName("test/test2/params")); - } - - public void testGetViewNameNestedNames() throws Exception { - provider.addView("test/subview", TestView2.class); - provider.addView("test", TestView.class); - assertEquals("Incorrect view name found for subview string", - "test/subview", provider.getViewName("test/subview")); - assertEquals( - "Incorrect view name found for subview string with empty parameters", - "test/subview", provider.getViewName("test/subview/")); - assertEquals( - "Incorrect view name found for subview string with parameters", - "test/subview", provider.getViewName("test/subview/parameters")); - assertEquals("Incorrect view name found for top level view string", - "test", provider.getViewName("test")); - assertEquals( - "Incorrect view name found for top level view string with empty parameters", - "test", provider.getViewName("test/")); - assertEquals( - "Incorrect view name found for top level view string with parameters starting like subview name", - "test", provider.getViewName("test/subviewnothere")); - } - - public void testGetViewClass() throws Exception { - assertNull("View class found for empty view provider", - provider.getViewClass("test")); - provider.addView("test", TestView.class); - assertEquals("View class not found", TestView.class, - provider.getViewClass("test")); - assertNull("View class found for unregistered view name", - provider.getViewClass("test2")); - } - - public void testGetViewSimple() throws Exception { - assertNull("Found view in an empty view provider", - provider.getViewName("test")); + public void testGetView() throws Exception { + ClassBasedViewProvider provider = new ClassBasedViewProvider("test", + TestView.class); - provider.addView("test", TestView.class); View view = provider.getView("test"); assertNotNull("Did not get view from a provider", view); assertEquals("Incorrect view type", TestView.class, view.getClass()); - assertTrue("View not initialized", ((TestView) view).initialized); - } - - public void testGetViewMultipleRegistered() throws Exception { - provider.addView("test", TestView.class); - provider.addView("test2", TestView2.class); - assertEquals("Incorrect view type", TestView.class, - provider.getView("test").getClass()); - assertEquals("Incorrect view type", TestView2.class, - provider.getView("test2").getClass()); - assertEquals("Incorrect view type", TestView.class, - provider.getView("test").getClass()); } - public void testRemoveView() throws Exception { - provider.addView("test", TestView.class); - assertNotNull("Did not get view from a provider", - provider.getView("test")); - provider.removeView("test"); - assertNull("View class found for removed view name", - provider.getViewClass("test")); - assertNull("View name found for removed view", - provider.getViewName(TestView.class)); - // cached view? - assertNull( - "Received view instance from a provider after removing view type", - provider.getView("test")); - } + public void testGetViewIncorrectViewName() throws Exception { + ClassBasedViewProvider provider = new ClassBasedViewProvider("test", + TestView.class); - public void testGetViewCached() throws Exception { - provider.addView("test", TestView.class); - View view1 = provider.getView("test"); - View view2 = provider.getView("test"); - assertSame("View instance not cached", view1, view2); + View view = provider.getView("test2"); + assertNull("Got view from a provider for incorrect view name", view); } } diff --git a/tests/server-side/com/vaadin/tests/server/navigator/NavigatorTest.java b/tests/server-side/com/vaadin/tests/server/navigator/NavigatorTest.java index d6a7cc68c7..ab983b4022 100644 --- a/tests/server-side/com/vaadin/tests/server/navigator/NavigatorTest.java +++ b/tests/server-side/com/vaadin/tests/server/navigator/NavigatorTest.java @@ -4,6 +4,8 @@ package com.vaadin.tests.server.navigator; +import java.util.LinkedList; + import junit.framework.TestCase; import org.easymock.EasyMock; @@ -11,16 +13,146 @@ import org.easymock.IMocksControl; import com.vaadin.navigator.FragmentManager; import com.vaadin.navigator.Navigator; +import com.vaadin.navigator.Navigator.SimpleViewDisplay; import com.vaadin.navigator.View; import com.vaadin.navigator.ViewChangeListener; +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; import com.vaadin.navigator.ViewDisplay; import com.vaadin.navigator.ViewProvider; +import com.vaadin.tests.server.navigator.ClassBasedViewProviderTest.TestView; +import com.vaadin.tests.server.navigator.ClassBasedViewProviderTest.TestView2; +import com.vaadin.ui.Root; public class NavigatorTest extends TestCase { // TODO test internal parameters (and absence of them) // TODO test listeners blocking navigation, multiple listeners + public static class NullDisplay implements ViewDisplay { + public void showView(View view) { + // do nothing + } + } + + public static class NullFragmentManager implements FragmentManager { + public String getFragment() { + return null; + } + + public void setFragment(String fragment) { + // do nothing + } + } + + public static class TestDisplay implements ViewDisplay { + private View currentView; + + public void showView(View view) { + currentView = view; + } + + public View getCurrentView() { + return currentView; + } + } + + public static class TestNavigator extends Navigator { + public TestNavigator() { + super(new NullFragmentManager(), new TestDisplay()); + } + + public View getView(String viewAndParameters) { + navigateTo(viewAndParameters); + return ((TestDisplay) getDisplay()).getCurrentView(); + } + } + + public static class ViewChangeTestListener implements ViewChangeListener { + private final LinkedList<ViewChangeEvent> referenceEvents = new LinkedList<ViewChangeListener.ViewChangeEvent>(); + private final LinkedList<Boolean> referenceIsCheck = new LinkedList<Boolean>(); + private final LinkedList<Boolean> checkReturnValues = new LinkedList<Boolean>(); + + public void addExpectedIsViewChangeAllowed(ViewChangeEvent event, + boolean returnValue) { + referenceIsCheck.add(true); + referenceEvents.add(event); + checkReturnValues.add(returnValue); + } + + public void addExpectedNavigatorViewChange(ViewChangeEvent event) { + referenceIsCheck.add(false); + referenceEvents.add(event); + } + + public boolean isReady() { + return referenceEvents.isEmpty(); + } + + public boolean equalsReferenceEvent(ViewChangeEvent event, + ViewChangeEvent reference) { + if (event == null) { + return false; + } + if (reference.getNavigator() != event.getNavigator()) { + return false; + } + if (reference.getOldView() != event.getOldView()) { + return false; + } + if (reference.getNewView() != event.getNewView()) { + return false; + } + if (!stringEquals(reference.getViewName(), event.getViewName())) { + return false; + } + if (!stringEquals(reference.getFragmentParameters(), + event.getFragmentParameters())) { + return false; + } + return true; + } + + private static boolean stringEquals(String string1, String string2) { + if (string1 == null) { + return string2 == null; + } else { + return string1.equals(string2); + } + } + + public boolean isViewChangeAllowed(ViewChangeEvent event) { + if (referenceEvents.isEmpty()) { + fail("Unexpected call to isViewChangeAllowed()"); + } + ViewChangeEvent reference = referenceEvents.remove(); + Boolean isCheck = referenceIsCheck.remove(); + if (!isCheck) { + fail("Expected navigatorViewChanged(), received isViewChangeAllowed()"); + } + // here to make sure exactly the correct values are removed from + // each queue + Boolean returnValue = checkReturnValues.remove(); + if (!equalsReferenceEvent(event, reference)) { + fail("View change event does not match reference event"); + } + return returnValue; + } + + public void navigatorViewChanged(ViewChangeEvent event) { + if (referenceEvents.isEmpty()) { + fail("Unexpected call to navigatorViewChanged()"); + } + ViewChangeEvent reference = referenceEvents.remove(); + Boolean isCheck = referenceIsCheck.remove(); + if (isCheck) { + fail("Expected isViewChangeAllowed(), received navigatorViewChanged()"); + } + if (!equalsReferenceEvent(event, reference)) { + fail("View change event does not match reference event"); + } + } + } + public void testBasicNavigation() { IMocksControl control = EasyMock.createControl(); FragmentManager manager = control.createMock(FragmentManager.class); @@ -32,7 +164,6 @@ public class NavigatorTest extends TestCase { // prepare mocks: what to expect EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); EasyMock.expect(manager.getFragment()).andReturn(""); view1.navigateTo(null); display.showView(view1); @@ -40,7 +171,6 @@ public class NavigatorTest extends TestCase { EasyMock.expect(provider.getViewName("test2/")).andReturn("test2"); EasyMock.expect(provider.getView("test2")).andReturn(view2); - view2.init(); EasyMock.expect(manager.getFragment()).andReturn("view1"); view2.navigateTo(null); display.showView(view2); @@ -49,7 +179,6 @@ public class NavigatorTest extends TestCase { EasyMock.expect(provider.getViewName("test1/params")) .andReturn("test1"); EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); EasyMock.expect(manager.getFragment()).andReturn("view2"); view1.navigateTo("params"); display.showView(view1); @@ -75,25 +204,15 @@ public class NavigatorTest extends TestCase { View view2 = control.createMock(View.class); // prepare mocks: what to expect - EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); - EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); - EasyMock.expect(manager.getFragment()).andReturn(""); - view1.navigateTo(null); - display.showView(view1); - manager.setFragment("test1"); - EasyMock.expect(provider.getViewName("test2")).andReturn("test2"); EasyMock.expect(provider.getView("test2")).andReturn(view2); - view2.init(); EasyMock.expect(manager.getFragment()).andReturn("view1"); view2.navigateTo(null); display.showView(view2); manager.setFragment("test2"); - EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); + EasyMock.expect(provider.getViewName("")).andReturn("test1"); EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); EasyMock.expect(manager.getFragment()).andReturn(""); view1.navigateTo(null); display.showView(view1); @@ -102,7 +221,6 @@ public class NavigatorTest extends TestCase { EasyMock.expect(provider.getViewName("test1/params")) .andReturn("test1"); EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); EasyMock.expect(manager.getFragment()).andReturn("view2"); view1.navigateTo("params"); display.showView(view1); @@ -113,8 +231,6 @@ public class NavigatorTest extends TestCase { // create and test navigator Navigator navigator = new Navigator(manager, display); navigator.registerProvider(provider); - // this also triggers navigation - navigator.setMainView("test1"); navigator.navigateTo("test2"); navigator.navigateTo(""); @@ -128,43 +244,46 @@ public class NavigatorTest extends TestCase { ViewProvider provider = control.createMock(ViewProvider.class); View view1 = control.createMock(View.class); View view2 = control.createMock(View.class); - ViewChangeListener listener = control - .createMock(ViewChangeListener.class); + ViewChangeTestListener listener = new ViewChangeTestListener(); + + // create navigator to test + Navigator navigator = new Navigator(manager, display); // prepare mocks: what to expect EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); + ViewChangeEvent event1 = new ViewChangeEvent(navigator, null, view1, + "test1", null); + listener.addExpectedIsViewChangeAllowed(event1, true); EasyMock.expect(manager.getFragment()).andReturn(""); - EasyMock.expect( - listener.isViewChangeAllowed(null, view1, "test1", null, - new Object[0])).andReturn(true); view1.navigateTo(null); display.showView(view1); manager.setFragment("test1"); - listener.navigatorViewChanged(null, view1); + listener.addExpectedNavigatorViewChange(event1); EasyMock.expect(provider.getViewName("test2")).andReturn("test2"); EasyMock.expect(provider.getView("test2")).andReturn(view2); - view2.init(); + ViewChangeEvent event2 = new ViewChangeEvent(navigator, view1, view2, + "test2", null); + listener.addExpectedIsViewChangeAllowed(event2, true); EasyMock.expect(manager.getFragment()).andReturn("view1"); - EasyMock.expect( - listener.isViewChangeAllowed(view1, view2, "test2", null, - new Object[0])).andReturn(true); view2.navigateTo(null); display.showView(view2); manager.setFragment("test2"); - listener.navigatorViewChanged(view1, view2); + listener.addExpectedNavigatorViewChange(event2); control.replay(); - // create and test navigator - Navigator navigator = new Navigator(manager, display); + // test navigator navigator.registerProvider(provider); navigator.addListener(listener); navigator.navigateTo("test1"); navigator.navigateTo("test2"); + + if (!listener.isReady()) { + fail("Missing listener calls"); + } } public void testBlockNavigation() { @@ -174,71 +293,60 @@ public class NavigatorTest extends TestCase { ViewProvider provider = control.createMock(ViewProvider.class); View view1 = control.createMock(View.class); View view2 = control.createMock(View.class); - ViewChangeListener listener1 = control - .createMock(ViewChangeListener.class); - ViewChangeListener listener2 = control - .createMock(ViewChangeListener.class); + ViewChangeTestListener listener1 = new ViewChangeTestListener(); + ViewChangeTestListener listener2 = new ViewChangeTestListener(); + + Navigator navigator = new Navigator(manager, display); // prepare mocks: what to expect // first listener blocks first view change EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); EasyMock.expect(manager.getFragment()).andReturn(""); - EasyMock.expect( - listener1.isViewChangeAllowed(null, view1, "test1", null, - new Object[0])).andReturn(false); + ViewChangeEvent event1 = new ViewChangeEvent(navigator, null, view1, + "test1", null); + listener1.addExpectedIsViewChangeAllowed(event1, false); // second listener blocks second view change EasyMock.expect(provider.getViewName("test1/test")).andReturn("test1"); EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); EasyMock.expect(manager.getFragment()).andReturn(""); - EasyMock.expect( - listener1.isViewChangeAllowed(null, view1, "test1", "test", - new Object[0])).andReturn(true); - EasyMock.expect( - listener2.isViewChangeAllowed(null, view1, "test1", "test", - new Object[0])).andReturn(false); + ViewChangeEvent event2 = new ViewChangeEvent(navigator, null, view1, + "test1", "test"); + listener1.addExpectedIsViewChangeAllowed(event2, true); + listener2.addExpectedIsViewChangeAllowed(event2, false); // both listeners allow view change EasyMock.expect(provider.getViewName("test1/bar")).andReturn("test1"); EasyMock.expect(provider.getView("test1")).andReturn(view1); - view1.init(); EasyMock.expect(manager.getFragment()).andReturn(""); - EasyMock.expect( - listener1.isViewChangeAllowed(null, view1, "test1", "bar", - new Object[0])).andReturn(true); - EasyMock.expect( - listener2.isViewChangeAllowed(null, view1, "test1", "bar", - new Object[0])).andReturn(true); + ViewChangeEvent event3 = new ViewChangeEvent(navigator, null, view1, + "test1", "bar"); + listener1.addExpectedIsViewChangeAllowed(event3, true); + listener2.addExpectedIsViewChangeAllowed(event3, true); view1.navigateTo("bar"); display.showView(view1); manager.setFragment("test1/bar"); - listener1.navigatorViewChanged(null, view1); - listener2.navigatorViewChanged(null, view1); + listener1.addExpectedNavigatorViewChange(event3); + listener2.addExpectedNavigatorViewChange(event3); // both listeners allow view change from non-null view EasyMock.expect(provider.getViewName("test2")).andReturn("test2"); EasyMock.expect(provider.getView("test2")).andReturn(view2); - view2.init(); EasyMock.expect(manager.getFragment()).andReturn("view1"); - EasyMock.expect( - listener1.isViewChangeAllowed(view1, view2, "test2", null, - new Object[0])).andReturn(true); - EasyMock.expect( - listener2.isViewChangeAllowed(view1, view2, "test2", null, - new Object[0])).andReturn(true); + ViewChangeEvent event4 = new ViewChangeEvent(navigator, view1, view2, + "test2", null); + listener1.addExpectedIsViewChangeAllowed(event4, true); + listener2.addExpectedIsViewChangeAllowed(event4, true); view2.navigateTo(null); display.showView(view2); manager.setFragment("test2"); - listener1.navigatorViewChanged(view1, view2); - listener2.navigatorViewChanged(view1, view2); + listener1.addExpectedNavigatorViewChange(event4); + listener2.addExpectedNavigatorViewChange(event4); control.replay(); - // create and test navigator - Navigator navigator = new Navigator(manager, display); + // test navigator navigator.registerProvider(provider); navigator.addListener(listener1); navigator.addListener(listener2); @@ -247,6 +355,215 @@ public class NavigatorTest extends TestCase { navigator.navigateTo("test1/test"); navigator.navigateTo("test1/bar"); navigator.navigateTo("test2"); + + if (!listener1.isReady()) { + fail("Missing listener calls for listener1"); + } + if (!listener2.isReady()) { + fail("Missing listener calls for listener2"); + } + } + + public void testDefaultDisplayType() { + IMocksControl control = EasyMock.createControl(); + Root root = control.createMock(Root.class); + + Navigator navigator = new Navigator(root); + + assertEquals("Default display should be a SimpleViewDisplay", + SimpleViewDisplay.class, navigator.getDisplay().getClass()); + } + + public void testAddViewInstance() throws Exception { + View view = new TestView(); + + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", view); + + assertEquals("Registered view instance not returned by navigator", + view, navigator.getView("test")); } + public void testAddViewInstanceSameName() throws Exception { + View view1 = new TestView(); + View view2 = new TestView2(); + + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", view1); + navigator.addView("test", view2); + + assertEquals( + "Adding second view with same name should override previous view", + view2, navigator.getView("test")); + } + + public void testAddViewClass() throws Exception { + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", TestView.class); + + View view = navigator.getView("test"); + assertNotNull("Received null view", view); + assertEquals("Received incorrect type of view", TestView.class, + view.getClass()); + } + + public void testAddViewClassSameName() throws Exception { + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", TestView.class); + navigator.addView("test", TestView2.class); + + assertEquals( + "Adding second view class with same name should override previous view", + TestView2.class, navigator.getView("test").getClass()); + } + + public void testAddViewInstanceAndClassSameName() throws Exception { + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", TestView.class); + TestView2 view2 = new TestView2(); + navigator.addView("test", view2); + + assertEquals( + "Adding second view class with same name should override previous view", + view2, navigator.getView("test")); + + navigator.addView("test", TestView.class); + + assertEquals( + "Adding second view class with same name should override previous view", + TestView.class, navigator.getView("test").getClass()); + } + + public void testAddViewWithNullName() throws Exception { + Navigator navigator = new Navigator(new NullFragmentManager(), + new NullDisplay()); + + try { + navigator.addView(null, new TestView()); + fail("addView() accepted null view name"); + } catch (IllegalArgumentException e) { + } + try { + navigator.addView(null, TestView.class); + fail("addView() accepted null view name"); + } catch (IllegalArgumentException e) { + } + } + + public void testAddViewWithNullInstance() throws Exception { + Navigator navigator = new Navigator(new NullFragmentManager(), + new NullDisplay()); + + try { + navigator.addView("test", (View) null); + fail("addView() accepted null view instance"); + } catch (IllegalArgumentException e) { + } + } + + public void testAddViewWithNullClass() throws Exception { + Navigator navigator = new Navigator(new NullFragmentManager(), + new NullDisplay()); + + try { + navigator.addView("test", (Class<View>) null); + fail("addView() accepted null view class"); + } catch (IllegalArgumentException e) { + } + } + + public void testRemoveViewInstance() throws Exception { + View view = new TestView(); + + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", view); + navigator.removeView("test"); + + assertNull("View not removed", navigator.getView("test")); + } + + public void testRemoveViewInstanceNothingElse() throws Exception { + View view = new TestView(); + View view2 = new TestView2(); + + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", view); + navigator.addView("test2", view2); + navigator.removeView("test"); + + assertEquals("Removed extra views", view2, navigator.getView("test2")); + } + + public void testRemoveViewClass() throws Exception { + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", TestView.class); + navigator.removeView("test"); + + assertNull("View not removed", navigator.getView("test")); + } + + public void testRemoveViewClassNothingElse() throws Exception { + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test", TestView.class); + navigator.addView("test2", TestView2.class); + navigator.removeView("test"); + + assertEquals("Removed extra views", TestView2.class, + navigator.getView("test2").getClass()); + } + + public void testGetViewNestedNames() throws Exception { + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test/subview", TestView2.class); + navigator.addView("test", TestView.class); + + assertEquals("Incorrect view name found for subview string", + TestView2.class, navigator.getView("test/subview").getClass()); + assertEquals( + "Incorrect view name found for subview string with empty parameters", + TestView2.class, navigator.getView("test/subview/").getClass()); + assertEquals( + "Incorrect view name found for subview string with parameters", + TestView2.class, navigator.getView("test/subview/parameters") + .getClass()); + assertEquals("Incorrect view name found for top level view string", + TestView.class, navigator.getView("test").getClass()); + assertEquals( + "Incorrect view name found for top level view string with empty parameters", + TestView.class, navigator.getView("test/").getClass()); + assertEquals( + "Incorrect view name found for top level view string with parameters starting like subview name", + TestView.class, navigator.getView("test/subviewnothere") + .getClass()); + } + + public void testGetViewLongestPrefixOrder() throws Exception { + TestNavigator navigator = new TestNavigator(); + + navigator.addView("test/subview", TestView2.class); + navigator.addView("test", TestView.class); + + assertEquals("Incorrect view name found", TestView.class, navigator + .getView("test").getClass()); + + // other order + + TestNavigator navigator2 = new TestNavigator(); + + navigator2.addView("test", TestView.class); + navigator2.addView("test/subview", TestView2.class); + + assertEquals("Incorrect view name found", TestView.class, navigator2 + .getView("test").getClass()); + } } diff --git a/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java b/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java new file mode 100644 index 0000000000..2240fc246b --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java @@ -0,0 +1,73 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.tests.components.javascriptcomponent; + +import java.util.Arrays; +import java.util.List; + +import com.vaadin.annotations.LoadScripts; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.AbstractJavascriptComponent; +import com.vaadin.ui.Root; + +@LoadScripts({ "/statictestfiles/jsconnector.js" }) +public class BasicJavascriptComponent extends AbstractTestRoot { + + public interface ExampleClickRpc extends ServerRpc { + public void onClick(String message); + } + + public static class SpecialState extends ComponentState { + private List<String> data; + + public List<String> getData() { + return data; + } + + public void setData(List<String> data) { + this.data = data; + } + } + + public static class ExampleWidget extends AbstractJavascriptComponent { + public ExampleWidget() { + registerRpc(new ExampleClickRpc() { + public void onClick(String message) { + Root.getCurrentRoot().showNotification( + "Got a click: " + message); + } + }); + getState().setData(Arrays.asList("a", "b", "c")); + } + + @Override + public SpecialState getState() { + return (SpecialState) super.getState(); + } + } + + @Override + protected void setup(WrappedRequest request) { + ExampleWidget c = new ExampleWidget(); + c.setCaption("test caption"); + c.setDescription("Some description"); + addComponent(c); + } + + @Override + protected String getTestDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/table/TableInTabsheet.java b/tests/testbench/com/vaadin/tests/components/table/TableInTabsheet.java new file mode 100644 index 0000000000..27e624b810 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/table/TableInTabsheet.java @@ -0,0 +1,114 @@ +package com.vaadin.tests.components.table; + +import java.net.MalformedURLException; + +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.gwt.client.ui.label.ContentMode; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.AbsoluteLayout; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.Table; +import com.vaadin.ui.Table.Align; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.Reindeer; + +public class TableInTabsheet extends AbstractTestRoot { + + @Override + protected void setup(WrappedRequest request) { + getRoot().setCaption("test"); + VerticalLayout vPrinc = new VerticalLayout(); + vPrinc.setStyleName(Reindeer.LAYOUT_BLUE); + + vPrinc.addComponent(title()); + vPrinc.addComponent(page()); + vPrinc.addComponent(new Label("Dvlop Tecnologia.")); + setContent(vPrinc); + } + + private VerticalLayout title() { + + VerticalLayout vP = new VerticalLayout(); + vP.setStyleName(Reindeer.LAYOUT_BLACK); + Label tit = new Label("<h1> Tab/Table Test</h1>", ContentMode.XHTML); + vP.addComponent(tit); + return vP; + + } + + private VerticalLayout page() { + + VerticalLayout vP = new VerticalLayout(); + vP.setStyleName(Reindeer.LAYOUT_BLUE); + TabSheet t = new TabSheet(); + t.setWidth(1000, Unit.PIXELS); + + HorizontalLayout hP = new HorizontalLayout(); + t.addTab(Ranking(), "Ranking"); + try { + + t.addTab(GDocs(""), "Dez 2011"); + t.addTab(GDocs(""), "Jan 2012"); + t.addTab(GDocs(""), "Abr 2012"); + + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + hP.addComponent(t); + vP.addComponent(hP); + return vP; + + } + + private AbsoluteLayout Ranking() { + + AbsoluteLayout vT = new AbsoluteLayout(); + vT.setHeight(500, Unit.PIXELS); + vT.setWidth(900, Unit.PIXELS); + vT.setStyleName(Reindeer.LAYOUT_BLUE); + + final Table table = new Table("Ranking Oficial"); + + table.addContainerProperty("Atleta", String.class, null); + table.addContainerProperty("P", String.class, null); + table.addContainerProperty("Dez/11", Integer.class, null); + table.setColumnAlignment("Dez/11", Align.CENTER); + table.addContainerProperty("Jan/12", Integer.class, null); + table.setColumnAlignment("Jan/12", Align.CENTER); + table.addContainerProperty("Abr/12", String.class, null); + table.addContainerProperty("Total", Integer.class, null); + table.setColumnAlignment("Total", Align.CENTER); + + table.addItem(new Object[] { "Araujo", "D.1", 8, 8, " ", 16 }, 1); + table.addItem(new Object[] { "Claudio", "D.2", 2, 10, " ", 12 }, 2); + table.setPageLength(12); + + vT.addComponent(table, "left: 50px; top: 50px;"); + return vT; + + } + + private VerticalLayout GDocs(String end) throws MalformedURLException { + + VerticalLayout vT = new VerticalLayout(); + vT.setHeight(500, Unit.PIXELS); + vT.setWidth(900, Unit.PIXELS); + + return vT; + + } + + @Override + protected String getTestDescription() { + return "Chaning to a different tab and then back to the first tab should properly render the table."; + } + + @Override + protected Integer getTicketNumber() { + return Integer.valueOf(8714); + } + +} diff --git a/tests/testbench/com/vaadin/tests/features/HelloWorldFeatureTest.java b/tests/testbench/com/vaadin/tests/extensions/HelloWorldExtensionTest.java index 3d26b92648..b56e05b2a0 100644 --- a/tests/testbench/com/vaadin/tests/features/HelloWorldFeatureTest.java +++ b/tests/testbench/com/vaadin/tests/extensions/HelloWorldExtensionTest.java @@ -1,7 +1,7 @@ /* @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.tests.features; +package com.vaadin.tests.extensions; import com.vaadin.terminal.WrappedRequest; import com.vaadin.tests.components.AbstractTestRoot; @@ -9,7 +9,7 @@ import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.HelloWorldExtension; -public class HelloWorldFeatureTest extends AbstractTestRoot { +public class HelloWorldExtensionTest extends AbstractTestRoot { @Override protected void setup(WrappedRequest request) { @@ -26,7 +26,7 @@ public class HelloWorldFeatureTest extends AbstractTestRoot { @Override protected String getTestDescription() { - return "Testing basic Feature"; + return "Testing basic Extension"; } @Override diff --git a/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.html b/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.html new file mode 100644 index 0000000000..7bdb8cd50f --- /dev/null +++ b/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.html @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="http://localhost:8888/" /> +<title>New Test</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">New Test</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/run/com.vaadin.tests.extensions.JavascriptManagerTest?restartApplication</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestsextensionsJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[4]</td> + <td>1. Got 4 arguments</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestsextensionsJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[3]</td> + <td>2. Argument 1 as a number: 42</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestsextensionsJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[2]</td> + <td>3. Argument 2 as a string: text</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestsextensionsJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[1]</td> + <td>4. Argument 3.p as a boolean: true</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestsextensionsJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[0]</td> + <td>5. Argument 4 is JSONObject.NULL: true</td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.java b/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.java new file mode 100644 index 0000000000..c84d37cd0f --- /dev/null +++ b/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.java @@ -0,0 +1,47 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.extensions; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.tests.util.Log; +import com.vaadin.ui.JavascriptCallback; + +public class JavascriptManagerTest extends AbstractTestRoot { + + private Log log = new Log(5); + + @Override + protected void setup(WrappedRequest request) { + addComponent(log); + getJavascriptManager().addCallback("testing", new JavascriptCallback() { + public void call(JSONArray arguments) throws JSONException { + log.log("Got " + arguments.length() + " arguments"); + log.log("Argument 1 as a number: " + arguments.getInt(0)); + log.log("Argument 2 as a string: " + arguments.getString(1)); + log.log("Argument 3.p as a boolean: " + + arguments.getJSONObject(2).getBoolean("p")); + log.log("Argument 4 is JSONObject.NULL: " + + (arguments.get(3) == JSONObject.NULL)); + } + }); + executeJavaScript("window.testing(42, 'text', {p: true}, null)"); + } + + @Override + protected String getTestDescription() { + return "Test javascript callback handling by adding a callback and invoking the javascript."; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/tests/testbench/com/vaadin/tests/extensions/SimpleJavascriptExtensionTest.java b/tests/testbench/com/vaadin/tests/extensions/SimpleJavascriptExtensionTest.java new file mode 100644 index 0000000000..92c134efb0 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/extensions/SimpleJavascriptExtensionTest.java @@ -0,0 +1,95 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.extensions; + +import com.vaadin.annotations.LoadScripts; +import com.vaadin.terminal.AbstractJavascriptExtension; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Root; + +@LoadScripts({ "/statictestfiles/jsextension.js" }) +public class SimpleJavascriptExtensionTest extends AbstractTestRoot { + + public static class SimpleJavascriptExtensionState extends SharedState { + private String prefix; + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return prefix; + } + } + + public static interface SimpleJavascriptExtensionClientRpc extends + ClientRpc { + public void greet(String message); + } + + public static interface SimpleJavascriptExtensionServerRpc extends + ServerRpc { + public void greet(String message); + } + + public static class SimpleJavascriptExtension extends + AbstractJavascriptExtension { + + public SimpleJavascriptExtension() { + registerRpc(new SimpleJavascriptExtensionServerRpc() { + public void greet(String message) { + Root.getCurrentRoot().showNotification( + getState().getPrefix() + message); + } + }); + } + + @Override + public SimpleJavascriptExtensionState getState() { + return (SimpleJavascriptExtensionState) super.getState(); + } + + public void setPrefix(String prefix) { + getState().setPrefix(prefix); + requestRepaint(); + } + + public void greet(String message) { + getRpcProxy(SimpleJavascriptExtensionClientRpc.class) + .greet(message); + } + } + + @Override + protected void setup(WrappedRequest request) { + final SimpleJavascriptExtension simpleJavascriptExtension = new SimpleJavascriptExtension(); + simpleJavascriptExtension.setPrefix("Prefix: "); + addExtension(simpleJavascriptExtension); + addComponent(new Button("Send greeting", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + simpleJavascriptExtension.greet("Greeted by button"); + } + })); + } + + @Override + protected String getTestDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +} |