/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wamblee.wicket.jquery;
import java.util.Arrays;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
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 makes it easy to write jQuery behaviors:
*
* - Creating a ready function which will be invoked for the component
* - 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.
* - Ensuring that the markup id of the component is output
* - Creating a call to an intialization function from the ready handler using
* the component id
*
*
* 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.
*
* The second parameter is obtained through a call to
* {@link #getConfigurationJavascript()}.
*
*
* @author Erik Brakkee
*
*
*/
public class AbstractJQueryBehavior extends CompositeBehavior {
private static final String DEFAULT_NAMESPACE = "org.wamblee";
private static JSONSerializer DEFAULT_JSON_SERIALIZER = new JSONSerializer();
private Component component;
private String function;
/**
* Constructs the behavior.
*
* @param aFunction
* 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.
*/
public AbstractJQueryBehavior(String aFunction, IBehavior... aBehaviors) {
super(getBehaviors(aBehaviors));
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();
for (int i = 0; i < aBehaviors.length; i++) {
behaviors[i + 1] = aBehaviors[i];
}
return behaviors;
}
@Override
public void bind(Component aComponent) {
if (component != null) {
throw new IllegalStateException(
"this kind of handler cannot be attached to " +
"multiple components; it is already attached to component " +
component + ", but component " + aComponent +
" wants to be attached too");
}
if (!isPageAllowed() && aComponent instanceof Page) {
throw new IllegalStateException(
"This behavior cannot be applied to a page: " + aComponent);
}
component = aComponent;
super.bind(aComponent);
if (!(aComponent instanceof Page)) {
aComponent.setOutputMarkupId(true);
}
}
@Override
public void renderHead(IHeaderResponse aResponse) {
super.renderHead(aResponse);
String jsString = createReadyFunction();
aResponse.renderJavascript(jsString, null);
}
/**
* Creates a jQuery ready handler that invokes a given javascript function
* 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, 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
*/
String createReadyFunction() {
if (!(component instanceof Page) && !component.getOutputMarkupId()) {
throw new IllegalStateException(
"The component " +
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(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()}.
*
* 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;
}
}