/*
* ==============================================================================
* 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 wicket.contrib.input.events;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnLoadHeaderItem;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.template.PackageTextTemplate;
import org.apache.wicket.util.template.TextTemplate;
import org.apache.wicket.util.value.IValueMap;
import wicket.contrib.input.events.key.KeyHookOn;
import wicket.contrib.input.events.key.KeyType;
/**
* Add this to your button, link whatever to create a shortcut key..
*
* <strong>WARNING:this behavior uses a special script for calling
* window.onload</strong>
*
* @author Nino Martinez (nino.martinez.wael *at* gmail *dot* com remember no
* stars)
*
*/
public class InputBehavior extends Behavior {
private static final long serialVersionUID = 1L;
private final ResourceReference SHORTCUTS_JAVASCRIPT = new PackageResourceReference(
InputBehavior.class, "shortcut.js");
private final KeyType[] keyCombo;
private EventType eventType;
private boolean autoHook = false;
private boolean linkUnbound = false;
private final TextTemplate shortcutJs = new PackageTextTemplate(
InputBehavior.class, "wicket-contrib-input-behavior.js");
private final TextTemplate shortcutJsAutoHook = new PackageTextTemplate(
InputBehavior.class, "wicket-contrib-input-behavior-autohook.js");
private final TextTemplate shortcutJsAutoHookLink = new PackageTextTemplate(
InputBehavior.class,
"wicket-contrib-input-behavior-autohook-link.js");
public InputBehavior(KeyType[] keyCombo, EventType eventType) {
this.keyCombo = keyCombo;
this.eventType = eventType;
}
/**
* if using auto hook be sure to add this behavior last, otherwise it might
* not pickup the event.. Also it will only hook up to the last event if
* more are present (use other constructor to specify manually)
*
*
* Note on keyCombo
*
* The shortcut keys should be specified in this format ...
* Modifier[+Modifier..]+Key
*
* Meaning that you should specify in this order, modifier keys first like
* 'ctrl' and then normal keys like 'a'
*
* @param keyCombo
* @param autoHook
*/
public InputBehavior(KeyType[] keyCombo) {
this.keyCombo = keyCombo;
autoHook = true;
}
/** The target component. */
private Component component;
@Override
public void bind(Component component) {
super.bind(component);
this.component = component;
component.setOutputMarkupId(true);
}
/**
* Gets the escaped DOM id that the input will get attached to. All non word
* characters (\W) will be removed from the string.
*
* @return The DOM id of the input - same as the component's markup id}
*/
protected final String getEscapedComponentMarkupId() {
return component.getMarkupId().replaceAll("\\W", "");
}
@Override
public void renderHead(Component c, IHeaderResponse response) {
super.renderHead(c, response);
response.render(JavaScriptHeaderItem.forReference(SHORTCUTS_JAVASCRIPT));
if (!autoHook) {
response.render(OnLoadHeaderItem
.forScript(generateString(shortcutJs)));
}
}
@Override
public void onComponentTag(Component component, ComponentTag tag) {
super.onComponentTag(component, tag);
if (autoHook) {
IValueMap attribs = tag.getAttributes();
for (String attrib : attribs.keySet()) {
List<EventType> list = Arrays.asList(EventType.values());
for (EventType e : list) {
if (attrib.toLowerCase().contains(
e.toString().toLowerCase())) {
eventType = e;
}
}
}
// Try to bind to link so shortcut will work. Should only be done if
// no other handlers were found
if (component instanceof Link && eventType == null) {
linkUnbound = true;
return;
}
}
}
@Override
public void afterRender(Component component) {
// TODO Auto-generated method stub
super.afterRender(component);
if (autoHook) {
Response response = component.getResponse();
if (linkUnbound) {
response.write(generateString(shortcutJsAutoHookLink));
} else {
response.write(generateString(shortcutJsAutoHook));
}
}
}
private String generateString(TextTemplate textTemplate) {
// variables for the initialization script
HashMap<String, Object> variables = new HashMap<String, Object>();
String widgetId = getEscapedComponentMarkupId();
StringBuilder keyComboString = new StringBuilder();
boolean first = true;
for (KeyType keyType : keyCombo) {
if (first) {
first = false;
} else {
keyComboString.append('+');
}
keyComboString.append(keyType.getKeyCode());
}
if (eventType != null) {
variables.put("event", eventType.toString());
}
variables.put("keys", keyComboString.toString());
variables.put("wicketComponentId", widgetId);
variables.put("disable_in_input", getDisable_in_input().toString());
variables.put("type", getType().toString());
variables.put("propagate", getPropagate().toString());
variables.put("target", getTarget());
textTemplate.interpolate(variables);
return textTemplate.asString();
}
/**
* If this is set to true, keyboard capture will be disabled in input and
* textarea fields. If these elements have focus, the keyboard shortcut will
* not work. This is very useful for single key shortcuts. Default: false
*
* @return
*/
protected Boolean getDisable_in_input() {
return false;
}
/**
* The event type - can be 'keydown','keyup','keypress'. Default: 'keydown'
*
* @return
*/
protected KeyHookOn getType() {
return KeyHookOn.keydown;
}
/**
* Should the command be passed onto the browser afterwards?
*
* @return
*/
protected Boolean getPropagate() {
return false;
}
/**
* target - DOM Node The element that should be watched for the keyboard
* event. Default : document
*
* @return
*/
protected String getTarget() {
return "document";
}
}