Coverage Report - org.apache.tapestry5.corelib.mixins.Autocomplete
 
Classes in this File Line Coverage Branch Coverage Complexity
Autocomplete
57%
21/37
50%
5/10
0
Autocomplete$1
0%
0/4
N/A
0
 
 1  
 // Copyright 2007, 2008, 2009 The Apache Software Foundation
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package org.apache.tapestry5.corelib.mixins;
 16  
 
 17  
 import org.apache.tapestry5.*;
 18  
 import org.apache.tapestry5.ContentType;
 19  
 import org.apache.tapestry5.annotations.*;
 20  
 import org.apache.tapestry5.internal.util.Holder;
 21  
 import org.apache.tapestry5.ioc.annotations.Inject;
 22  
 import org.apache.tapestry5.ioc.services.TypeCoercer;
 23  
 import org.apache.tapestry5.json.JSONArray;
 24  
 import org.apache.tapestry5.json.JSONObject;
 25  
 import org.apache.tapestry5.services.MarkupWriterFactory;
 26  
 import org.apache.tapestry5.services.Request;
 27  
 import org.apache.tapestry5.services.ResponseRenderer;
 28  
 import org.apache.tapestry5.util.TextStreamResponse;
 29  
 
 30  
 import java.util.Collections;
 31  
 import java.util.List;
 32  
 
 33  
 /**
 34  
  * A mixin for a text field that allows for autocompletion of text fields. This is based on Prototype's autocompleter
 35  
  * control.
 36  
  * <p/>
 37  
  * The mixin renders an (initially invisible) progress indicator after the field (it will also be after the error icon
 38  
  * most fields render). The progress indicator is made visible during the request to the server. The mixin then renders
 39  
  * a &lt;div&gt; that will be filled in on the client side with dynamically obtained selections.
 40  
  * <p/>
 41  
  * Multiple selection on the client is enabled by binding the tokens parameter (however, the mixin doesn't help split
 42  
  * multiple selections up on the server, that is still your code's responsibility).
 43  
  * <p/>
 44  
  * The container is responsible for providing an event handler for event "providecompletions".  The context will be the
 45  
  * partial input string sent from the client.  The return value should be an array or list of completions, in
 46  
  * presentation order.  I.e.
 47  
  * <p/>
 48  
  * <pre>
 49  
  * String[] onProvideCompletionsFromMyField(String input)
 50  
  * {
 51  
  *   return . . .;
 52  
  * }
 53  
  * </pre>
 54  
  */
 55  
 @IncludeJavaScriptLibrary({ "${tapestry.scriptaculous}/controls.js", "autocomplete.js" })
 56  
 @Events(EventConstants.PROVIDE_COMPLETIONS)
 57  2
 public class Autocomplete
 58  
 {
 59  
     static final String EVENT_NAME = "autocomplete";
 60  
 
 61  
     private static final String PARAM_NAME = "t:input";
 62  
 
 63  
     /**
 64  
      * The field component to which this mixin is attached.
 65  
      */
 66  
     @InjectContainer
 67  
     private Field field;
 68  
 
 69  
     @Inject
 70  
     private ComponentResources resources;
 71  
 
 72  
     @Environmental
 73  
     private RenderSupport renderSupport;
 74  
 
 75  
     @Inject
 76  
     private Request request;
 77  
 
 78  
     @Inject
 79  
     private TypeCoercer coercer;
 80  
 
 81  
     @Inject
 82  
     private MarkupWriterFactory factory;
 83  
 
 84  
     @Inject
 85  
     @Path("${tapestry.spacer-image}")
 86  
     private Asset spacerImage;
 87  
 
 88  
     /**
 89  
      * Overwrites the default minimum characters to trigger a server round trip (the default is 1).
 90  
      */
 91  
     @Parameter(defaultPrefix = BindingConstants.LITERAL)
 92  
     private int minChars;
 93  
 
 94  
     @Inject
 95  
     private ResponseRenderer responseRenderer;
 96  
 
 97  
 
 98  
     /**
 99  
      * Overrides the default check frequency for determining whether to send a server request. The default is .4
 100  
      * seconds.
 101  
      */
 102  
     @Parameter(defaultPrefix = BindingConstants.LITERAL)
 103  
     private double frequency;
 104  
 
 105  
     /**
 106  
      * If given, then the autocompleter will support multiple input values, seperated by any of the individual
 107  
      * characters in the string.
 108  
      */
 109  
     @Parameter(defaultPrefix = BindingConstants.LITERAL)
 110  
     private String tokens;
 111  
 
 112  
     /**
 113  
      * Mixin afterRender phrase occurs after the component itself. This is where we write the &lt;div&gt; element and
 114  
      * the JavaScript.
 115  
      *
 116  
      * @param writer
 117  
      */
 118  
     void afterRender(MarkupWriter writer)
 119  
     {
 120  2
         String id = field.getClientId();
 121  
 
 122  2
         String menuId = id + ":menu";
 123  2
         String loaderId = id + ":loader";
 124  
 
 125  
         // The spacer image is used as a placeholder, allowing CSS to determine what image
 126  
         // is actually displayed.
 127  
 
 128  2
         writer.element("img",
 129  
 
 130  
                        "src", spacerImage.toClientURL(),
 131  
 
 132  
                        "class", "t-autoloader-icon " + CSSClassConstants.INVISIBLE,
 133  
 
 134  
                        "alt", "",
 135  
 
 136  
                        "id", loaderId);
 137  2
         writer.end();
 138  
 
 139  2
         writer.element("div",
 140  
 
 141  
                        "id", menuId,
 142  
 
 143  
                        "class", "t-autocomplete-menu");
 144  2
         writer.end();
 145  
 
 146  2
         Link link = resources.createEventLink(EVENT_NAME);
 147  
 
 148  
 
 149  2
         JSONObject config = new JSONObject();
 150  2
         config.put("paramName", PARAM_NAME);
 151  2
         config.put("indicator", loaderId);
 152  
 
 153  2
         if (resources.isBound("minChars")) config.put("minChars", minChars);
 154  
 
 155  2
         if (resources.isBound("frequency")) config.put("frequency", frequency);
 156  
 
 157  2
         if (resources.isBound("tokens"))
 158  
         {
 159  6
             for (int i = 0; i < tokens.length(); i++)
 160  
             {
 161  4
                 config.accumulate("tokens", tokens.substring(i, i + 1));
 162  
             }
 163  
         }
 164  
 
 165  
         // Let subclasses do more.
 166  2
         configure(config);
 167  
 
 168  2
         renderSupport.addInit("autocompleter", new JSONArray(id, menuId, link.toAbsoluteURI(), config));
 169  2
     }
 170  
 
 171  
     Object onAutocomplete()
 172  
     {
 173  0
         String input = request.getParameter(PARAM_NAME);
 174  
 
 175  0
         final Holder<List> matchesHolder = Holder.create();
 176  
 
 177  
         // Default it to an empty list.
 178  
 
 179  0
         matchesHolder.put(Collections.emptyList());
 180  
 
 181  0
         ComponentEventCallback callback = new ComponentEventCallback()
 182  
         {
 183  0
             public boolean handleResult(Object result)
 184  
             {
 185  0
                 List matches = coercer.coerce(result, List.class);
 186  
 
 187  0
                 matchesHolder.put(matches);
 188  
 
 189  0
                 return true;
 190  
             }
 191  
         };
 192  
 
 193  0
         resources.triggerEvent(EventConstants.PROVIDE_COMPLETIONS, new Object[] { input }, callback);
 194  
 
 195  0
         ContentType contentType = responseRenderer.findContentType(this);
 196  
 
 197  0
         MarkupWriter writer = factory.newPartialMarkupWriter(contentType);
 198  
 
 199  0
         generateResponseMarkup(writer, matchesHolder.get());
 200  
 
 201  0
         return new TextStreamResponse(contentType.toString(), writer.toString());
 202  
     }
 203  
 
 204  
     /**
 205  
      * Invoked to allow subclasses to further configure the parameters passed to the JavaScript Ajax.Autocompleter
 206  
      * options. The values minChars, frequency and tokens my be pre-configured. Subclasses may override this method to
 207  
      * configure additional features of the Ajax.Autocompleter.
 208  
      * <p/>
 209  
      * <p/>
 210  
      * This implementation does nothing.
 211  
      *
 212  
      * @param config parameters object
 213  
      */
 214  
     protected void configure(JSONObject config)
 215  
     {
 216  2
     }
 217  
 
 218  
     /**
 219  
      * Generates the markup response that will be returned to the client; this should be an &lt;ul&gt; element with
 220  
      * nested &lt;li&gt; elements. Subclasses may override this to produce more involved markup (including images and
 221  
      * CSS class attributes).
 222  
      *
 223  
      * @param writer  to write the list to
 224  
      * @param matches list of matching objects, each should be converted to a string
 225  
      */
 226  
     protected void generateResponseMarkup(MarkupWriter writer, List matches)
 227  
     {
 228  0
         writer.element("ul");
 229  
 
 230  0
         for (Object o : matches)
 231  
         {
 232  0
             writer.element("li");
 233  0
             writer.write(o.toString());
 234  0
             writer.end();
 235  
         }
 236  
 
 237  0
         writer.end(); // ul
 238  0
     }
 239  
 }