Play with Lift’s Autocomplete Widget using Scala-One of the Best Feature of LiftWeb Framework


The AutoComplete widget provides a flexible and accessible way to offer suggestions or some kind of filtering information as a user type text information in an input field . If you have used Google , you might see that Google starts providing autocomplete suggestion with entries depending on user’s current input .

Lift provides a very easy way to implement Autocomplete using Scala .

Here , I am going to explain two ways to implement Autocomplete Functionality in Lift using Scala .

1) Using this way , you can use Lift’s Autocomplete widget but with limited features .

Simple Autocomplete

Here is how we integrate it into your Lift application.

a)Initialisation

Add below line in Boot.Scala

import net.liftweb.widgets.autocomplete.AutoComplete
  class Boot {
    def boot {
      ...
      AutoComplete.init
      ...
    }
  }

b) Snippet Binding
In order to implement in Snippet , follow below lines :-

"autocomplete" ->AutoComplete(startValue,
                               getResults _, 
                                value => takeAction(value)
                                , List(("minChars" -> "3"))
/**
where:
startValue is a string for the initial value of the AutoComplete text field.
getResults is a function to populate the Autocomplete drop down results.
takeAction is a function called when the form is submitted, containing the value of the widget
**/
def getResults(current: String, limit: Int): Seq[String] = {
...
}
 /**
where
current is the string typed into the autoComplete text field.
limit is an integer to limit the number of Strings returned by the function
**/
def takeAction(str : String) = {
println("Selected Value"+str)
}

2) If you have used Facebook , you might agree that at many places , Facebook provides filtered information in a very nice way . Using above way , you could not implement like this .
For this you have to extend Autocomplete.Scala and modify it . In one of my project , I implemented Auto complete widget in facebook style by modifying Autocomplete.Scala .

Customized Autocomplete Widget

package it.autocompleteexample.lib {

  import _root_.scala.xml.{ NodeSeq, Node, Elem, PCData, Text, Unparsed }
  import _root_.net.liftweb.common._
  import _root_.net.liftweb.util._
  import _root_.net.liftweb.http._
  import _root_.net.liftweb.http.js._
  import JsCmds._
  import JE._
  import S._
  import SHtml._
  import Helpers._

  object AutoComplete {

    def apply(start: String,
      options: (String, Int) => Seq[(String, String, String, String, String)],
      onSubmit: String => Unit,
      jsonOptions: List[(String, String)],
      handler: JsCmd,
      attrs: (String, String)*) = new AutoComplete().render(start, options, onSubmit, jsonOptions, handler, attrs: _*)

  
  def init() {
      import _root_.net.liftweb.http.ResourceServer

      ResourceServer.allow({
        case "autocomplete" :: _ => true
      })
    }

  }

  class AutoComplete {
  
    def render(start: String,
      options: (String, Int) => Seq[(String, String, String, String, String)],
      onSubmit: String => Unit,
      jsonOptions: List[(String, String)],
      handler: JsCmd,
      attrs: (String, String)*): Elem = {

     

      val f = (ignore: String) => {
        val q = S.param("q").openOr("")
        val limit = S.param("limit").flatMap(asInt).openOr(10)
        PlainTextResponse(options(q, limit).map(item => item._1 + "|" + item._2 + "|" + item._3 + "|" + item._4 + "|" + item._5).mkString("\n"))
      }

      fmapFunc(SFuncHolder(f)) { func =>
        val what: String = encodeURL(S.contextPath + "/" + LiftRules.ajaxPath + "?" + func + "=foo")

        val id = Helpers.nextFuncName
        fmapFunc(SFuncHolder(onSubmit)) { hidden =>

          /* merge the options that the user wants */
          val jqOptions = ("minChars", "0") ::
            ("matchContains", "true") :: ("delay", "600") ::
            ("max", "20") ::
            Nil ::: jsonOptions
          val json = jqOptions.map(t => t._1 + ":" + t._2).mkString("{", ",", "}")
          val autocompleteOptions = JsRaw(json)

          val onLoad = JsRaw("""  
          jQuery(document).ready(function(){  
            var data = """ + what.encJs + """;  
            jQuery("#""" + id + """").autocomplete(data, """ + autocompleteOptions.toJsCmd + """).result(function(event, dt, formatted) {  
              jQuery("#searchValue").val(formatted);  
              jQuery("#""" + id + """").val(dt[1]);  
               if(dt[0] == "-1"){
              jQuery("#searchValue").val(dt[0]); 
              	jQuery("#""" + id + """").val(dt[1]);  
              	}else if(dt[0] == ""){
              jQuery("#searchValue").val(dt[0]); 
              	jQuery("#""" + id + """").val(dt[0]);  
              	}	""" + handler.toJsCmd + """
            });  
          });""")

          <span>
            <head>
              <link rel="stylesheet" href={ "/" + LiftRules.resourceServerPath + "/autocomplete/jquery.autocomplete.css" } type="text/css"/>
              <script type='text/javascript' src='js/jquery.autocomplete.js'></script>
              <script type="text/javascript">{ Unparsed(onLoad.toJsCmd) }</script>
            </head>

            {
              attrs.foldLeft(<input type="text" id={ id } value={ start }  placeholder={"Add "+category.getOrElse("")+" ...."} />)(_ % _)
            }<input type="hidden" name="searchValue" id="searchValue" value=""/>
            <input type="hidden" name={ hidden } id={ hidden } value={ start }/>
          </span>
        }
      }
    }
  }

}  

In the Snippet :

 AutoComplete("", getResults _, value => takeAction(value)
, List(("minChars" -> "3"), 
("formatItem", """function(row, i, max) { return "<table><tr><td><img src='" + row[2] + "' width='40' height='50' /> </td><td>" + row[1]+"<br>"+row[3]+"<br>"+row[4]+"</td><tr><table>"; }""")), SHtml.ajaxCall(
                JE.ValById("searchValue"), term => {
                  SHtml.ajaxInvoke(() => {
                    takeActionForItem(term)
                  })._2.cmd
                })._2.cmd)

def getResults(current: String, limit: Int): Seq[(String, String, String, String, String)] = {
...
}

In the above code , getResults is returning sequence of 5 strings . I have modified Autocomplete.Scala as per requirement . You can modified this file , if you want to make your Autocomplete widget to be more attractive .

Lift’s Auto complete Widget is basically a JQuery’s AutoComplete widget . You can use Jquery’s Autocomplete property in Autocomplete.Scala .

About Ayush Mishra

Ayush is the Sr. Software Consultant @ Knoldus Software LLP. In his 5 years of experience he has become developer with proven experience in architecting and developing web applications. Ayush has a Masters in Computer Application from U.P. Technical University, Ayush is a strong-willed and self-motivated professional who takes deep care in adhering to quality norms within projects. He is capable of managing challenging projects with remarkable deadline sensitivity without compromising code quality. .
This entry was posted in Agile, Java, Scala, Web. Bookmark the permalink.

2 Responses to Play with Lift’s Autocomplete Widget using Scala-One of the Best Feature of LiftWeb Framework

  1. Joe Stein says:

    What is the HTML side of this code to make it all work?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s