001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.extensions.ajax.markup.html.autocomplete; 018 019 020import java.util.Arrays; 021import java.util.List; 022import java.util.Objects; 023 024import org.apache.wicket.Application; 025import org.apache.wicket.Component; 026import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior; 027import org.apache.wicket.ajax.AjaxRequestTarget; 028import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 029import org.apache.wicket.markup.head.HeaderItem; 030import org.apache.wicket.markup.head.IHeaderResponse; 031import org.apache.wicket.markup.head.IWrappedHeaderItem; 032import org.apache.wicket.markup.head.JavaScriptHeaderItem; 033import org.apache.wicket.markup.head.OnDomReadyHeaderItem; 034import org.apache.wicket.markup.head.ResourceAggregator; 035import org.apache.wicket.request.Response; 036import org.apache.wicket.request.cycle.RequestCycle; 037import org.apache.wicket.request.resource.JavaScriptResourceReference; 038import org.apache.wicket.request.resource.ResourceReference; 039 040/** 041 * @since 1.2 042 * 043 * @author Janne Hietamäki (jannehietamaki) 044 */ 045public abstract class AbstractAutoCompleteBehavior extends AbstractDefaultAjaxBehavior 046{ 047 /** 048 * A wrapper for the auto-complete DOM-ready event handler. 049 * <p> 050 * A plain OnDomReadyItem would be aggregated by {@link ResourceAggregator}, possible coming 051 * after the event registration of other behaviors. 052 */ 053 private static final class WrappedHeaderItem extends HeaderItem implements IWrappedHeaderItem 054 { 055 private final OnDomReadyHeaderItem item; 056 057 private WrappedHeaderItem(OnDomReadyHeaderItem onDomReady) 058 { 059 item = onDomReady; 060 } 061 062 @Override 063 public void render(Response response) 064 { 065 item.render(response); 066 } 067 068 @Override 069 public Iterable<?> getRenderTokens() 070 { 071 return item.getRenderTokens(); 072 } 073 074 @Override 075 public HeaderItem getWrapped() 076 { 077 return item; 078 } 079 080 @Override 081 public HeaderItem wrap(HeaderItem item) 082 { 083 if (item instanceof OnDomReadyHeaderItem) 084 return new WrappedHeaderItem((OnDomReadyHeaderItem)item); 085 return item; 086 } 087 088 @Override 089 public List<HeaderItem> getDependencies() 090 { 091 ResourceReference wicketAjaxReference = Application.get(). 092 getJavaScriptLibrarySettings().getWicketAjaxReference(); 093 return Arrays.<HeaderItem>asList(JavaScriptHeaderItem.forReference(wicketAjaxReference)); 094 } 095 096 @Override 097 public boolean equals(Object o) 098 { 099 if (this == o) return true; 100 if (o == null || getClass() != o.getClass()) return false; 101 WrappedHeaderItem that = (WrappedHeaderItem) o; 102 return Objects.equals(item, that.item); 103 } 104 105 @Override 106 public int hashCode() 107 { 108 return Objects.hash(item); 109 } 110 } 111 112 public static final ResourceReference AUTOCOMPLETE_JS = new JavaScriptResourceReference( 113 AutoCompleteBehavior.class, "wicket-autocomplete.js"); 114 115 private static final long serialVersionUID = 1L; 116 117 protected AutoCompleteSettings settings; 118 119 /** 120 * Constructor that creates an default {@link AutoCompleteSettings} 121 */ 122 public AbstractAutoCompleteBehavior() 123 { 124 this(new AutoCompleteSettings()); 125 } 126 127 /** 128 * Constructor 129 * 130 * @param settings 131 * settings for the autocomplete list 132 */ 133 public AbstractAutoCompleteBehavior(AutoCompleteSettings settings) 134 { 135 if (settings == null) 136 { 137 settings = new AutoCompleteSettings(); 138 } 139 this.settings = settings; 140 } 141 142 @Override 143 public void renderHead(final Component component, final IHeaderResponse response) 144 { 145 super.renderHead(component, response); 146 147 renderAutocompleteHead(response); 148 } 149 150 /** 151 * Render autocomplete init javascript and other head contributions 152 * 153 * @param response 154 */ 155 private void renderAutocompleteHead(final IHeaderResponse response) 156 { 157 response.render(JavaScriptHeaderItem.forReference(AUTOCOMPLETE_JS)); 158 159 String initJS = String.format("new Wicket.AutoComplete(%s, %s);", renderAjaxAttributes(getComponent()), constructSettingsJS()); 160 161 final OnDomReadyHeaderItem onDomReady = OnDomReadyHeaderItem.forScript(initJS); 162 163 response.render(new WrappedHeaderItem(onDomReady)); 164 } 165 166 /** 167 * 168 * @return JS settings 169 */ 170 protected final String constructSettingsJS() 171 { 172 final StringBuilder sb = new StringBuilder(); 173 sb.append("{preselect: ").append(settings.getPreselect()); 174 sb.append(",maxHeight: ").append(settings.getMaxHeightInPx()); 175 sb.append(",adjustInputWidth: ").append(settings.isAdjustInputWidth()); 176 sb.append(",useSmartPositioning: ").append(settings.getUseSmartPositioning()); 177 sb.append(",showListOnEmptyInput: ").append(settings.getShowListOnEmptyInput()); 178 sb.append(",ignoreBordersWhenPositioning: ").append( 179 settings.getIgnoreBordersWhenPositioning()); 180 sb.append(",showListOnFocusGain: ").append(settings.getShowListOnFocusGain()); 181 sb.append(",throttleDelay: ").append(settings.getThrottleDelay()); 182 sb.append(",minInputLength: ").append(settings.getMinInputLength()); 183 sb.append(",parameterName: '").append(settings.getParameterName()).append('\''); 184 sb.append(",showCompleteListOnFocusGain: ").append( 185 settings.getShowCompleteListOnFocusGain()); 186 if (settings.getCssClassName() != null) 187 { 188 sb.append(",className: '").append(settings.getCssClassName()).append('\''); 189 } 190 sb.append('}'); 191 return sb.toString(); 192 } 193 194 /** 195 * Callback for the ajax event generated by the javascript. This is where we need to generate 196 * our response. 197 * 198 * @param input 199 * the input entered so far 200 * @param requestCycle 201 * current request cycle 202 */ 203 protected abstract void onRequest(String input, RequestCycle requestCycle); 204 205 @Override 206 protected void respond(final AjaxRequestTarget target) 207 { 208 final RequestCycle requestCycle = RequestCycle.get(); 209 final String val = requestCycle.getRequest() 210 .getRequestParameters() 211 .getParameterValue(settings.getParameterName()) 212 .toOptionalString(); 213 214 onRequest(val, requestCycle); 215 } 216 217 @Override 218 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 219 { 220 super.updateAjaxAttributes(attributes); 221 222 attributes.setWicketAjaxResponse(false); 223 attributes.setDataType("html"); 224 } 225}