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.
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.
<!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>
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>