Zafu: KendoUI JSP autocomplete revisited


In Zafu: KendoUI JSP taglib + Couchbase (3) I have explained how to implement an autocomplete for accessing the name of the beers of Couchbase sample-beer database. That time I have used a pretty complex structure for mapping KendoUI filter.

But if the only thing that I need is sending whatever the user has typed so far, might not be worthwhile using such complex structure. If that’s your case, this is what you have to do.

Redefine parameterMap

In the previous post I have used the following function for generating the parameters to send to the server.

function kendoJson(d, t) {
    return "param=" + JSON.stringify(d);
}

Now, I have redefined it as:

function kendoJson(d, t) {
    return "name=" + d.filter.filters[0].value + "&limit=" + d.take;
}

I.e., only send the data typed so far as a parameter called name and limit as the number of results that is the value defined in kendo:dataSource JSP tag.

Get the parameters in the server

In the server now the parameters are much easier to retrieve since they arrive as two different parameters with the names name and limit.

String name = request.getParameter("name");
int limit = 10;
System.out.println("Limit:" + request.getParameter("limit"));
if (request.getParameter("limit") != null) {
    try {
        limit = Integer.parseInt(request.getParameter("limit"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

And then invoke the query as we explained in the last post:

Query query = new Query();
query.setIncludeDocs(false);
query.setStale(Stale.FALSE);
query.setDescending(false);
query.setReduce(false);
query.setSkip(0);
query.setLimit(limit);
query.setRangeStart(name)
ViewResponse result = client.query(view, query);
Iterator itr = result.iterator();

Zafu: KendoUI JSP taglib + Couchbase (3)


In the previous two previous posts of this serie, I’ve shown how to use KendoUI Grid with server-side paging and client paging, using the all new KendoUI JSP wrapper. It’s time to take a look into another of KendoUI widgets: autocomplete, and use it for creating an input that suggested the name of one of the beer provided in Couchbase sample-beer database.

KendoUI autocomplete using JSP taglib

Lets start showing all the JSP code for the dissecting it:

<kendo:autoComplete name="beer" dataTextField="name" ignoreCase="false">
    <kendo:dataSource pageSize="10" serverPaging="true" serverFiltering="true">
        <kendo:dataSource-transport parameterMap="kendoJson">
            <kendo:dataSource-transport-read url="/BeerNames"/>
        </kendo:dataSource-transport>
        <kendo:dataSource-schema type="json" data="data"/>
    </kendo:dataSource>
</kendo:autoComplete>

In kendo:autoComplete I’ve used two attributes:

  1. dataTextField value will be sent to the server as parameter and we use it for indicating the field of our document used for filtering.
  2. ignoreCase allow us to tell the server if the filtering should or not differentiate lowercase from uppercase characters.

In kendo:dataSource I used three attributes that allow me to control the filtering of the data. What I saying is:

  1. pageSize: I only want to retrieve a maximum of 10 records (name of beers).
  2. serverPaging: Paging is going to be done in the server, meaning that the server should pick that 10 and send to the client (to KendoUI) a maximum of 10 records.
  3. serverFiltering: the filtering (I mean, the server should choose those records that start with the typed characters and send me the next 10).

In kendo:dataSource-transport I have chosen to encode the parameters by myself to overcame the problem found with KendoUI JSP wrapper and the way that it sends the arguments to the server by default (see First “problem” in KendoUI wrapper in my post Zafu: KendoUI JSP taglib + Couchbase (2)).

Finally, in kendo:dataSource-schema I let KendoUI know that data is going to be received in JSON format and  in an object element called data. So, it is going to be something like this:

{"data":[
    { "name":"A. LeCoq Imperial Extra Double Stout 1999" },
    { "name":"A. LeCoq Imperial Extra Double Stout 2000" },
    { "name":"Abana Amber Ale" },
    { "name":"Abbaye de Floreffe Double" },
    { "name":"Abbaye de Saint-Martin Blonde" },
    { "name":"Abbaye de Saint-Martin Brune" },
    { "name":"Abbaye de Saint-Martin Cuvée de Noel" },
    { "name":"Abbaye de Saint-Martin Triple" },
    { "name":"Abbaye de St Amand" },
    { "name":"Abbey 8" }
]}

Where we see that the array of results is called data, beer names is returned as the value of the attribute name and we get 10 results.

Parameters sent to the server

But what is what I am going to receive into the server (the servlet)? It is going to be a parameter called <em>param</em> which value is something like:

{
    "take":    10,
    "skip":    0,
    "page":    1,
    "pageSize":10,
    "filter":  {
        "logic":  "and",
        "filters":[
            {
                "value":     "a",
                "operator":  "startswith",
                "field":     "name",
                "ignoreCase":false
            }
        ]
    }
}

That says that the server should take 10 results, skip the 0 first and the filtering condition are values that startwith an “a” in the column name and should not ignore case. Since there is only one condition the and is not important (these is used when filtering by several condition where we might choose that all condition must be fulfill -using and- or enough if one condition is met -using or-.

Using the parameters in the server

For a question of convenience I did create a Java class for accessing the conditions of the search. This class are something like this:

public class RequestParameters {
    public int take;
    public int skip;
    public int page;
    public int pageSize;
    public Filter filter = new Filter();
}

public class Filter {
    public String                     logic;
    public ArrayList filters = new ArrayList();
}

public class FilterCondition {
    public String  value;
    public String  operator;
    public String  field;
    public Boolean ignoreCase;
}

Where I divided the parameter sent in three classes: RequestParametersFilter and FilterCondition.

When the servlet receives the paramater, I fill this structure using:

Gson gson = new Gson();
RequestParameters params = gson.fromJson(request.getParameter("param"),
                                         RequestParameters.class);

And Couchbase query is filled as:

Filter filter = params.filter;
FilterCondition cond = filter.filters.get(0);
Query query = new Query();
query.setIncludeDocs(false);
query.setStale(Stale.FALSE);
query.setDescending(false);
query.setReduce(false);
query.setSkip(params.skip);
query.setLimit(params.take);
query.setRangeStart(cond.value)
ViewResponse result = client.query(view, query);
Iterator itr = result.iterator();

And I fill the response using:

PrintWriter out = response.getWriter();
out.println("{\"data\":[");

StringBuilder buf = new StringBuilder();
for (Object result : results) {
    buf.append(",{ \"name\":\"").append(((ViewRow) result).getKey()).append("\"}");
}
if (buf.length() > 0) {
    out.print(buf.substring(1));
}
System.out.println("buffer:" + buf);
out.println("]}");

Zafu: KendoUI JSP taglib + Couchbase (2)


Just a few hours after being presented KendoUI Q3 I wrote the first post on KendoUI JSP wrapper. It was a pretty simple grid that retrieved data from Couchbase 2.0. That first post showed how to get data from a Couchbase 2.0 view and displayed it in a grid doing the paging in the client. In that example that meant transfer almost 6000 records and then do the paging in the browser -not very smart for such volume-. This time, I will implement the paging in the server and transfer only a small group of records.

Zafu server-side paging

Step one, a brief introduction to what we need for implementing server-side paging and what KendoUI and Couchbase.

KendoUI server-side paging

Configuring a KendoUI grid for server-side paging is as easy as defining serverPaging as true in the DataSource definition used by our Grid (see documentation here or here). Something like:

<kendo:dataSource pageSize="10" serverPaging="true">
    <kendo:dataSource-transport>
        <kendo:dataSource-transport-read url="/ListBeer" type="GET"/>
    </kendo:dataSource-transport>
    <kendo:dataSource-schema data="data" total="total" groups="data">
        <kendo:dataSource-schema-model>
            <kendo:dataSource-schema-model-fields>
                <kendo:dataSource-schema-model-field name="name" type="string"/>
                <kendo:dataSource-schema-model-field name="abv" type="number"/>
                <kendo:dataSource-schema-model-field name="style" type="string"/>
                <kendo:dataSource-schema-model-field name="category" type="string"/>
            </kendo:dataSource-schema-model-fields>
        </kendo:dataSource-schema-model>
    </kendo:dataSource-schema>
</kendo:dataSource>

In the previous definition I specify both that the server will do paging (send a page at a time) and the size of each page defined in pageSize (defined as 10 in the previous example).

But, in addition when the DataSource loads data, it also needs to specify the number of records to skip from the dataset. What I will get in my servlet /ListBeer is four tuples of paramater-value:

  1. take the number of records to retrieve.
  2. skip the number of records to skip from the beginning of the DataSet.
  3. page the index of the current page.
  4. pageSize the number of records displayed on each page.

Couchbase 2.0 server-side paging

When we build a query in Couchbase 2.0, we define a series of parameter to configure it. This includes:

  1. setSkip for defining the number of elements to skip.
  2. setLimit for defining the number of records to retrieve.

The mapping between KendoUI and Couchbase 2.0 paging parameters is easy: KendoUI:skip maps into Couchbase:skip and KendoUI:take maps into Couchbase:limit.

New Java read code

The new read function (defined in the previous post) now is as follow:

public List read(String key, int skip, int limit) throws Exception {
    View view = client.getView(ViewDocument, ViewName);

    if (view == null) {
        throw new Exception("View is null");
    }

    Query query = new Query();
    if (key != null) {
        query.setKey(key);
    }
    if (skip >= 0) {
        query.setSkip(skip);
    }
    if (limit >= 0) {
        query.setLimit(limit);
    }
    query.setStale(Stale.FALSE);
    query.setIncludeDocs(true);
    query.setDescending(false);
    query.setReduce(false);

    ViewResponse result = client.query(view, query);
    Iterator<ViewRow> itr = result.iterator();
    return IteratorUtils.toList(itr);
}

Where skip is what I get in the servlet as request.getParameter(“skip”) and limit is request.getParameter(“take”).

First “problem” in KendoUI wrapper

NOTE: Please, remember that KendoUI JSP wrapper is a beta release which means (according the Wikipedia):

Beta (named after the second letter of the Greek alphabet) is the software development phase following alpha. It generally begins when the software is feature complete. Software in the beta phase will generally have many more bugs in it than completed software, as well as speed/performance issues. The focus of beta testing is reducing impacts to users, often incorporating usability testing. The process of delivering a beta version to the users is called beta release and this is typically the first time that the software is available outside of the organization that developed it.

So, having defects is not bad, it is something that we cannot avoid, and as far as it is usable (and so far it is) and the defects get fixed by the time the final release is introduced it is completely understandable.

The defect is the way we receive the parameters that is different that for traditional HTML / JavaScript usage but also a little odd.

Parameters in KendoUI HTML / Javascript

Parameters are sent to the server (unless you define your own parameter mapping) encoded in the URL as url?param1=value1&param2=value2&… (Ex: /ListBeer?pageSize=10&take=10&skip=0&page=1).

Parameters in KendoUI JSP wrapper

Parameters are sent to the server (unless you define your own parameter mapping) as a stringified JSON and this as a parameter: url?{param1:value1,param2:value2,…} (Ex: /ListBeer?{pageSize:10,take:10,skip:0,page:1}=).

And here I have two concerns:

  1. I should not change my servlet no mather I user HTML/JavaScript, ASP wrapper, JSP wrapper,…
  2. Sending a string serialized JSON object as a parameter name is not nice, if I want to send it as JSON I will send it in the value side of a parameter and not the name side.

Workaround

Have 2 different servlets (code paths) for parsing parameters:

  1. HTML/JavaScript
  2. JSP wrapper.

Fixes

  1. KendoUI fixes TransportTag.doEndTag to do not define as default parameterMap function a JSON.stringify but leave it empty (this is something that we **cannot do**).
  2. Define our own parameter map that sends the parameters encoded in the URL as HTML 5 / JavaScript does or as JSON but in the value side and with well-known parameter name.

Which one I do recommend…? Fix 1, and while KendoUI provide us a patched version, you fix it or you go with Fix 2.

<script type="text/javascript">
    function encodeParametersAsJSON(param) {
        return "param=" + JSON.stringify(param);
    }
</script>
...
<kendo:dataSource-transport parameterMap="encodeParametersAsJSON">
    <kendo:dataSource-transport-read url="/ListBeer" type="GET"/>
</kendo:dataSource-transport>

I do not recommend Workaround since you have to duplicate your java code.

Zafu: KendoUI JSP taglib + Couchbase (1)


A couple a days ago I attended KendoUI Q3 announcement. Being more in the Java than C# side I was very excited about JSP taglib (again, zillions of thanks).

Just one hour after finishing the presentation I connected to Couchbase 2.0 webinar on Couchbase App Development.

Few hours latter (about 4 working hours) I finished writing some code using both together.

Why took me almost 4 hours?

  1. Just as a little of feedback I admit that before this I didn’t write a single line of code for Couchbase (many for CouchDB using Java Client libraries as well as CouchApp) but they are not the same and they do not use the same Java Client library.
  2. I have written many lines of code for KendoUI using HTML5 + JavaScript (never ASP and C#), created new KendoUI widgets (and even fixed some bugs in KendoUI). BUT KendoUI JSP taglib is new, so I did not have experience on KendoUI JSP taglib neither on the server side, so a little of challenge.
  3. But this was not where I spend most of the time :-( It was because Couchbase Java Client (plus the libraries that come with it), is for Java 6 while KendoUI JSP taglib is for Java 7 so I had to figure out how to run both together under Apache Tomcat (and this took me more than 2 hours).

@toddanglin,@BrandonSatrom,@JohnBristowe,@burkeholland,@alex_gyoshev Would be possible to have your taglib **officially** release for Java 6 too? You just need to change a couple of lines of code ;-)

What I did in the remaining two hours was…

When you install Couchbase 2.0, you are prompted (at least I was) about installing a sample database (a list of almos 6000 beers!!!).

So, I’ve decide to display in a grid those (almost) 6000 records (name, abv, category and style) and being able to navigate them. NOTE: For this first example I will not be doing server side paging, filtering,… By the end of the experiment (I live in Europe) it was almost 1am and I went to sleep.

What did I need to write:

  1. Java Server Page (JSP) using KendoUI taglib (not much documentation on how to use it but looking their example was enough for me).
  2. Servlet invoked by KendoUI DataSource transport read for retrieving the data, this is Java and uses Couchbase Java Client Library.
  3. Couchbase view for extracting the list of beers (this is a MapReduce function).

Kendo UI JSP taglib

This is what I wrote.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="kendo" uri="http://www.kendoui.com/jsp/tags" %>
<html>
<head>
    <title>Powered by Zafu: OnaBai 2012 (c)</title>
    <!-- Kendo UI Web styles-->
    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css"/>
    <link href="styles/kendo.silver.min.css" rel="stylesheet" type="text/css"/>

    <!-- Kendo UI Web scripts-->
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.web.min.js" type="text/javascript"></script>

    <style type="text/css">
        html {
            font-family: "Arial", sans-serif;
            font-weight: 300;
            font-variant: normal;
            font-style: normal;
            font-size: 12px;
        }

        table[role="grid"] {
            font-size: 1em;
        }
    </style>
</head>
<body>
<kendo:window name="main" title="Powered by Zafu: OnaBai 2012 (c)" minHeight="450" minWidth="700" maxWidth="700">

    <kendo:grid name="grid" pageable="true" sortable="true" filterable="false" groupable="false">
        <kendo:grid-columns>
            <kendo:grid-column title="Name" field="name"/>
            <kendo:grid-column title="ABV" field="abv" format="{0:n1}" width="50px"/>
            <kendo:grid-column title="Style" field="style"/>
            <kendo:grid-column title="Category" field="category"/>
        </kendo:grid-columns>
        <kendo:dataSource pageSize="10">
            <kendo:dataSource-transport>
                <kendo:dataSource-transport-read url="/ListBeer" type="GET" contentType="application/json"/>
            </kendo:dataSource-transport>
            <kendo:dataSource-schema data="data" total="total" groups="data">
                <kendo:dataSource-schema-model>
                    <kendo:dataSource-schema-model-fields>
                        <kendo:dataSource-schema-model-field name="name" type="string"/>
                        <kendo:dataSource-schema-model-field name="abv" type="number"/>
                        <kendo:dataSource-schema-model-field name="style" type="string"/>
                        <kendo:dataSource-schema-model-field name="category" type="string"/>
                    </kendo:dataSource-schema-model-fields>
                </kendo:dataSource-schema-model>
            </kendo:dataSource-schema>
        </kendo:dataSource>
    </kendo:grid>
    <div style="position: absolute; bottom: 5px;">Powered by Zafu, Couchbase 2.0 & KendoUI</div>
</kendo:window>
</body>
</html>

This JSP is equivalent to this HTML (what I have had to write in the past).

<html>
<head>
    <title>Powered by Zafu: OnaBai 2012 (c)</title>
    <!-- Kendo UI Web styles-->
    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css"/>
    <link href="styles/kendo.black.min.css" rel="stylesheet" type="text/css"/>

    <!-- Kendo UI Web scripts-->
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.web.min.js" type="text/javascript"></script>

    <style type="text/css">
        html {
            font-family: "Arial", sans-serif;
            font-weight: 300;
            font-variant: normal;
            font-style: normal;
            font-size: 12px;
        }

        table[role="grid"] {
            font-size: 1em;
        }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#main").kendoWindow({
                title:    "Powered by Zafu: OnaBai 2012 (c)",
                minHeight:450,
                minWidth: 700,
                maxWidth: 700
            });

            $("#grid").kendoGrid({
                columns:   [
                    { field:"name", title:"Name" },
                    { field:"abv", title:"ABV", format:"{0:n1}", width:"50" },
                    { field:"style", title:"Style", format:"{0:n1}" },
                    { field:"category", title:"Category", format:"{0:n1}" }
                ],
                pageable:  true,
                sortable:  true,
                filterable:false,
                groupable: false,
                dataSource:{
                    pageSize: 10,
                    transport:{
                        read:{
                            url:        "/ListBeer",
                            type:       "GET",
                            contentType:"application/json"
                        }
                    },
                    schema:   {
                        data:  "data",
                        total: "total",
                        groups:"data",
                        model: {
                            fields:{
                                name:    { type:"string" },
                                abv:     { type:"number" },
                                style:   { type:"string" },
                                category:{ type:"string" }
                            }
                        }
                    }
                }
            })
        });
    </script>
</head>
<body>
<div id="main">
    <div id="grid"></div>
    <div style="position: absolute; bottom: 5px;">Powered by Zafu, Couchbase 2.0 & KendoUI</div>
</div>
</body>
</html>

MapReduce

function (doc, meta) {
  if (doc.type && doc.type == "beer" && doc.name) {
     emit(doc.name, doc.id);
  }
}

Couchbase 2.0 Java Client: View invocation

    public List read(String key) throws Exception {
        View view = client.getView(ViewDocument, ViewName);

        if (view == null) {
            throw new Exception("View is null");
        }

        Query query = new Query();
        // Set the key for the query based on beer name
        if (key != null) {
            query.setKey(key);
        }
        query.setStale(Stale.FALSE);
        query.setIncludeDocs(true);
        query.setDescending(false);

        ViewResponse result = client.query(view, query);
        Iterator<ViewRow> itr = result.iterator();
        return IteratorUtils.toList(itr);
    }

Conclusion

Positive

  1. It works!
  2. It was not that hard!
  3. Both fit pretty nice and both are easy to start with!
  4. Even that tag libraries are pretty verbose having snippets you can type it quite fast.
  5. My IDE recognized KendoUI taglib and coloring and typing was pretty fast.

Negative

  1. KendoUI, please release Java 6 version (I know that it is pretty old but as you can see there people developing new products and  still using it).
  2. Couchbase, please upgrade to Java 7. Java 6 is pretty old otherwise it will look like Windows XP.

Pending

  1. Play with more widgets and server side paging / filtering / …
  2. See how it fits (if it actually does, I don’t think so) with my own Widgets (I have a few that I use a lot).

Neutral

  1. I think that would be nice a series of blogs exploiting the idea of JSP taglib… maybe I start one… is not that much the question of documentation as writing Guides on how to use if for practical cases.
Follow

Get every new post delivered to your Inbox.

Join 57 other followers

%d bloggers like this: