Telerik blogs

In the last two weeks we have gone from zero to HTML5 hero with the Kendo UI JSP Wrappers . That was corny I know, but lets take a look at what we did in each of the previous two parts...

Part 1 ( Read It )

  • Created a basic JSP Dynamic Web Application
  • Added a data access layer
  • Created the first servlet
  • Used the Kendo UI TreeView to lazy load hierarchical data

Part 2 ( Read It )

  • Expanded the data access layer to include products with relationships to the categories and suppliers tables
  • Wired up a Kendo UI Grid to the products table
  • Implemented server side paging for wicked fast presentation of large data sets

In last week's post, we also talked about how a great many applications are not much more than a grid laid on top of a relational database. The grid needs to expose an editing surface for users where they can safely manage their own data in an interface that stitches back together the data that has been broken apart for storage.

Editing In A Kendo UI Grid

Today we will look at enabling more editing features in the Kendo UI grid with the JSP wrappers.

You can grab the code for today's article from the GitHub Repo (It’s part 3).

To turn editing on in the grid, it's pretty trivial. You just need to set the editable: true attribute on the grid.

<kendo:grid name="products" pageable="true" editable="true">
<kendo:dataSource pageSize="5" serverPaging="true">
<kendo:dataSource-transport read="api/products">
<kendo:dataSource-transport-parameterMap>
.....

 

What you will notice when you do that is that when you click on an item in the grid, it becomes editable. What's even better is that Kendo UI is smart enough to recognize that you have numeric data types so it gives you a numeric textbox. It also automatically gives you a checkbox for the boolean field.

gridedit1

However, the Supplier and Category are plain text fields. We don't want this. We want them to be able to choose from a list of already predefined values for categories and suppliers in the database. Also, the grid is giving us a numeric textbox for the price, but we don't want a plain number there, we want currency.

Formatting grid columns

We can solve the price issue right away by specifying a format for the column. In this case, we want currency so all we need to do is specify a "c". Format are specified as {0:format} .

<kendo:grid-columns>
<kendo:grid-column title="Name" field="ProductName" />
<kendo:grid-column title="Supplier" field="Supplier.SupplierName" />
<kendo:grid-column title="Category" field="Category.CategoryName" />
<kendo:grid-column title="Price" field="UnitPrice" format="{0:c}" />
<kendo:grid-column title="# In Stock" field="UnitsInStock" />
<kendo:grid-column title="Discontinued" field="Discontinued" />
</kendo:grid-columns>

 

We could specify a more restrictive format for the column You can refer to the Kendo UI formatting docs for all the available options and formats.

The supplier and category fields seem fine right now, but when we go into edit mode, they are displayed as plain text. We need dropdowns instead. We are currently bound to the SupplierName and CategoryName fields. Instead, we are going to bind to the Supplier and Category objects themselves. Since we are now bound to an object instead of a value, we need to specify a template so the grid doesn't give us [object Object] .

<kendo:grid-columns>
<kendo:grid-column title="Name" field="ProductName" />
<kendo:grid-column title="Supplier" field="Supplier" template="#: Supplier.SupplierName #" />
<kendo:grid-column title="Category" field="Category" template="#: Category.CategoryName #" />
<kendo:grid-column title="Price" field="UnitPrice" format="{0:c}" />
<kendo:grid-column title="# In Stock" field="UnitsInStock" />
<kendo:grid-column title="Discontinued" field="Discontinued" />
</kendo:grid-columns>

 

Kendo UI Templates are a way to specify how the data is output. You use #: on the left, put your binding expression in the middle, then close it off with another # . The binding expression is the specific field in the source object you want the value from.

We could mix HTML in with this as well if we wanted to. For instance, if we wanted to display a checkmark in the Discontinued column instead of just true/false, we could give the grid a template.

<kendo:grid-columns>
<kendo:grid-column title="Name" field="ProductName" />
<kendo:grid-column title="Supplier" field="Supplier" template="#: Supplier.SupplierName #" />
<kendo:grid-column title="Category" field="Category" template="#: Category.CategoryName #" />
<kendo:grid-column title="Price" field="UnitPrice" format="{0:c}" />
<kendo:grid-column title="# In Stock" field="UnitsInStock" />
<kendo:grid-column title="Discontinued" field="Discontinued" 
template="
# if (data.Discontinued) { # 
<span class='k-icon k-i-tick'></span> 
# } #" />
</kendo:grid-columns>

 

gridedit2

These template can execute JavaScript logic like I have included above. To identify JavaScript blocks, you open with a # and the close with a # before you return to straight HTML. Refer to the Kendo UI Template documentation for more information on how you can use templates to get complete control over your UI.

Now that we have the Products and Categories templates working, we need to address their edit mode. Right now when the grid goes into edit mode, it will display [object Object] again for Supplier and Category. We need to specify Custom Editors for these fields.

Drop down below the grid and open a script tag. Inside of it create a function called supplierEditor that takes in a container parameter and an options parameter.

.....
</kendo:grid-columns>
</kendo:grid>

<script>

function supplierEditor(container, options) {
$("<input data-text-field='SupplierName' data-value-field='SupplierID' data-bind='value:" + options.field + "' />")
.appendTo(container)
.kendoDropDownList({
dataSource: {
transport: {
read: "api/suppliers"
}
}
});
};

</script>

 

This function does a few things so lets break it down.

  1. Creates a new input element with jQuery. This new element has all of it's configuration in the HTML by way of data attributes. Any setting on a Kendo UI Widget can be declared in the HTML by using a data attribute. Any camel cased settings are separated by dashes (i.e. dataTextField becomes data-text-field)
  2. The new input is appended to the container which is the grid row
  3. The input is transformed into a Kendo UI DropDown List and reads from the api/suppliers endpoint which doesn't exist yet.

Since the Category field is nearly identical to the Supplier, just copy the function and change any references of Supplier to Category.

function categoryEditor(container, options) {
$("<input data-text-field='CategoryName' data-value-field='CategoryID' data-bind='value:" + options.field + "' />")
.appendTo(container)
.kendoDropDownList({
dataSource: {
transport: {
read: "api/categories"
}
}
});
};

 

Lets now define the two endpoints that we need for the DropDown Lists. This would be the api/suppliers and api/categories .

Creating the necessary repositories

If you have been following this series, you know that we will need to create data access endpoints for our Suppliers and Categories.

Right-click the repositories package and create a new class called SuppliersRepository . Make sure you change the Superclass to our Repository base class.

supplierrespository

Create a doList method that returns a list of all the Suppliers from the database.

Remember that I am passing the path string in for my file based database. You will most likely not have to do this so you wouldn't need the constructor like I have it.

package repositories;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class SuppliersRepository extends Repository {

  public SuppliersRepository(String path) {
    super(path);
  }

  public List<models.supplier> listSuppliers() throws SQLException {

    PreparedStatement stmt = null;
    ResultSet rs = null;

    // prepare a list of suppliers to populate as a return value
    List<models.supplier> suppliers = new ArrayList<models.supplier>();

    try {

      // set sql statement
      String sql = "SELECT SupplierID, CompanyName AS SupplierName FROM Suppliers";

      // prepare the string for safe execution
      stmt = super.conn.prepareStatement(sql);

      // execute the statement into a ResultSet
      rs = stmt.executeQuery();

      // loop through the results
      while(rs.next()) {

        // create a new supplier object
        models.Supplier supplier = new models.Supplier();

        // populate it with the values from the database
        supplier.setSupplierID(rs.getInt("SupplierID"));
        supplier.setSupplierName(rs.getString("SupplierName"));

        // add the supplier to the return list
        suppliers.add(supplier);
      }
    }
    finally {
      // close out all connection related instances
      stmt.close();
      rs.close();
    }

    // return the list of suppliers
    return suppliers;
  }
}

 

Now create a repository for the Categories. It's nearly identical to the Suppliers repository with a few subtle differences.

package repositories;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class CategoriesRepository extends Repository {

  public CategoriesRepository(String path) {
    super(path);
  }

  public List<models.Category> listCategories() throws SQLException {

    PreparedStatement stmt = null;
    ResultSet rs = null;

    // create a list of categories to return
    List<models.Category> categories = new ArrayList<models.Category>();

    try {

      // create the sql string
      String sql = "SELECT CategoryID, CategoryName FROM Categories";

      // prepare the string for safe execution
      stmt = super.conn.prepareStatement(sql);

      // execute the sql and return the results to the ResultSet
      rs = stmt.executeQuery();

      // iterate through the result set
      while(rs.next()) {

        // create a new category object
        models.Category category = new models.Category();

        // populate it's values from the database
        category.setCategoryID(rs.getInt("CategoryID"));
        category.setCategoryName(rs.getString("CategoryName"));

        // add it to the list of categories
        categories.add(category);
      }
    }
    finally {
      // close out all connection related instances
      stmt.close();
      rs.close();
    }

    // return the list of categories
    return categories;

  }
}

 

Adding the servlets

By now you should be comfortable creating new servlets. Right-click the api package and select New/Servlet. Call it Suppliers and change the url mapping to /api/suppliers.

suppliersservlet

Include a reference to the SupplierRepository at the top. Also include the Gson library for returning JSON. Again, if you have been following along, this should all be old hat. Remember that I do some server path magic in my samples because I am using a file based database. In the doGet method, return the list of suppliers in JSON format.

package api;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;

import repositories.SuppliersRepository;

/**
 * Servlet implementation class Suppliers
 */
@WebServlet("/api/suppliers")
public class Suppliers extends HttpServlet {
  private static final long serialVersionUID = 1L;
    private repositories.SuppliersRepository _repository;   
    private Gson _gson = new Gson();

    /**
     * @see HttpServlet#HttpServlet()
     */
    public Suppliers() {
        super();
        // TODO Auto-generated constructor stub
    }

    public void init() throws ServletException {
      super.init();
      _repository = new SuppliersRepository(this.getServletContext().getRealPath("data/sample.db"));
    }

  /**
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // create a list of suppliers to send back as JSON
    List<models.Supplier> suppliers = new ArrayList<models.Supplier>();

    try {

      // get the suppliers from the database
      suppliers = _repository.listSuppliers();

      // set the content type we are sending back as JSON
      response.setContentType("application/json"); 

      // print the content to the response
      response.getWriter().print(_gson.toJson(suppliers));

    } catch (SQLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      response.sendError(500);
    }

  }

}
 

Now we need a Categories servlet.

package api;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import repositories.CategoriesRepository;

import com.google.gson.Gson;

/**
 * Servlet implementation class Categories
 */
@WebServlet("/api/categories")
public class Categories extends HttpServlet {
  private static final long serialVersionUID = 1L;
    private repositories.CategoriesRepository _repository = null;
    private Gson _gson = new Gson();

    /**
     * @see HttpServlet#HttpServlet()
     */
    public Categories() {
        super();
    }

    public void init() throws ServletException {
      super.init();
      _repository = new CategoriesRepository(this.getServletContext().getRealPath("data/sample.db"));
    }

  /**
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    List<models.Category> categories = new ArrayList<models.Category>();

    try {

      categories = _repository.listCategories();

      // set the content type we are sending back as JSON
      response.setContentType("application/json"); 

      // print the content to the response
      response.getWriter().print(_gson.toJson(categories));

    } catch (SQLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      response.sendError(500);
    }

  }

}

 

With all that plumbing work out of the way, we just need to have the grid use these new custom editors. We do that by specifying the editor attribute for the Supplier and Category columns.

<kendo:grid-columns>
  <kendo:grid-column field="ProductName" title="Product"></kendo:grid-column>
  <kendo:grid-column field="Supplier" title="Supplier" editor="supplierEditor" template="#: Supplier.SupplierName #"></kendo:grid-column>
  <kendo:grid-column field="Category" title="Category" width="150px" editor="categoryEditor" template="#: Category.CategoryName #"></kendo:grid-column>
  <kendo:grid-column field="UnitPrice" title="Price" format="{0:c}" width="75px"></kendo:grid-column>
  <kendo:grid-column field="UnitsInStock" title="# In Stock" width="80px"></kendo:grid-column>
  <kendo:grid-column field="Discontinued" title="Discontinued" width="100px"></kendo:grid-column>
  <kendo:grid-column>
    <kendo:grid-column-command>
      <kendo:grid-column-commandItem name="edit"></kendo:grid-column-commandItem>
      <kendo:grid-column-commandItem name="destroy"></kendo:grid-column-commandItem>
    </kendo:grid-column-command>
  </kendo:grid-column>
</kendo:grid-columns>

 

I also adjusted the column widths a bit to make things look a tad cleaner. Edit mode now displays a drop down for the Suppliers and Categories.

Screen Shot 2013-02-18 at 4.06.04 PM

We have successfully joined all of the backend data into a cohesive interface for our users.  However lets tweak the edit interface just a bit before we actually wire everything up.

Altering the default edit mode

We can edit a row just by clicking into it, but then each cell is in edit mode individually. I want the whole row in edit mode. To do this, we need to set the grid edit mode to "inline".

<kendo:grid name="products" pageable="true">
  <kendo:grid-editable mode="inline" />
  ....

 

Now clicking on a row will do nothing. We need to add a button which will put the row into edit mode. We do this with Command Columns. Let's also add a delete button while we're at it.  Delete is referred to as Destroy throughout Kendo UI as delete is a JavaScript reserved word.

....
<kendo:grid-column field="UnitsInStock" title="# In Stock" width="80px"></kendo:grid-column>
  <kendo:grid-column title="Discontinued" field="Discontinued" width="100px"
             template="# if (data.Discontinued) { # <span class='k-icon k-i-tick'></span> # } #" />
  <kendo:grid-column>
    <kendo:grid-column-command>
      <kendo:grid-column-commandItem name="edit"></kendo:grid-column-commandItem>
      <kendo:grid-column-commandItem name="destroy"></kendo:grid-column-commandItem>
    </kendo:grid-column-command>
  </kendo:grid-column>
</kendo:grid-columns>

 

Now we get a column with Edit and Delete buttons.

grid-edit-delete

Now clicking a row will put it in update mode. When the grid switches to update mode, the Edit and Delete buttons will turn to Update and Cancel.

editlooksbad

I don't like the way this looks though. The numeric editors are cutting off their content and the buttons stack on each other inside of the column. We can get into adjusting column widths here, but that's tedious. Another option is to just display a popup window for the user to edit in. The grid allows us to do this by simply specifying popup as the edit mode.

<kendo:grid name="products" pageable="true">
  <kendo:grid-editable mode="popup" />

 

grid-edit-pretty

And just like that you get a nice modal window with all the fields aligned, all the labels aligned right and plenty of room for editing.

Grab the source

As always, you can grab the source for this project from the GitHub repo. This is part 3 by the way.

Coming up next

Next we'll look at wiring all this up to the server. We also need to add the ability to create items which is going to take us into DataSource Model territory where we will also be able to leverage the Kendo UI validation framework for easy field validation.


Burke Holland is the Director of Developer Relations at Telerik
About the Author

Burke Holland

Burke Holland is a web developer living in Nashville, TN and was the Director of Developer Relations at Progress. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke worked for Progress as a Developer Advocate focusing on Kendo UI.

Comments

Comments are disabled in preview mode.