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}