d49110dd3d14e929162d149d2e3ba950abea3b22
[utils] / wicket / components / src / main / java / org / wamblee / wicket / jquery / AbstractJQueryBehavior.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.wicket.jquery;
17
18 import java.util.Arrays;
19
20 import org.apache.wicket.Component;
21 import org.apache.wicket.Page;
22 import org.apache.wicket.behavior.IBehavior;
23 import org.apache.wicket.markup.html.IHeaderResponse;
24 import org.wamblee.wicket.behavior.CompositeBehavior;
25
26 import flexjson.JSONSerializer;
27
28 /**
29  * Abstract JQuery hehavior class that makes it easy to write jQuery behaviors:
30  * <ul>
31  * <li>Creating a ready function which will be invoked for the component</li>
32  * <li>Checking that the component is not a page</li>
33  * <li>Ensuring tha the markup id of the component is output</li>
34  * <li>Creating a call to an intialization function from the ready handler using
35  * the component id</li>
36  * </ul>
37  * 
38  * The ready function will be invoked as part of a ready handler and will invoke 
39  * a function with two arguments. The first is the id of the component and the second
40  * is a configuration object. 
41  * <p>
42  * The second parameter is obtained through a call to {@link #getConfigurationJavascript()}.
43  * 
44  * 
45  * @author Erik Brakkee
46  * 
47  * <ConfigType> 
48  */
49 public class AbstractJQueryBehavior<ConfigType> extends CompositeBehavior {
50
51     private static final String DEFAULT_NAMESPACE = "org.wamblee";
52     private static JSONSerializer DEFAULT_JSON_SERIALIZER = new JSONSerializer();
53
54     private Component component;
55     private String function;
56
57     /**
58      * Constructs the behavior.
59      * 
60      * @param aFunction
61      *            Function to be invoked from the ready handler. This function
62      *            is invoked with a CSS selector that identifies the component.
63      * @param aBehaviors
64      *            Behaviors to add in addition to the basic JQuery stuff.
65      */
66     public AbstractJQueryBehavior(String aFunction, IBehavior... aBehaviors) {
67         super(getBehaviors(aBehaviors));
68         function = aFunction;
69     }
70
71     private static IBehavior[] getBehaviors(IBehavior[] aBehaviors) {
72         IBehavior[] behaviors = new IBehavior[aBehaviors.length + 1];
73         behaviors[0] = new JQueryHeaderContributor();
74         for (int i = 0; i < aBehaviors.length; i++) {
75             behaviors[i + 1] = aBehaviors[i];
76         }
77         return behaviors;
78     }
79
80     @Override
81     public void bind(Component aComponent) {
82         if (component != null) {
83             throw new IllegalStateException(
84                 "this kind of handler cannot be attached to " +
85                     "multiple components; it is already attached to component " +
86                     component + ", but component " + aComponent +
87                     " wants to be attached too");
88         }
89         if (aComponent instanceof Page) {
90             throw new IllegalStateException(
91                 "This behavior cannot be applied to a page: " + aComponent);
92         }
93         component = aComponent;
94         super.bind(aComponent);
95         aComponent.setOutputMarkupId(true);
96     }
97
98     @Override
99     public void renderHead(IHeaderResponse aResponse) {
100         super.renderHead(aResponse);
101         String jsString = createReadyFunction();
102         aResponse.renderJavascript(jsString, null);
103     }
104
105     /**
106      * Creates a jQuery ready handler that invokes a given javascript function
107      * with the id of a component and with a second parameter of parameters to
108      * pass additional information to the function.
109      * 
110      * @param aFunction
111      *            Javascript function to invoke, the name of this function must
112      *            include the namespace as it is called from a global scope.
113      * @param aComponent
114      *            Component to invoke the id for.
115      * @return
116      */
117     String createReadyFunction() {
118         if (!component.getOutputMarkupId()) {
119             throw new IllegalStateException(
120                 "The component " +
121                     component +
122                     " does not have its markup id set so this ready handler will not have any effect");
123         }
124         StringBuffer js = new StringBuffer();
125         js.append("jQuery(function(){");
126         js.append(function + "(");
127         js.append("\"#" + component.getMarkupId() + "\"");
128         String config = getConfigurationJavascript();
129         if (config != null) {
130             js.append(",");
131             js.append(config);
132         }
133         js.append(");");
134         js.append("});");
135         String jsString = js.toString();
136         return jsString;
137     }
138
139     /**
140      * Returns a javascript object that is passed as second argument to the
141      * ready function. This method uses {@link #getConfigurationObject()} to
142      * obtain the configuration object to use which is then serialized to
143      * javascript using {@link JSONSerializer}. Subclasses can override the
144      * default JSONSerializer by implementing {@link #getCustomSerializer()}.
145      * <p>
146      * Subclasses should override this method to perform custom serialization.
147      * 
148      * @return Configuration object in javascript.
149      */
150     protected String getConfigurationJavascript() {
151         Object config = getConfigurationObject();
152         return getActualSerializer().serialize(config);
153     }
154
155     /**
156      * Gets the actual serializer to use. It uses {@link #getCustomSerializer()}
157      * to check if there is a custom serializer available.
158      * 
159      * @return Serializer.
160      */
161     private JSONSerializer getActualSerializer() {
162         JSONSerializer serializer = getCustomSerializer();
163         if (serializer != null) {
164             return serializer;
165         }
166         return DEFAULT_JSON_SERIALIZER;
167     }
168
169     /**
170      * Returns the serializer to use. Implementations can override this method
171      * to perform custom initialization of the serializer knowing the type of
172      * configuration object to use.
173      * 
174      * @return Custom serializer to use.
175      */
176     protected JSONSerializer getCustomSerializer() {
177         return null;
178     }
179
180     /**
181      * Gets the configuration object to use. This is transformed to JSON using
182      * the serializer.
183      * 
184      * @return Configuration object.
185      */
186     protected ConfigType getConfigurationObject() {
187         return null;
188     }
189 }