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
019import org.apache.wicket.core.util.string.JavaScriptUtils;
020import org.apache.wicket.request.Response;
021import org.apache.wicket.util.string.Strings;
022
023/**
024 * A renderer that abstracts auto-assist specific details and allows subclasses to only render the
025 * visual part of the assist instead of having to also render the necessary auto-assist javascript
026 * hooks.
027 * 
028 * @param <T>
029 * 
030 * @since 1.2
031 * 
032 * @author Igor Vaynberg (ivaynberg)
033 */
034public abstract class AbstractAutoCompleteRenderer<T> implements IAutoCompleteRenderer<T>
035{
036        private static final long serialVersionUID = 1L;
037
038        @Override
039        public void render(final T object, final Response response, final String criteria)
040        {
041                String textValue = getTextValue(object);
042                if (textValue == null)
043                {
044                        throw new IllegalStateException(
045                                "A call to textValue(Object) returned an illegal value: null for object: " +
046                                        object.toString());
047                }
048                textValue = Strings.escapeMarkup(textValue).toString();
049
050                response.write("<li textvalue=\"" + textValue + "\"");
051                final CharSequence handler = getOnSelectJavaScriptExpression(object);
052                if (handler != null)
053                {
054                        response.write(" onselect=\"" + Strings.escapeMarkup(handler) + '"');
055                }
056                response.write(">");
057                renderChoice(object, response, criteria);
058                response.write("</li>");
059        }
060
061        @Override
062        public void renderHeader(final Response response)
063        {
064                response.write("<ul>");
065        }
066
067        @Override
068        public void renderFooter(final Response response, int count)
069        {
070                response.write("</ul>");
071        }
072
073        /**
074         * Render the visual portion of the assist. Usually the html representing the assist choice
075         * object is written out to the response use {@link Response#write(CharSequence)}
076         * 
077         * <p>
078         * <b>Whatever is written to the response reaches the markup as is, without escaping.</b>
079         * Any part of it that comes from user input or other dynamic content must be escaped
080         * before it is written, with {@link Strings#escapeMarkup(CharSequence)}.
081         * </p>
082         *
083         * @param object
084         *            current assist choice
085         * @param response
086         * @param criteria
087         */
088        protected abstract void renderChoice(T object, Response response, String criteria);
089
090        /**
091         * Retrieves the text value that will be set on the textbox if this assist is selected
092         * 
093         * @param object
094         *            assist choice object
095         * @return the text value that will be set on the textbox if this assist is selected
096         */
097        protected abstract String getTextValue(T object);
098
099        /**
100         * Allows the execution of a custom javascript expression when an item is selected in the
101         * autocompleter popup (either by clicking on it or hitting enter on the current selection).
102         * <p/>
103         * The javascript to execute must be a javascript expression that will be processed using
104         * javascript's eval(). The function should return the textvalue to copy it into the
105         * corresponding form input field (the default behavior).
106         * 
107         * the current text value will be in variable 'input'.
108         * 
109         * If the function returns <code>null</code> the chosen text value will be ignored.
110         * <p/>
111         * example 1:
112         * 
113         * <pre>
114         * protected CharSequence getOnSelectJavaScript(Address address)
115         * {
116         *      final StringBuilder js = new StringBuilder();
117         *      js.append(&quot;Wicket.DOM.get('street').value ='&quot; + address.getStreet() + &quot;';&quot;);
118         *      js.append(&quot;Wicket.DOM.get('zipcode').value ='&quot; + address.getZipCode() + &quot;';&quot;);
119         *      js.append(&quot;Wicket.DOM.get('city').value ='&quot; + address.getCity() + &quot;';&quot;);
120         *      js.append(&quot;arguments[0]&quot;); // &lt;-- do not use return statement here!
121         *      return js.toString();
122         * }
123         * </pre>
124         * 
125         * example 2:
126         * 
127         * <pre>
128         * protected CharSequence getOnSelectJavaScript(Currency currency)
129         * {
130         *      final StringBuilder js = new StringBuilder();
131         *      js.append(&quot;val rate = ajaxGetExchangeRateForCurrency(currencySymbol);&quot;);
132         *      js.append(&quot;if(rate == null) alert('exchange rate service currently not available');&quot;);
133         *      js.append(&quot;rate&quot;);
134         *      return js.toString();
135         * }
136         * </pre>
137         * 
138         * Then the autocompleter popup will be closed.
139         * 
140         * @param item
141         *            the autocomplete item to get a custom javascript expression for
142         * @return javascript to execute on selection or <code>null</code> if default behavior is
143         *         intended
144         */
145        protected CharSequence getOnSelectJavaScriptExpression(final T item)
146        {
147                return null;
148        }
149}