Home » tapestry-src-5.0.19 » org.apache.tapestry5.corelib.mixins » [javadoc | source]

    1   // Copyright 2007, 2008 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   public class Autocomplete
   57   {
   58       static final String EVENT_NAME = "autocomplete";
   59   
   60       private static final String PARAM_NAME = "t:input";
   61   
   62       /**
   63        * The field component to which this mixin is attached.
   64        */
   65       @InjectContainer
   66       private Field field;
   67   
   68       @Inject
   69       private ComponentResources resources;
   70   
   71       @Environmental
   72       private RenderSupport renderSupport;
   73   
   74       @Inject
   75       private Request request;
   76   
   77       @Inject
   78       private TypeCoercer coercer;
   79   
   80       @Inject
   81       private MarkupWriterFactory factory;
   82   
   83       @Inject
   84       @Path("${tapestry.spacer-image}")
   85       private Asset spacerImage;
   86   
   87       /**
   88        * Overwrites the default minimum characters to trigger a server round trip (the default is 1).
   89        */
   90       @Parameter(defaultPrefix = BindingConstants.LITERAL)
   91       private int minChars;
   92   
   93       @Inject
   94       private ResponseRenderer responseRenderer;
   95   
   96   
   97       /**
   98        * Overrides the default check frequency for determining whether to send a server request. The default is .4
   99        * seconds.
  100        */
  101       @Parameter(defaultPrefix = BindingConstants.LITERAL)
  102       private double frequency;
  103   
  104       /**
  105        * If given, then the autocompleter will support multiple input values, seperated by any of the individual
  106        * characters in the string.
  107        */
  108       @Parameter(defaultPrefix = BindingConstants.LITERAL)
  109       private String tokens;
  110   
  111       /**
  112        * Mixin afterRender phrase occurs after the component itself. This is where we write the &lt;div&gt; element and
  113        * the JavaScript.
  114        *
  115        * @param writer
  116        */
  117       void afterRender(MarkupWriter writer)
  118       {
  119           String id = field.getClientId();
  120   
  121           String menuId = id + ":menu";
  122           String loaderId = id + ":loader";
  123   
  124           // The spacer image is used as a placeholder, allowing CSS to determine what image
  125           // is actually displayed.
  126   
  127           writer.element("img",
  128   
  129                          "src", spacerImage.toClientURL(),
  130   
  131                          "class", "t-autoloader-icon " + CSSClassConstants.INVISIBLE,
  132   
  133                          "id", loaderId);
  134           writer.end();
  135   
  136           writer.element("div",
  137   
  138                          "id", menuId,
  139   
  140                          "class", "t-autocomplete-menu");
  141           writer.end();
  142   
  143           Link link = resources.createEventLink(EVENT_NAME);
  144   
  145   
  146           JSONObject config = new JSONObject();
  147           config.put("paramName", PARAM_NAME);
  148           config.put("indicator", loaderId);
  149   
  150           if (resources.isBound("minChars")) config.put("minChars", minChars);
  151   
  152           if (resources.isBound("frequency")) config.put("frequency", frequency);
  153   
  154           if (resources.isBound("tokens"))
  155           {
  156               for (int i = 0; i < tokens.length(); i++)
  157               {
  158                   config.accumulate("tokens", tokens.substring(i, i + 1));
  159               }
  160           }
  161   
  162           // Let subclasses do more.
  163           configure(config);
  164   
  165           renderSupport.addInit("autocompleter", new JSONArray(id, menuId, link.toAbsoluteURI(), config));
  166       }
  167   
  168       Object onAutocomplete()
  169       {
  170           String input = request.getParameter(PARAM_NAME);
  171   
  172           final Holder<List> matchesHolder = Holder.create();
  173   
  174           // Default it to an empty list.
  175   
  176           matchesHolder.put(Collections.emptyList());
  177   
  178           ComponentEventCallback callback = new ComponentEventCallback()
  179           {
  180               public boolean handleResult(Object result)
  181               {
  182                   List matches = coercer.coerce(result, List.class);
  183   
  184                   matchesHolder.put(matches);
  185   
  186                   return true;
  187               }
  188           };
  189   
  190           resources.triggerEvent("providecompletions", new Object[] {input}, callback);
  191   
  192           ContentType contentType = responseRenderer.findContentType(this);
  193   
  194           MarkupWriter writer = factory.newPartialMarkupWriter(contentType);
  195   
  196           generateResponseMarkup(writer, matchesHolder.get());
  197   
  198           return new TextStreamResponse(contentType.toString(), writer.toString());
  199       }
  200   
  201       /**
  202        * Invoked to allow subclasses to further configure the parameters passed to the JavaScript Ajax.Autocompleter
  203        * options. The values minChars, frequency and tokens my be pre-configured. Subclasses may override this method to
  204        * configure additional features of the Ajax.Autocompleter.
  205        * <p/>
  206        * <p/>
  207        * This implementation does nothing.
  208        *
  209        * @param config parameters object
  210        */
  211       protected void configure(JSONObject config)
  212       {
  213       }
  214   
  215       /**
  216        * Generates the markup response that will be returned to the client; this should be an &lt;ul&gt; element with
  217        * nested &lt;li&gt; elements. Subclasses may override this to produce more involved markup (including images and
  218        * CSS class attributes).
  219        *
  220        * @param writer  to write the list to
  221        * @param matches list of matching objects, each should be converted to a string
  222        */
  223       protected void generateResponseMarkup(MarkupWriter writer, List matches)
  224       {
  225           writer.element("ul");
  226   
  227           for (Object o : matches)
  228           {
  229               writer.element("li");
  230               writer.write(o.toString());
  231               writer.end();
  232           }
  233   
  234           writer.end(); // ul
  235       }
  236   }

Home » tapestry-src-5.0.19 » org.apache.tapestry5.corelib.mixins » [javadoc | source]