Telerik blogs

This article is going to wrap up what you need to know to get started with the JSP Wrappers for Kendo UI. Let's do just a quick review.

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
  • Used the "popup" edit mode to improve the editing interface

Part 3 (Read It)

  • Refactored the Java a bit to use some inheritance which reduces the amount of code that I have to duplicate
  • Enabled editing in the Kendo UI Grid
  • Used custom editors to display heterogeneous data

In this final installment, I'm going to be adding in all of the necessary actions to allow users to edit, create, and delete items from my grid. This article builds heavily on the first three, so if you find yourself late to the conversation, might I suggest giving the prior entries a quick read.

Persisting editing changes

The popup editor that we have now is really nice. It's got plenty of real-estate, and all of the forms controls are laid out cleanly so we don't have to fight with CSS or tables.

jsp4_1

You might also enjoy the kitty in the upper right-hand side of that screenshot. I know I do. That's because I have Chrome profiles setup. You can set them up yourself and get your very own kitty.

However if you click the Update button, nothing happens. This is due to the fact that the grid currently has no idea what we want it to do when this button is clicked. What it's going to be looking for is a defined update transport on the data source.

...
<kendo:dataSource serverPaging="true" pageSize="10">
  <kendo:dataSource-transport>
    <kendo:dataSource-transport-read url="api/products">
    </kendo:dataSource-transport-read>
    <kendo:dataSource-transport-update url="api/products?update" type="POST">
    </kendo:dataSource-transport-update>
...          

 

We need to stop a minute and talk about the update transport settings, because there is actually some pretty important stuff buried in there.

Transports

The transports are broken out into their own elements so that I can provide extra attributes. In the case of the update, it's the type attribute. I am going to be calling the same URL that I did for the read to do the update. In fact, I'm going to be calling that same url for create and delete eventually. How does this work?

The type attribute here specifies that when the servlet responds to this request, it should be using the doPost method as we have specified that this is a POST and not a GET (GET is the default). Additionally, any parameters that we send will go in the body of the request, not the query string. Don’t worry, Kendo UI is going to handle all of this for you. The only thing we need to do is tell the doPost what sort of action we want to do. This is why I am passing update on end of the query string. I can still read the query string in the servlet, we just can't parse parameters off if it as easily as I did in the doGet.

I realize that might be a bit confusing if you are new to HTTP requests and the way that they are made. Don't worry thought, its actually very simple. You can essentially make 4 types of requests from a browser. You can do a GET, POST, PUT, or DELETE. If you are using a framework like Spring MVC, you can use the PUT for a create and the DELETE for a delete. However, plain servlets do not provide a way to do this very easily. It's going to be better for me to stick with a POST and just determine my action from the query string.

Now clicking the button will execute a request to the server.

jsp4_2

 

I just need to tell the server what to do when it receives this request.

Implement the doPost method

Open the Products servlet. If you don't already have a doPost method, add one.

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  // handle CRUD operations here
}

 

Inside of this doPost method, we are going to get the variables from the body of the request and map them into a Products model object. Doing this helps us validate that the data we are getting from the client actually maps to a product object. It's highly advisable that you do server-side data validation in this method as well. We're going to implement it on the client in a just a bit, but always validate your data on both the client and the server.

I created a private method in the servlet which just maps the request parameters into a Product model object.

private models.Product parseRequest(HttpServletRequest request) {

  models.Product product = new models.Product();

  // VALIDATION SHOULD BE DONE HERE AS WELL

  // parse the rest of the information off of the request
  product.setSupplier(new models.Supplier(Integer.parseInt(request.getParameter("Supplier[SupplierID]")), 
      request.getParameter("Supplier[SupplierName]")));
  product.setCategory(new models.Category(Integer.parseInt(request.getParameter("Category[CategoryID]")), 
      request.getParameter("Category[Categoryname]")));
  product.setUnitPrice(Float.parseFloat(request.getParameter("UnitPrice")));
  product.setUnitsInStock(Integer.parseInt(request.getParameter("UnitsInStock")));
  product.setDiscontinued(Boolean.parseBoolean(request.getParameter("Discontinued")));
  product.setProductName(request.getParameter("ProductName"));

  return product;
}

 

It's pretty easy to just read parameters from the request that Kendo UI sends using the servlet getParameter method. How do I know what these parameters are? Simply look at the request in your browser tools. The screenshot above clearly shows me what parameters I am dealing with.

You need to be very comfortable using your browser development tools when doing HTML5 development, or you will be flying blind. If you are on Windows, you can use Fiddler, which will make working with AJAX requests an absolutely sublime experience.

Update repository method

Of course, I also need methods in the data access layer (something that I've called "repositories"). I'm going to implement the update method. While I'm at it, I'm also going to implement create and delete methods, because I'll be needing those very soon.

// update products method
public models.Product doUpdateProduct(models.Product product) throws Exception {

  // create a prepared statement object
  PreparedStatement stmt = null;

  try {

    // build the sql needed for the update
    String sql = "UPDATE Products SET SupplierID = ?, CategoryID = ?, ProductName = ?, " +
           "UnitPrice = ?, UnitsInStock = ?, Discontinued = ? " +
           "WHERE ProductID = ?";

    // prepare the statement for safe execution
    stmt = super.connection.prepareStatement(sql);

    // map the parameters into the query
    stmt.setInt(1, product.getSupplier().getSupplierID());
    stmt.setInt(2, product.getCategory().getCategoryID());
    stmt.setString(3, product.getProductName());
    stmt.setFloat(4, product.getUnitPrice());
    stmt.setInt(5, product.getUnitsInStock());
    stmt.setBoolean(6, product.getDiscontinued());
    stmt.setInt(7, product.getProductID());

    // execute the update
    stmt.executeUpdate();
  }
  catch (Exception e) {
    e.printStackTrace();
    throw new Exception(e.getMessage());
  }
  finally {
    // close all necessary connection related instances
    stmt.close();
  }

  return product;
}

 

The create method has a bit of a twist to it in that what I need back is the id of the item that was just generated. In order to get that, I have to first tell the prepared statement that I'm going to be wanting it, then I have to ask for it after the statement is executed.

// product create method
public int doCreateProduct(models.Product product) throws Exception {

  // set a default id value
  int id = 0;

  //create prepared statement and result set objects
  PreparedStatement stmt = null;
  ResultSet rs = null;

  try {

    // build the sql string
    String sql = &quot;INSERT INTO Products (SupplierID, CategoryID, &quot; +
           &quot;ProductName, UnitPrice, UnitsInStock, Discontinued ) &quot; +
           &quot;VALUES (?, ?, ?, ?, ?, ?)&quot;;

    // prepare the statement for safe execution, specifying that we
    // want the auto-generated id from the database returned.
    stmt = super.connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

    // map the product object to the sql
    stmt.setInt(1, product.getSupplier().getSupplierID());
    stmt.setInt(2, product.getCategory().getCategoryID());
    stmt.setString(3, product.getProductName());
    stmt.setFloat(4, product.getUnitPrice());
    stmt.setInt(5, product.getUnitsInStock());
    stmt.setBoolean(6, product.getDiscontinued());

    // execute the create statement
    int rows = stmt.executeUpdate();
    // if no rows were returned, it failed
    if (rows == 0) {
      throw new SQLException(&quot;Unable to create product&quot;);
    }

    // get the generated key for this item
    rs = stmt.getGeneratedKeys();

    // pull the key off the result set
    if (rs.next()) {
      id = rs.getInt(1);
    }
    else {
      throw new SQLException(&quot;Unable to create product. No auto-genereated key obtained&quot;);
    }
  }
  finally {
    // close all connection related instances
    stmt.close();
    rs.close();
  }

  // return the id that was or wasn&#039;t created
  return id;
}

 

The delete is nice and simple. There is nothing to return from this method. It either worked or it didn't.

// the delete method
public void doDeleteProduct(int productId) throws SQLException {

  // create a prepared statement object
  PreparedStatement stmt = null;

  try {

    // build the simple sql statement
    String sql = "DELETE FROM Products WHERE ProductID = ?";

    // prepare the statement for safe execution
    stmt = super.connection.prepareStatement(sql);

    // set the id of the product to delete
    stmt.setInt(1, productId);

    // execute the delete
    stmt.executeUpdate();
  }
  finally {
    // close all connection related instances
    stmt.close();
  }
}

 

These methods are verbose and there is no doubt about that. Building an entire application this way might result is some carpel tunnel. That's why developers usually choose a framework which handles so much of this manual labor for them. To avoid getting bogged down in the specifics of some specific framework that might not be applicable to everyone, I'm doing things the hard way.

Calling the edit method from the servlet

I've got all the CRUD statements I need in the data access layer, and that's great because calling them from the servlet is nice and easy. At this time I just have to handle the edit.

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

  try {

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

      // get the product id off of the request. only a delete or
      // update will contain the product id
      int productId = request.getParameter("ProductID") == "" ? 0 : Integer.parseInt(request.getParameter("ProductID"));

      // get the type of operation off the query string
      String type = request.getQueryString().trim();

      // parse the rest of the request into a product object
      models.Product product = parseRequest(request);

      // if the query string specified an update
      if (type.equals("update")) {
        // add the product id onto the model and update the product
        product.setProductID(productId);
        product = _repository.doUpdateProduct(product);

        response.getWriter().print(_gson.toJson(product));
    } 
  }
  catch (Exception e) {
    e.printStackTrace();
    // if something went wrong, send back a server error
    response.sendError(500);
  }

}

 

Edits in the grid will now be persisted in the database. The editor sends the product and it's updated properties over to the database. It only does this if a value is actually changed. If you bring up the edit window and don't change anything, it's smart enough to know that and not issue an unnecessary update to the server.

Validation with Kendo UI

There are some fields that I want to validate. I don't want people to be able to completely null out the product name. Right now, they can do that. Kendo UI will do this for me, but I first need to define my constraints in the model of the grid datasource. Once I specify the validation rules in the model, Kendo UI will not let the CRUD actions happen unless these validation rules are met.

....
<kendo:dataSource-schema-model id="ProductID">
  <kendo:dataSource-schema-model-fields>
    <kendo:dataSource-schema-model-field name="ProductName">
      <kendo:dataSource-schema-model-field-validation required="true"/>
    </kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="Supplier"></kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="Category"></kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="UnitPrice" type="number"></kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="UnitsInStock" type="number"></kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="Discontinued" type="boolean"></kendo:dataSource-schema-model-field>
  </kendo:dataSource-schema-model-fields>
</kendo:dataSource-schema-model>
....

 

A Kendo UI model is very similar to a server-side model class. It wants to know which field is the id, or primary key. Then I specify each of the fields in the model, and which type they are if they are something other than a standard string. For the ProductName, I specify that this field is required.

Now If I try and submit a row in edit mode with a blanked out product name, it will give me a message which kindly informs me that this is not allowed.

JSP4_3

Kendo UI provides very granular control over validation. I encourage you to read through the documentation on the validation for models. It's a tutorial in and of itself, but they are rather easy to implement and customize once you know how a basic validation rule works in a model. Now you do!

Create and delete

I need to add in definitions for the transport on the datasource on how to handle a create and a delete. Kendo UI refers to a delete as a destroy because delete is a reserved word in JavaScript.

....
<kendo:dataSource-transport>
  <kendo:dataSource-transport-read url="api/products"></kendo:dataSource-transport-read>
  <kendo:dataSource-transport-create url="api/products?create" type="POST"></kendo:dataSource-transport-create>
  <kendo:dataSource-transport-update url="api/products?update" type="POST"></kendo:dataSource-transport-update>
  <kendo:dataSource-transport-destroy url="api/products?delete" type="POST"></kendo:dataSource-transport-destroy>
</kendo:dataSource-transport>
....

 

Let's round this out by adding in the necessary UI for the delete and create actions.

The delete

The delete button is already there next to the edit button. Nothing else is required of me at this point for a delete.

The create

For creating a product, I want to add a toolbar at the top of the grid with a convenient "create" button which opens the popup window.

.....
<kendo:grid-editable mode="popup"/>
<kendo:grid-toolbar>
  <kendo:grid-toolbarItem name="create" />
</kendo:grid-toolbar>
<kendo:dataSource serverPaging="true" pageSize="10">
......

 

That small configuration change to the markup will do the trick. Kendo UI will take care of the rest.

I do need to tell the model what values it should use for the Category and Supplier, because as you will recall, these are not simply fields but actually objects consisting of both a name and an id. I can use Java for that by opening a tag and creating a new supplier and category model object for the default value.

<kendo:dataSource-schema-model id="ProductID">
  <kendo:dataSource-schema-model-fields>
    <kendo:dataSource-schema-model-field name="ProductName">
      <kendo:dataSource-schema-model-field-validation required="true"/>
    </kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="Supplier" defaultValue="<%= new Supplier() %>"></kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="Category" defaultValue="<%= new Category() %>"></kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="UnitPrice" type="number"></kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="UnitsInStock" type="number"></kendo:dataSource-schema-model-field>
    <kendo:dataSource-schema-model-field name="Discontinued" type="boolean"></kendo:dataSource-schema-model-field>
  </kendo:dataSource-schema-model-fields>
</kendo:dataSource-schema-model>

 

Handling create and delete on the server

I've already implemented the create and delete methods in the data access layer, so all that's left to do is call them in the event that I request one of those actions in the query string. The completed doPost method looks like this:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

  try {

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

      int productId = request.getParameter("ProductID") == "" ? 0 : Integer.parseInt(request.getParameter("ProductID"));

      // get the type of operation
      String type = request.getQueryString().trim();

      // parse the rest of the request into an employee object
      models.Product product = parseRequest(request);

      if (type.equals("update")) {
        // add the product id onto the model and update the product
        product.setProductID(productId);
        product = _repository.doUpdateProduct(product);

        response.getWriter().print(_gson.toJson(product));
      }
      if (type.equals("create")) {
        //create the product
        productId = _repository.doCreateProduct(product);
        response.getWriter().print(_gson.toJson(productId));
      }
      if (type.equals("delete")) {
        // delete the product
        System.out.println(type);
        _repository.doDeleteProduct(productId);
      }

  }
  catch (Exception e) {
    e.printStackTrace();
    response.sendError(500);
  }

}

 

Get the completed source code

Grab the completed project for this final tutorial from the GitHub repo. It contains everything that has been covered so far.

And you're done!

That's all there is to it. You have a complete editing interface that you can now begin to customize to your hearts content. If you made it through all 4 of these tutorials, you have already examined some of the most complex scenarios you will likely encounter, and you are ready to go forth and unleash your inner HTML5 creativity.

Kendo UI doesn't build your application for you. Instead, it simply helps you to realize your full potential as a web and mobile developer by taking care of the details that can take the fun out of programming.  This is what allows you to concentrate on bringing your ideas and requirements to life as only you can.

Be sure to sign up for the launch webinar for the JSP Wrappers and MANY other new things in Kendo UI. It's just around the corner and you don't want to miss it. Besides, you stand a chance at winning some pretty awesome prizes and we love to give them out! 

Spring Keynote Banner


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.