Listing 1: the doKeyPress function

projsf.jdj.doKeyPress = function(
  event)
{
  var input = (event.srcElement || event.target);
  var inputId = input.id;
  var div = document.getElementById(inputId + "$suggest");
  var divStyle = (div.currentStyle || div.style);
  if (event.keyCode == 9 && divStyle.display == "block")
  {
    div.style.display = "none";
    var activeRow = projsf.jdj._findActiveRow(div);
    input.value = activeRow.innerHTML;
    return false; //Cancel Tab out
  }
  
  return true; //Proceed with Tab out, which will invoke the doBlur()
}

Listing 2: The doKeyUp function

projsf.jdj.doKeyUp = function(
  event)
{
  var input = (event.srcElement || event.target);
  var inputId = input.id;
  
  var div = document.getElementById(inputId + "$suggest");
  if (event.keyCode == 9)//Tab key
  {
    return false;
  }
  else if ((div.style.display == "block" || div.childNodes.length > 0) &&
           (event.keyCode == 40 || event.keyCode == 38))
  {
    if (div.style.display == "none")
    {
      div.style.display = "block";
    }
    else
    {
      var activeRow = projsf.jdj._findActiveRow(div);
      
      switch (event.keyCode)
      {
        case 40: // Arrow Down
          if (activeRow.nextSibling)
          {
            activeRow.className = "HtmlInputSuggestRow";
            activeRow = activeRow.nextSibling;
            activeRow.className = "HtmlInputSuggestActiveRow";
          }
          break;
  
        case 38: // Arrow Up
          if (activeRow.previousSibling)
          {
            activeRow.className = "HtmlInputSuggestRow";
            activeRow = activeRow.previousSibling;
            activeRow.className = "HtmlInputSuggestActiveRow";
          }
          break;
      }
  
      input.value = activeRow.innerHTML;
    }

    return false;
  }

  if (event.keyCode != 8)//Not a Backspace
  {
    input.blur();
    input.focus();
  }

  if (input.value.length <= 2)
    div.style.display = "none";
}

Listing 3: The doChange function

projsf.jdj.doChange = function(
  event,
  doSuggestURL)
{
  var input = (event.srcElement || event.target);
  var inputId = input.id;
  
  var context = { _inputId: inputId };
  net.java.dev.mabon.send({ url: doSuggestURL,
                            args: [input.value],
                            callback: function(result) {
                              projsf.jdj._callback.call(context,
                                                        result);} });
  return true;
}

Listing 4: The _callback function

projsf.jdj._callback = function(
  results)
{
  var inputId = this._inputId;
  var input = document.getElementById(inputId);

  var div = document.getElementById(inputId + "$suggest");

  if (results.length <= 1)
  {
    div.style.display = "none";
    return;
  }
 
  // get the input from the context
  var input = document.getElementById(inputId);
  div.style.width = input.offsetWidth;
  while (div.firstChild)
  {
    div.removeChild(div.firstChild);
  }
  
  for (var i=0; i < results.length; i++)
  {
    var row = document.createElement("div");
    var span = document.createElement("span");
    var text = document.createTextNode(results[i]);
    row.className = "HtmlInputSuggestRow";
    row.appendChild(text);
    row.onmouseover = new Function("event", 
                                   "projsf.jdj._doMouseOver(event || 
                                                     window.event)");
    row.onclick = new Function("event", 
                               "projsf.jdj._doMouseClick(event || 
                                                     window.event)");
    div.appendChild(row);
  }
  
  div.firstChild.className = "HtmlInputSuggestActiveRow";
  
  div.style.display = "block";

  window.setTimeout("projsf.jdj._selectText('" + inputId + "', " + 
                                           "'" + input.value + "', " + 
                                           "'" + results[0] + "')", 
                    200);
}

Listing 5: The _selectText Function

projsf.jdj._selectText = function(
  inputId,
  initialValue,
  suggestion)
{
  var input = document.getElementById(inputId);
  if (input.value != initialValue)
    return;
  
  if (input.value == suggestion)
    return;

  if (input.createTextRange)//IE Specific
  {
    var selectionStart = input.value.length;
    input.value = suggestion;
    var range = input.createTextRange();
    range.moveStart("character", selectionStart);
    range.moveEnd("character", input.value.length);
    range.select();
  }
  else //DOM Compliant
  {
    var selectionStart = input.value.length;
    input.value = suggestion;
    input.selectionStart = selectionStart;
    input.selectionEnd = input.value.length;
  }
}

Listing 6: The HtmlInputSuggestRenderer encodeBegin() Method

package com.apress.projsf.jdj.render.html;

Import ...

/**
 * HtmlInputSuggestRenderer renders a traditional HtmlInputText field
 * with auto-suggest behavior.
 */
public class HtmlInputSuggestRenderer extends HtmlRenderer
{
...
  public static String TITLE_ATTR = "title";
  public static String DO_SUGGEST_ATTR = "doSuggest";

  public void encodeBegin(
    FacesContext context,
    UIComponent  component) throws IOException
  {
    writeScriptResource(context, 
                        "weblet://org.dojotoolkit.browserio/dojo.js");
    writeScriptResource(context, 
                        "weblet://net.java.dev.mabon/mabon.js");
    writeScriptResource(context, 
                        "weblet://com.apress.projsf.jdj/inputSuggest.js");
    writeStyleResource(context, 
                       "weblet://com.apress.projsf.jdj/inputSuggest.css");
}

Listing 7: The HtmlInputSuggestRenderer encodeEnd() Method

  public void encodeEnd(
    FacesContext context,
    UIComponent  component) throws IOException
  {
    String valueString = _getValueAsString(context, component);
    String clientId = component.getClientId(context);

    Map attrs = component.getAttributes();
    String title = (String)attrs.get(TITLE_ATTR);
    String onchange = (String)attrs.get(ONCHANGE_ATTR);
    MethodBinding doSuggest = (MethodBinding)attrs.get(DO_SUGGEST_ATTR);

    ResponseWriter out = context.getResponseWriter();
    out.startElement("div", component);

    if (title != null)
      out.writeAttribute("title", title, TITLE_ATTR);

    // <input id="[clientId]" name="[clientId]"
    //        value="[converted-value]" onchange="[onchange]" />
    out.startElement("input", component);
    out.writeAttribute("id", clientId, null);
    out.writeAttribute("name", clientId, null);
    if (valueString != null)
      out.writeAttribute("value", valueString, null);
    if (doSuggest != null)
    {
      // disable browser auto-complete when using server-side suggest
      out.writeAttribute("autocomplete", "off", null);
      
      String expression = doSuggest.getExpressionString();
      // trim #{} from expression
      String bindingRef = 
             expression.substring(2, expression.length() - 1);
      ViewHandler handler = 
             context.getApplication().getViewHandler();
      String doSuggestURL = 
             handler.getResourceURL(context, "mabon:/" + bindingRef);
      out.writeAttribute("onkeypress", 
                         "return projsf.jdj.doKeyPress(event);", null);
      out.writeAttribute("onkeyup", 
                         "return projsf.jdj.doKeyUp(event);", null);
      out.writeAttribute("onchange", 
                         "projsf.jdj.doChange(event, '" + doSuggestURL 
                                              + "');", null);
      out.writeAttribute("onblur", 
                         "return projsf.jdj.doBlur(event);", null);
    }
    out.endElement("input");
    out.startElement("br", null);
    out.endElement("br");
    out.startElement("div", null);
    out.writeAttribute("id", clientId + "$suggest", null);
    out.writeAttribute("class", "HtmlInputSuggest", null);
    out.endElement("div");
    
  }

Listing 8: A JSP page using the JSF input suggest component

<?xml version="1.0" encoding="UTF-8" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"
          xmlns:jdj="http://projsf.apress.com/jdj" 
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html" >
  <jsp:directive.page contentType="text/html" />
  <f:view>
    <html>
      <head>
        <title>ProJSF : HtmlInputSuggest</title>
      </head>
      <body>
        <h:form id="form" >
          <jdj:inputSuggest id="inputSuggest" 
                            title="Input Suggest Component" 
                            value="#{backingBean.value}"
                            doSuggest="#{backingBean.doSuggest}" />
        </h:form>
      </body>
    </html>
  </f:view>
</jsp:root>

Listing 9: The backing bean value property

package com.apress.projsf.jdj.application;

import java.util.ArrayList;
import java.util.List;

/**
 * BackingBean is a backing bean for inputSuggest.jspx document.
 */
public class BackingBean
{
  public void setValue(
    Object value)
  {
    _value = value;
  }

  public Object getValue()
  {
    return _value;
  }

Listing 10: The backing bean doSuggest() method

  public String[] doSuggest(
    String initialValue)
  {
    List<String> suggestions = new ArrayList<String>();

    for (int i=0; i < _MASTER_LIST.length; i++)
    {
      if (_MASTER_LIST[i].startsWith(initialValue))
        suggestions.add(_MASTER_LIST[i]);
    }

    return suggestions.toArray(new String[0]);
  }
  
  private Object _value;

  static private final String[] _MASTER_LIST = new String[]
                                               {
                                                 "Pro JSF and Ajax",
                                                 "Pro Ajax",
                                                 "Pro JSP 2",
                                                 "Pro Jakarta",
                                                 "Pro J2EE 1.4"
                                               };
}