Grails Cookbook - A collection of tutorials and examples

Grails 3 Vue.js AJAX Table Examples

With all the popular JavaScript framework, I believe that Vue.js stands out as a powerful yet easy to learn framework. Other popular JavaScript framework have high learning curve. It takes weeks to understand and write simple stuff. Vue.js is a breath of fresh air because it provides excellent features yet it can be learned within minutes. In fact, I have written the example in this tutorial in less than 1 hour and having no prior Vue.js background. Here is an example on how to write in Grails 3 and Vue.js an AJAX table that displays records of a database table in paginated form.

Domain Model and Bootstrap

Let's start by creating a domain class Person, here is the sample code:

package test
class Person {
    String firstName
    String lastName
}

Then let's populate some records on Bootstrap. Remember that in Grails 3, the location of bootstrap is inside <grails-app>\init folder. Here is the code:
package test
class BootStrap {
    def init = { servletContext ->
        if (Person.count()==0) {
            new Person(lastName:'Doe', firstName:'John').save()
            new Person(lastName:'Doe', firstName:'Ronda').save()
            new Person(lastName:'Doe', firstName:'Peter').save()
            new Person(lastName:'Smith', firstName:'Alex').save()
            new Person(lastName:'Smith', firstName:'Anna').save()
            new Person(lastName:'Smith', firstName:'James').save()
            new Person(lastName:'Smith', firstName:'Oliver').save()
            new Person(lastName:'Lee', firstName:'Jet').save()
            new Person(lastName:'Lee', firstName:'Bruce').save()
            new Person(lastName:'Lee', firstName:'Jackie').save()
            new Person(lastName:'Lee', firstName:'Sammo').save()
            new Person(lastName:'Davis', firstName:'Anthony').save()
            new Person(lastName:'Davis', firstName:'Rex').save()
            new Person(lastName:'Davis', firstName:'Albert').save()
            new Person(lastName:'Davis', firstName:'Harry').save()
            new Person(lastName:'Johnson', firstName:'Rey').save()
            new Person(lastName:'Johnson', firstName:'Claire').save()
            new Person(lastName:'Johnson', firstName:'Bernadette').save()
            new Person(lastName:'Johnson', firstName:'Bryan').save()
        }
    }
    def destroy = {
    }
}
We create several records so that it is easier to paginate later. To those new to Grails, the Bootstrap contains code that is executed when we start our application. So the code above creates several record if no data exists in the database.

Controller

Next we write the controller code
package test
import grails.converters.JSON
class PersonController {
    def index() { }
    def list(int max, int pageNumber) {
        int offset = pageNumber * max
        def listOfPerson = Person.executeQuery("from Person order by lastName, firstName",
            [offset:offset, max:max])
        def count = Person.count()
        int numberOfPages = (count + max - 1) / max
        def result = [listOfPerson:listOfPerson, numberOfPages:numberOfPages]
        render result as JSON
    }
}
There are two actions, the first action index just displays our gsp page that contains the html code. The second one is a service that will query the database for the list of person in the given page and page size. Then the result is returned to the caller in JSON form.

Vue.js

Here is our code that uses Vue.js to render the AJAX table.
<!doctype html>
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Grails and Vue.js Table Example</title>
    <script src="https://unpkg.com/vue@2.3.3/dist/vue.min.js"></script>
    <script src="https://unpkg.com/axios@0.16.1/dist/axios.min.js"></script>
  </head>
  <body>
    <div id="app">
      <table border="1">
        <thead>
          <tr>
            <th>Last Name</th>
            <th>First Name</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="person in listOfPerson">
            <td>{{ person.lastName }}</td>
            <td>{{ person.firstName }}</td>
          </tr>
        </tbody>
      </table>
      <div id="pagination">
        <span v-for="pageNumber in numberOfPages">
          <a href="#" @click="fetchData(pageNumber-1)">{{ pageNumber }}</a>
        </span>
      </div>
    </div>
    <script>
      var app = new Vue({
        el: '#app',
          data: {
            listOfPerson: [],
            numberOfPages: 0, 
            max: 5
          },
          methods: {
            fetchData: function (pageNumber) {
              axios.get('/person/list', {
                params:{
                  pageNumber:pageNumber,
                  max:this.max
                }
              })
              .then(function (response) {
                app.listOfPerson = response.data.listOfPerson;
                app.numberOfPages = response.data.numberOfPages;
              })
              .catch(function (error) {
                console.log(error);
              });
            }
          },
          created: function () {
            this.fetchData(0);
          }
      })
    </script>
  </body>
</html>

For simplicity, we included the library using these CDN. Please change this in real production use to more appropriate way. We just do it this way for convenience. Note that we use axios for performing AJAX calls.

<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

We have the template part below. It has two sections. The first one loops through the list of person and display each record on new row. The second one displays the pagination links, again using loops.

    <div id="app">
      <table border="1">
        <thead>
          <tr>
            <th>Last Name</th>
            <th>First Name</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="person in listOfPerson">
            <td>{{ person.lastName }}</td>
            <td>{{ person.firstName }}</td>
          </tr>
        </tbody>
      </table>
      <div id="pagination">
        <span v-for="pageNumber in numberOfPages">
          <a href="#" @click="fetchData(pageNumber-1)">{{ pageNumber }}</a>
        </span>
      </div>
    </div>

Our Javascript code couldn't be any simpler. We just declare our variables which is the list of person, the number of pages, and max (number of records per page). The only method we implement is the one that invokes the AJAX call, which is straightforward. Lastly, the created function is called after everything is created and ready. Which immediately fetches the data from the back-end and populate our table.

    <script>
      var app = new Vue({
        el: '#app',
          data: {
            listOfPerson: [],
            numberOfPages: 0, 
            max: 5
          },
          methods: {
            fetchData: function (pageNumber) {
              axios.get('/person/list', {
                params:{
                  pageNumber:pageNumber,
                  max:this.max
                }
              })
              .then(function (response) {
                app.listOfPerson = response.data.listOfPerson;
                app.numberOfPages = response.data.numberOfPages;
              })
              .catch(function (error) {
                console.log(error);
              });
            }
          },
          created: function () {
            this.fetchData(0);
          }
      })
    </script>

Rendered Result

The rendered result on load of page will look like below. The contents are updated in AJAX fashion as pagination links are clicked.

<table border="1">
      <thead>
        <tr>
          <th>Last Name</th>
          <th>First Name</th>
        </tr></thead>
      <tbody>
        <tr>
          <td>Smith</td>
          <td>Alex</td>
        </tr>
        <tr>
          <td>Smith</td>
          <td>Anna</td>
        </tr>
        <tr>
          <td>Smith</td>
          <td>James</td>
        </tr>
        <tr>
          <td>Smith</td>
          <td>Oliver</td>
        </tr>
      </tbody>
</table>
<div id="pagination">
      <span> <a href="#">1</a></span>
      <span><a href="#">2</a></span>
      <span><a href="#">3</a></span>
      <span><a href="#">4</a></span>
</div>

Conclusion

In my opinion, Vue.js is the perfect Javascript framework to match with Grails 3. The inspiration of Grails is rapid application development using the least amount of code and intuitive way of doing things. Using the other heavyweight Javascript framework are complicated and takes a lot of time to learn and code with. Vue.js is something very productive from the minute you try to use it.