Addition of tooltip behavior.
[utils] / wicket / components / src / main / java / org / wamblee / wicket / jquery / AbstractJQueryBehavior.java
index 14692bfb8bbd03aed1eaf94a499b6caeaced6667..cdc72ab8b23629d283ef3bf81f21a8c44c3041fc 100644 (file)
@@ -23,19 +23,39 @@ import org.apache.wicket.behavior.IBehavior;
 import org.apache.wicket.markup.html.IHeaderResponse;
 import org.wamblee.wicket.behavior.CompositeBehavior;
 
+import flexjson.JSONSerializer;
+
 /**
- * Abstract JQuery hehavior class that performs some useful basic behaviors for
- * the behavior such as:
+ * Abstract JQuery hehavior class that makes it easy to write jQuery behaviors:
  * <ul>
  * <li>Creating a ready function which will be invoked for the component</li>
- * <li>Checking that the component is not a page</li>
- * <li>Creating a call to an intialization function from the ready handler using the component id </li>
+ * <li>Checking whether or not the behavior may be attached to a page using
+ * {@link #isPageAllowed()}. By default, the behavior may be attached to
+ * pages. Behaviors that should not be allowed to be attached to pages should
+ * override this method.</li>
+ * <li>Ensuring that the markup id of the component is output</li>
+ * <li>Creating a call to an intialization function from the ready handler using
+ * the component id</li>
  * </ul>
  * 
+ * The ready function will be invoked as part of a ready handler and will invoke
+ * a function with two arguments. The first is the selector of the component and
+ * the second is a configuration object. In case the behavior is attached to a
+ * component, a selector is used based on the unique markup id. When used on a
+ * page, the selector matches with the "body" of the page.
+ * <p>
+ * The second parameter is obtained through a call to
+ * {@link #getConfigurationJavascript()}.
+ * 
+ * 
  * @author Erik Brakkee
  * 
+ *         <ConfigType>
  */
-public class AbstractJQueryBehavior extends CompositeBehavior {
+public class AbstractJQueryBehavior<ConfigType> extends CompositeBehavior {
+
+    private static final String DEFAULT_NAMESPACE = "org.wamblee";
+    private static JSONSerializer DEFAULT_JSON_SERIALIZER = new JSONSerializer();
 
     private Component component;
     private String function;
@@ -44,7 +64,8 @@ public class AbstractJQueryBehavior extends CompositeBehavior {
      * Constructs the behavior.
      * 
      * @param aFunction
-     *            Ready function to be invoked.
+     *            Function to be invoked from the ready handler. This function
+     *            is invoked with a CSS selector that identifies the component.
      * @param aBehaviors
      *            Behaviors to add in addition to the basic JQuery stuff.
      */
@@ -53,6 +74,15 @@ public class AbstractJQueryBehavior extends CompositeBehavior {
         function = aFunction;
     }
 
+    /**
+     * Determines whether the behavior is allowed to be attached toa page.
+     * 
+     * @return True. 
+     */
+    protected boolean isPageAllowed() {
+        return true;
+    }
+
     private static IBehavior[] getBehaviors(IBehavior[] aBehaviors) {
         IBehavior[] behaviors = new IBehavior[aBehaviors.length + 1];
         behaviors[0] = new JQueryHeaderContributor();
@@ -71,46 +101,110 @@ public class AbstractJQueryBehavior extends CompositeBehavior {
                     component + ", but component " + aComponent +
                     " wants to be attached too");
         }
-        if (aComponent instanceof Page) {
+        if (!isPageAllowed() && aComponent instanceof Page) {
             throw new IllegalStateException(
                 "This behavior cannot be applied to a page: " + aComponent);
         }
         component = aComponent;
         super.bind(aComponent);
-        aComponent.setOutputMarkupId(true);
+        if (!(aComponent instanceof Page)) {
+            aComponent.setOutputMarkupId(true);
+        }
     }
 
     @Override
     public void renderHead(IHeaderResponse aResponse) {
         super.renderHead(aResponse);
-        String jsString = createReadyFunction(function, component);
+        String jsString = createReadyFunction();
         aResponse.renderJavascript(jsString, null);
     }
 
     /**
      * Creates a jQuery ready handler that invokes a given javascript function
-     * with the id of a component.
+     * with the id of a component and with a second parameter of parameters to
+     * pass additional information to the function.
      * 
      * @param aFunction
-     *            Javascript function to invoke.
+     *            Javascript function to invoke, the name of this function must
+     *            include the namespace as it is called from a global scope.
      * @param aComponent
      *            Component to invoke the id for.
      * @return
      */
-    public static String createReadyFunction(String aFunction,
-        Component aComponent) {
-        if (!aComponent.getOutputMarkupId()) {
+    String createReadyFunction() {
+        if (!(component instanceof Page) && !component.getOutputMarkupId()) {
             throw new IllegalStateException(
                 "The component " +
-                    aComponent +
+                    component +
                     " does not have its markup id set so this ready handler will not have any effect");
         }
         StringBuffer js = new StringBuffer();
         js.append("jQuery(function(){");
-        js.append("org.wamblee." + aFunction + "(\"#" +
-            aComponent.getMarkupId() + "\");");
+        js.append(function + "(");
+        String selector = "body";
+        if (!(component instanceof Page)) {
+            selector = "#" + component.getMarkupId();
+        }
+        js.append("\"" + selector + "\"");
+        String config = getConfigurationJavascript();
+        if (config != null) {
+            js.append(",");
+            js.append(config);
+        }
+        js.append(");");
         js.append("});");
         String jsString = js.toString();
         return jsString;
     }
+
+    /**
+     * Returns a javascript object that is passed as second argument to the
+     * ready function. This method uses {@link #getConfigurationObject()} to
+     * obtain the configuration object to use which is then serialized to
+     * javascript using {@link JSONSerializer}. Subclasses can override the
+     * default JSONSerializer by implementing {@link #getCustomSerializer()}.
+     * <p>
+     * Subclasses should override this method to perform custom serialization.
+     * 
+     * @return Configuration object in javascript.
+     */
+    protected String getConfigurationJavascript() {
+        Object config = getConfigurationObject();
+        return getActualSerializer().serialize(config);
+    }
+
+    /**
+     * Gets the actual serializer to use. It uses {@link #getCustomSerializer()}
+     * to check if there is a custom serializer available.
+     * 
+     * @return Serializer.
+     */
+    private JSONSerializer getActualSerializer() {
+        JSONSerializer serializer = getCustomSerializer();
+        if (serializer != null) {
+            return serializer;
+        }
+        return DEFAULT_JSON_SERIALIZER;
+    }
+
+    /**
+     * Returns the serializer to use. Implementations can override this method
+     * to perform custom initialization of the serializer knowing the type of
+     * configuration object to use.
+     * 
+     * @return Custom serializer to use.
+     */
+    protected JSONSerializer getCustomSerializer() {
+        return null;
+    }
+
+    /**
+     * Gets the configuration object to use. This is transformed to JSON using
+     * the serializer.
+     * 
+     * @return Configuration object.
+     */
+    protected ConfigType getConfigurationObject() {
+        return null;
+    }
 }