The toolbox of an enterprise HTML5 developer contains a number of tools that improve his or her productivity. In this chapter we’ll share with you some of the tools that we use.
We’ll start this chapter with a brief introduction of Node.js (or simply Node) - the server-side JavaScript framework and Node Packages Manages (NPM). Node and NPM serve as a foundation for the tools covered in this chapter.
Next, we’d like to highlight the a handful of productivity tools that authors of this book use in their consulting projects, namely:
-
Grunt is a task runner framework for the JavaScript projects that allows to automate repetitive operations like running tests.
-
Bower is a package manager for the web projects that helps in maintaining application dependencies.
-
Yeoman is a collection of code-generation tools and best practices.
In addition to the above tools that can be used with various JavaScript frameworks, we’ll introduce you to Clear Toolkit for Ext JS, which include the code generator Clear Data Builder - it’s created and open sourced by our company, Farata Systems. With Clear Toolkit you’ll be able to quickly start the project that utilizes Ext JS framework for the front-end development and Java on the server side.
Node.js is a server-side JavaScript framework. Node uses V8, the JavaScript engine by Google (Chrome/Chromium also use it). Node provides JavaScript API for accessing the file system, sockets and running processes which makes it great general purpose scripting runtime. You can find more information about Node at their website.
Many tools are built on top of Node JavaScript APIs. The Grunt tool is one of them. We will use Grunt later in this book to automate execution of repetitive development tasks.
NPM is a utility that comes bundled with Node. NPM provides unified API and metadata model for managing dependencies in JavaScript projects. A package.json
file is the project’s dependencies descriptor. NPM installs project dependencies using information from package.json
. NPM uses community repository for open source JavaScript projects to resolve dependencies.
Node and NPM are cross-platform software and binaries available for Windows, Linux and OS X operating systems.
To use this book code samples you need to download and install Node from their web site.
You should automate every aspect of the development workflow to reduce the cost of building, deploying, and maintaining your application.
In this section we are going to introduce Grunt - a task runner framework for the JavaScript projects - that can help you with automation of repetitive operations like running tests when the code changes. You can follow the instructions from Grunt’s website to install it on your machine.
Grunt can watch your code changes and automate the process of running tests when the code changes. Tests should help in assessing the quality of our code.
With the Grunt tool you can have a script to run all your tests. If you came from the Java world, you know about Apache Ant, a general-purpose command-line tool to drive processes described build files as targets in the build.xml file. Grunt also runs the tasks described in scripts. There is a wide range of tasks available today - starting with running automated unit tests and ending with JavaScript code minification. Grunt provides a separate layer of abstraction where you can define tasks in a special DSL (domain-specific language) in Gruntfile for execution.
Let’s start with the simplest Grunt project setup. The following two files must be present in the project directory:
-
package.json: This file is used by NPM to store metadata and a project dependencies.
List Grunt and its plugins that your project needs as devDependencies in this file.
-
Gruntfile
: This file is named Gruntfile.js or Gruntfile.coffee and is used to configure or define the tasks and load Grunt plugins.
module.exports = function (grunt) {
'use strict';
grunt.registerTask('hello', 'say hello', function(){ // (1)
grunt.log.writeln('Hello from grunt'); // (2)
});
grunt.registerTask('default', 'hello'); // (3)
};
-
Register a new task named
hello
. -
Print the greeting text using grunt’s log API.
-
With
grunt.registerTask
we define a default task to run when Grunt is called without any parameters.
Each task can be called separately from the command line by passing the task’s name as a command line parameter. For example, grunt hello
would only execute the task named "hello" from the above script.
Let’s run this hello
task with the following command:
grunt --gruntfile Grunt_simple.js hello.
Running "hello" task
Hello from grunt
Done, without errors.
Now after covering the basics of Grunt tool we can use it for something more interesting than just printing "hello world" on the screen. Since JavaScript is a interpreted language there is no compiler to help catch syntax errors. But you can use JSHint, an open source tool, which helps with identifying errors in JavaScript code in lieu of compiler. Consider the following JavaScript code.
var bonds = [ // (1)
'Sean Connery',
'George Lazenby',
'Roger Moore',
'Timothy Dalton',
'Pierce Brosnan',
'Daniel Craig', // (2)
//'Unknow yet actor'
] // (3)
-
We want to define an array that contains names of actors who played James Bond in a canonical series.
-
Here is example of a typical typo. A developer commented out the line containing an array element but kept the coma in previous line.
-
A missing semicolon is a typical typo. It is not an actual error, but omitting semicolon is not a good habit. An automatic semicolon insertion (ASI) will get you covered in this case.
In JavaScript, you can omit a semicolon between two statements written in separate lines. Automatic semicolon insertion is a source code parsing procedure that infers omitted semicolons in certain contexts into your program. You can read more about optional semicolons in JavaScript in the chapter "Optional Semicolons" in 'JavaScript. Definitive Guide. 6th Edition' book.
The above code snippet is a fairly simple example that can cause trouble and frustration if you don’t have proper tools to check the code semantics and syntax. Let’s see how JSHint can help in this situation.
JSHint can be installed via NPM with command npm install jshint -g
. Now you can run JSHint against our code snippet:
> jshint jshint_example.js
jshint_example.js: line 7, col 27, Extra comma. (it breaks older versions of IE)
jshint_example.js: line 9, col 10, Missing semicolon. # (1)
2 errors # (2)
-
JSHint reports the location of error and a short description of the problem.
-
The total count of errors
Tip
|
WebStorm IDE has built-in support for JSHint tool. There is 3rd party plugin for Eclipse - jshint-eclipse. |
Grunt also has a task to run JSHint against your JavaScript code base. Here is how JSHint configuration in Grunt looks like.
module.exports = function(grunt) {
grunt.initConfig({
jshint: {
gruntfile: { // (1)
src: ['Gruntfile_jshint.js']
},
app: {
src: ['app/js/app.js']
}
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['jshint']); // (2)
};
-
Because Gruntfile is JavaScript file, JSHint can check it as well and identify the errors.
-
When grunt will be run without any parameters, default task
jshint
will be triggered.
> grunt
Running "jshint:gruntfile" (jshint) task
>> 1 file lint free.
Running "jshint:app" (jshint) task
>> 1 file lint free.
Done, without errors.
Another handy task that to use in developer’s environment is the watch
task. The purpose of this task is to monitor files in pre-configured locations. When the watcher detects any changes in those files it will run the configured task. Here is how a watch task config looks like:
watch
task configmodule.exports = function(grunt) {
grunt.initConfig({
jshint: {
// ... configuration code is omitted
},
watch: { // (1)
reload: {
files: ['app/*.html', 'app/data/**/*.json', 'app/assets/css/*.css', 'app/js/**/*.js', 'test/test/tests.js', 'test/spec/*.js'], // (2)
tasks: ['jshint'] // (3)
}
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['jshint']);
};
-
The
watch
task configuration starts here -
The list of the files that need to be monitored for changes
-
A array of tasks to be triggered after file change event occurs
> grunt watch
Running "watch" task
Waiting...OK
>> File "app/js/Player.js" changed.
Running "jshint:gruntfile" (jshint) task
>> 1 file lint free.
Running "jshint:app" (jshint) task
>> 1 file lint free.
Done, without errors.
Completed in 0.50s at Tue May 07 2013 00:41:42 GMT-0400 (EDT) - Waiting...
Bower is a package manager for Web projects. Twitter has donated it to the open-source community. Bower is a utility and a community driven repository of libraries that help in downloading the third-party software required for the application code that will run in a Web browser. The Bower’s purpose is very similar to NPM, but the latter is more suitable for the server-side projects.
Bower can take care of transitive (dependency of a dependency) dependencies and download all required library components. Each Bower’s package has a bower.json file, which contains the package metadata for managing the package’s transitive dependencies. Also, bower.json can contain information about the package repository, readme file, license et al. You can find bower.json in the root directory of the package. For example, components/requirejs/bower.json is a path for the RequireJS metadata file. Bower can be installed via NPM. The following line shows how to install Bower globally in your system.
npm install -g bower
Tip
|
Java developers use package managers like Gradle or Maven that have similar to Bower functionality. |
Let’s start using Bower now. For example, here is a Bower’s command to install the library RequireJS.
bower install requirejs --save
Bower installs RequireJS into components/requirejs directory and saves information about dependencies in bower.json configuration file.
Bower simplifies the delivery of dependencies into target platform, which means that you don’t need to store dependencies of your application in the source control system. Just keep you application code there and let Bower to bring all other dependencies described in its configuration file.
Tip
|
There are pros and cons for storing dependencies in the source control repositories. Read the article by Addi Osmani that covers this subject in more detail. |
Your application will have its own file bower.json with the list of the dependencies. At this point, Bower can install all required application dependencies with one command - bower install
, which will deliver all your dependency files into the components directory. Here is the content of the file bower.json for our Save The Child application.
{
"name": "ch7_dynamic_modules",
"description": "Chapter 7: Save The Child, Dynamic Modules app",
"dependencies": {
"requirejs": "~2.1.5",
"jquery": ">= 1.8.0",
"qunit": "~1.11.0",
"modernizr": "~2.6.2",
"requirejs-google-maps": "latest"
}
}
Application dependencies are specified in corresponding "dependencies section. The >= sign specifies that the corresponding software has to be not older than the specified version.
Also, there is a Bower search tool to find the desired component in its repository.
Yeoman is a collection of tools and best practices that help to bootstrap a new web project. Yeoman consists from three main parts: Grunt, Bower and Yo. Grunt and Bower were explained earlier in this chapter.
Yo is a code-generation tool. It makes the start of the project faster by scaffolds a new JavaScript application. Yo can installed via NPM similar to the other tools. The following commands shows how to install Yo globally in your system. And if you didn’t have Grunt and Bower installed before, this command will install them automatically.
npm install -g yo
For code-generation, Yo relies on plugins called generators. Generator is a set of instructions to Yo and file templates. You can use Yeoman Generators search tool to discover community-developed generators.
For example, let’s scaffold the Getting Started project for RequreJS. RequireJS is a framework that helps to dice code of your JavaScript application into modules. We will cover this framework in details later in «Modularizing Large-Scale JavaScript Projects» chapter.
The search tool found bunch of generators that have keyword requirejs in their name of description. We’re looking for generator that called "requirejs" (highlighted with red square). When we click on name link, the Github page of requirejs generator will be displayed. Usually, the generator developers provide a reference of the generator’s available tasks.
Next we need to install generator on our local machine with following command:
npm install -g generator-requirejs
After installation, we can start yo command and as a parameter we need to specify generator’s name. To start scaffolding a RequireJS application we can use following command:
yo requirejs
We need to provide answers to the wizard’s questions.
_-----_ | | |--(o)--| .--------------------------. `---------´ | Welcome to Yeoman, | ( _´U`_ ) | ladies and gentlemen! | /___A___\ '__________________________' | ~ | __'.___.'__ ´ ` |° ´ Y ` This comes with requirejs, jquery, and grunt all ready to go [?] What is the name of your app? requirejs yo [?] Description: description of app for package.json create Gruntfile.js create package.json create bower.json create .gitignore create .jshintrc create .editorconfig create CONTRIBUTING.md create README.md create app/.jshintrc create app/config.js create app/main.js create test/.jshintrc create test/index.html create test/tests.js create index.htm I'm all done. Running bower install & npm install for you to install the required dependencies. If this fails, try running the command yourself. .... npm install output is omitted
You will get all directories and files set up, and you can start writing your code immediately. The structure of your project will be reflecting common best practices from JavaScript community (refer to following figure).
After executing the yo command you will get Grunt set up with following configured tasks:
-
clean: Clean files and folders.
-
concat: Concatenate files.
-
uglify: Minify files with UglifyJS.
-
qunit: Run QUnit unit tests in a headless PhantomJS instance.
-
jshint: Validate files with JSHint.
-
watch: Run predefined tasks whenever watched files change.
-
requirejs: Build a RequireJS project.
-
connect: Start a connect web server.
-
default: Alias for "jshint", "qunit", "clean", "requirejs", "concat", "uglify" tasks.
-
preview: Alias for "connect:development" tas* preview-live Alias for "default", "connect:production" tasks.
Yeoman also has generator for generator scaffolding. It might be very useful if in your want to introduce your own workflow for web project.
The next code generator that we’ll cover is a more specific one - it can generates the entire ExtJS-Java application.
Authors of this book work for the company called Farata Systems, which has developed an open source freely available software Clear Toolkit for Ext JS, and the code generator and Eclipse IDE plugin CDB comes with it. CDB is a productivity tool that was created specifically for the enterprise applications that use Java one the server side and need to retrieve, manipulate, and save the data in some persistent storage.
Such enterprise applications are known as CRUD applications because they perform Create-Retrieve-Update-Delete operations with data. If the server side of your Web application is developed in Java, with CDB you can easily generate a CRUD application, where Ext JS front end communicates the Java back end. In this section you will learn how jump start development of such CRUD Web applications.
Important
|
Familiarity with core Java concepts like classes, constructors, getters and setters, and annotations is required for understanding of the materials of this section. |
The phrase to be more productive means to write less code while producing the results faster. This is what CDB is for, and you’ll see it helps you to integrate the client side with the back end using the RPC style and how to implements data pagination for your application. To be more productive, you need to have the proper tools installed and we’ll cover this next.
In this section we’ll cover the following topics:
-
What is Clear Toolkit for Ext JS
-
How to create an Ext JS MVC front end for a Java-based project
-
How to deploy and run your first Ext JS and Java application on Apache Tomcat server
Clear Toolkit for Ext JS includes the following:
-
Clear Data Builder - an Eclipse plugin that supports code generation Ext JS MVC artifacts based on the code written in Java. CDB comes with wizards to start new project with plain Java or with popular frameworks like Hibernate, Spring, MyBatis.
-
Clear JS - a set of JavaScript components that extends Ext JS standard components. In particular, it includes a
ChangeObject
that traces the modifications of any item in a store. -
Clear Runtime - Java components that implements server side part of ChangeObject, DirectOptions an others.
CDB distribution available as plug-in for a popular among Java developers Eclipse IDE. The current update site of CDB is located here. The current version is 4.1.4. You can install this plug-in via the Install New Software menu in Eclipse IDE. The Verifying CDB installation shows "Clear Data Builder for Ext JS feature" in the list of Installed Software in your Eclipse IDE, which means that CDB is installed.
Important
|
You have to have work with "Eclipse IDE for Java EE Developers", which includes plugins for automation of the Web application development. |
Clear Data Builder comes with a set of prepared examples that demonstrate the integration with popular Java frameworks - MyBatis, Hibernate, and Spring. There is also a plain Java project example that doesn’t use any persistence frameworks. Let’s start with the creation of the new project by selecting Eclipse menu File → New → Other → Clear. You’ll see a window similar to New CDB Project Wizard.
Name the new project episode_1_intro. CDB supports different ways of linking the Ext JS framework to the application. CDB automatically copies the Ext JS framework under the Web server (Apache Tomcat in our case). We’re going to use this local Ext JS URL, but you can specify any folder in your machine and CDB will copy the Ext JS file from there into your project. You can also use Ext JS from the Sencha’s CDN, if you don’t want to store these libraries inside your project. Besides, using a common CDN will allow Web browser to reuse the cached version of Ext JS.
For this project we are not going to use any server-side persistence frameworks like MyBatis or Hibernate. Just click the button Finish, and you’ll see some some initial CDB messages on the Eclipse console. When CDB runs for the first time it creates in your project’s WebContent folder the directory structure recommended by Sencha for MVC applications. It also generates index.html for this application, which contains the link to the entry point of our Ext JS application.
CDB generates an empty project with one sample controller and one view - Viewport.js. To run this application, you need to add the newly generated Dynamic Web Project to Tomcat and start the server (right-click on the Tomcat in the Servers view of Eclipse IDE).
Open this application in your Web browser at http://localhost:8080/episode_1_intro . Voila! In less than a couple of minutes we’ve created a new Dynamic Web Project with the Ext JS framework and one fancy button as shown on Running scaffolded application.
The next step is to make something useful out of this basic application.
The Part Two of the CDB section covers the process of creation of a simple CRUD application that uses Ext JS and Java. We’ll go through the following steps:
-
Create a plain old Java object (POJO) and the corresponding
Ext.data.Model
-
Create a Java service and populate
Ext.data.Store
with data from service -
Use the auto-generated Ext JS application
-
Extend the auto-generated CRUD methods
-
Use
ChangeObject
to track the data changes
Now let’s use CDB to create a CRUD application. You’ll learn how turn a POJO into an Ext JS model, namely:
-
how to populate the Ext JS store from a remote service
-
how to use automatically generated UI for that application
-
how to extend the UI
-
what the
ChangeObject
class is for
First, we’ll extend the application from Part One - the CRUD application needs a Java POJO. To start, create a Java class Person
in the package dto
. Then add to this class the properties (as well as getters and setters) firstName
, lastName
, address
, ssn
and phone
and id
. Add the class constructor that initializes these properties as shown in the code listing below.
package dto;
import com.farata.dto2extjs.annotations.JSClass;
import com.farata.dto2extjs.annotations.JSGeneratedId;
@JSClass
public class Person {
@JSGeneratedId
private Integer id;
private String firstName;
private String lastName;
private String phone;
private String ssn;
public Person(Integer id, String firstName, String lastName,
String phone, String ssn) {
super();
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.phone = phone;
this.ssn = ssn;
}
// Getters and Setters are omitted for brevity
}
You may also add a toString()
method to the class. Now you’ll need the same corresponding Ext JS model for the Java class Person
. Just annotate this class with the annotation @JSClass
to have CDB generate the Ext JS model.
The next step is to annotate the id
field with the CDB annotation @JSGeneratedId
. This annotation instructs CDB to threat this field as an auto generated id. Let’s examine the directory of Ext JS MVC application to see what’s inside the model folder. In the JavaScript section there is the folder dto which corresponds to the Java dto package where the PersonModel
resides as illustrated on Generated from Java class Ext JS model.
Clear Data Builder generated two files as recommended by the Generation Gap pattern, which is about keeping the generated and handwritten parts separate by putting them in different classes linked by inheritance. Let’s open the person model. In our case the PersonModel.js
is extended from the generated _PersonModel.js
. Should we need to customize this class, we’ll do it inside the Person.js, but this underscore-prefixed file will be regenerated each and every time when we change something in our model. CDB follows this pattern for all generated artifacts - Java services, Ext JS models and stores. This model contains all the fields from our Person DTO.
Now we need to create a Java service to populate the Ext JS store with the data. Let’s create a Java interface PersonService
in the package service
. This service will to return the list of Person
objects. This interface contains one method -List<Person> getPersons()
.
To have CDB to expose this service as a remote object, we’ll use the annotation called @JSService
. Another annotation @JSGenetareStore
will instruct CDB to generate the store. In this case CDB will create the destination-aware store. This means that store will know from where to populate its content. All configurations of the store’s proxies will be handled by the code generator. With @JSFillMethod
annotation we will identify our main read method (the "R" from CRUD).
Also it would be nice to have some sort of a sample UI to test the service - the annotation @JSGenerateSample
will help here. CDB will examine the interface PersonService
, and based on these annotations will generate all Ext JS MVC artifacts (models, views, controller) and the sample application.
@JSService
public interface PersonService {
@JSGenerateStore
@JSFillMethod
@JSGenerateSample
List<Person> getPersons();
}
When the code generation is complete, you’ll get the implementation for the service - PersonServiceImpl
. The store folder inside the application folder (WebContent\app) has the Ext JS store, which is bound to the previously generated PersonModel
. In this case, CDB generated store that binds to the remote service.
All this intermediate translation from the JavaScript to Java and from Java to JavaScript is done by DirectJNgine, which is a server side implementation of the Ext Direct Protocol. You can read about this protocol in Ext JS documentation.
CDB has generated a sample UI for us too. Check out the samples directory shown on Folder with generated UI files.
CDB has generated SampleController.js
, SampleGridPanel.js
, and the Ext JS application entry point sampleApp.js
. To test this application just copy the file SampleController.js
into the controller folder, SampleGridPanel.js
panel into the view folder, and the sample application in the root of the WebContent folder. Change the application entry point with to be sampleApp.js
in the index.html of the Eclipse project as shown below.
<script type="text/javascript" src="sampleApp.js"></script>
This is how the generated UI of the sample application looks like Scaffolded CRUD application template.
On the server side, CDB also follows the Generation Gap Pattern and it generated stubs for the service methods. Override these methods when you’re ready to implement the CRUD functionality, similar to the below code sample.
package service;
import java.util.ArrayList;
import java.util.List;
import clear.data.ChangeObject;
import dto.Person;
import service.generated.*;
public class PersonServiceImpl extends _PersonServiceImpl { // (1)
@Override
public List<Person> getPersons() { // (2)
List<Person> result = new ArrayList<>();
Integer id= 0;
result.add(new Person(++id, "Joe", "Doe",
"555-55-55", "1111-11-1111"));
result.add(new Person(++id, "Joe", "Doe",
"555-55-55", "1111-11-1111"));
result.add(new Person(++id, "Joe", "Doe",
"555-55-55", "1111-11-1111"));
result.add(new Person(++id, "Joe", "Doe",
"555-55-55", "1111-11-1111"));
return result; // (3)
}
@Override
public void getPersons_doCreate(ChangeObject changeObject) { // (4)
Person dto = (Person) deserializeObject(
(Map<String, String>) changeObject.getNewVersion(),
Person.class);
System.out.println(dto.toString());
}
@Override
public void getPersons_doUpdate(ChangeObject changeObject) { // (5)
// TODO Auto-generated method stub
super.getPersons_doUpdate(changeObject);
}
@Override
public void getPersons_doDelete(ChangeObject changeObject) { // (6)
// TODO Auto-generated method stub
super.getPersons_doDelete(changeObject);
}
}
-
Extend the generated class and provide the actual implementation
-
The
getPerson()
is our retrieve method (the R in CRUD) -
For this sample application we can use
java.util.ArrayList
class as in-memory server side storage of thePerson
objects. In the real world applications you’d use a database or other persistent storage -
fillmethod+
doCreate()
is our create method (the C in CRUD) -
fillmethod+` doUpdate` is our update method (the U in CRUD)
-
fillmethod+` doDelete` is our delete method (the D in CRUD)
Click on the Load menu on the UI, and the application will retrieve four persons from our server
To test the rest of the CRUD methods, we’ll ask the user to insert one new row, modify three existing ones and remove two rows using the generated Web client. The Clear.data.DirectStore
object will automatically create a collection of six `ChangeObject`s - one to represent a new row, three to represent the modified ones, and two for the removed rows.
When the user clicks on the Sync UI menu the changes will be sent to the corresponding do…
remote method. When you sync()
a standard Ext.data.DirectStore
Ext JS is POST-ing new, modified and deleted items to the server. When the request is complete the server’s response data is applied to the store expecting that some items can be modified by the server. In case of Clear.data.DirectStore
instead of passing around items, we pass the deltas, wrapped in the ChangeObject
.
Each instance of the ChangeOject
contains the following:
-
newVersion
- it’s an instance of the newly inserted or modified item. On the Java side it’s available viagetNewVersion()
. -
prevVersion
- it’s an instance of the deleted of old version of modified item. On the Java side it’s available viagetPrevVersion()
. -
array of
changepropertyNames
if thisChangeObject
represents an update operation.
The rest of ChangeObject
details described on the Clear Toolkit Wiki.
The corresponding Java implementation of ChangeObject
is available on the server side and Clear Toolkit passes ChangeObject
instances to the appropriate do*
method of the service class. Take a look at the getPersons_doCreate()
method from Implementation of PersonService interface. When the server needs to read the new or updated data arrived from the client your Java class has to invoke the method changeObject.getNewVersion()
. This method will return the JSON object that you need to deserialize into the object Person
. This is done in Implementation of PersonService interface and looks like this.
Person dto = (Person) deserializeObject(
(Map<String, String>) changeObject.getNewVersion(),Person.class);
When the new version of the Person
object is extracted from the ChangeObject
you can do with it whatever has to be done to persist it in the appropriate storage. In our example we just print the new person information on the server-side Java console. This is why we said earlier, that it may be a good idea to provide a pretty printing feature on the class Person
by overriding method toString()
. Similarly, when you need to do a delete, the changeObject.getPrevVersion()
would give you a person to be deleted.
The pagination feature is needed in almost every enterprise web application. Often you don’t want to bring all the data to the client at once - a page by page feed brings the data to the user a lot faster. The user can navigate back and forth between the pages using pagination UI components. To do that, we need to split our data on the server side into chunks, to send them page by page by the client request. Implementing pagination is the agenda for this section. We’ll do the following:
-
Add the data pagination to our sample CRUD application:
-
Add the
Ext.toolbar.Paging
component -
Bind both grid and pagingtoolbar to the same store
-
Use
DirectOptions
class to read the pagination parameters
-
We are going to improve our CRUD application by adding the paging toolbar component bound to the same store as the grid. The class DirectOptions
will handle the pagination parameters on the server side.
So far CDB has generate the UI from the Java back end service as well as the Ext JS store and model. We’ll refactor the service code from previous example to generate more data (a thousand objects) so we have something to paginate, see below.
public class PersonServiceImpl extends _PersonServiceImpl {
@Override
public List<Person> getPersons() {
List<Person> result = new ArrayList<>();
for (int i=0; i<1000; i++){
result.add(new Person(i, "Joe", "Doe", "555-55-55",
"1111-11-1111"));
}
return result;
}
}
If you’ll re-run the application now, the Google Chrome Console will show that PersonStore
is populated with one thousand records. Now we’ll add the the Ext JS paging toolbarpaging
UI component to the file sampleApp.js as shown below.
Ext.Loader.setConfig({
disableCaching : false,
enabled : true,
paths : {
episode_3_pagination : 'app',
Clear : 'clear'
}
});
Ext.syncRequire('episode_3_pagination.init.InitDirect');
// Define GridPanel
var myStore = Ext.create('episode_3_pagination.store.dto.PersonStore',{}); //(1)
Ext.define('episode_3_pagination.view.SampleGridPanel', {
extend : 'Ext.grid.Panel',
store : myStore,
alias : 'widget.samplegridpanel',
autoscroll : true,
plugins : [{
ptype : 'cellediting'
}],
dockedItems: [
{
xtype: 'pagingtoolbar', //(2)
displayInfo: true,
dock: 'top',
store: myStore //(3)
}
],
columns : [
{header : 'firstName', dataIndex : 'firstName',
editor : {xtype : 'textfield'}, flex : 1 },
{header : 'id', dataIndex : 'id', flex : 1 },
{header : 'lastName', dataIndex : 'lastName',
editor : {xtype : 'textfield'}, flex : 1 },
{header : 'phone', dataIndex : 'phone',
editor : {xtype : 'textfield'}, flex : 1 },
{header : 'ssn', dataIndex : 'ssn',
editor : {xtype : 'textfield'}, flex : 1 }],
tbar : [
{text : 'Load', action : 'load'},
{text : 'Add', action : 'add'},
{text : 'Remove', action : 'remove'},
{text : 'Sync', action : 'sync'}
]
});
// Launch the application
Ext.application({
name : 'episode_3_pagination',
requires : ['Clear.override.ExtJSOverrider'],
controllers : ['SampleController'],
launch : function() {
Ext.create('Ext.container.Viewport', {
items : [{
xtype : 'samplegridpanel'
}]
});
}
});
-
Manual store instantiation - create a separate variable
myStore
for this store with emptyconfig
object -
Adding the
xtype
pagingtoolbar
to this component docked items property to display the information and dock this element at the top. -
Now the paging toolbar is also connected to same store.
The next step is to fix the automatically generated controller to take care of the loading of data on click of Load button as shown in the code below.
Ext.define('episode_3_pagination.controller.SampleController', {
extend: 'Ext.app.Controller',
stores: ['episode_3_pagination.store.dto.PersonStore'],
refs: [{ //(1)
ref: 'ThePanel',
selector: 'samplegridpanel'
}],
init: function() {
this.control({
'samplegridpanel button[action=load]': {
click: this.onLoad
}
});
},
onLoad: function() {
// returns instance of PersonStore
var store = this.getThePanel().getStore(); //(2)
store.load();
}
});
-
Bind the store instance to our grid panel. In controller’s
refs
property we’re referencing oursimplegrid
panel withThePanel
alias. -
In this case there is no need to explicitly retrieve the store instance by name. Instead, we can use getters
getPanel()
andgetStore()
automatically generated by the Ext JS framework.
When the user clicks the button next or previous the method loadPage
of the underlying store is called. Let’s examine the directprovider
URL - the server side router of the remoting calls - to see how the direct request looks like. Open Google Chrome Developer Tools from the menu View → Developer, refresh the Web page and go to the Network tab. You’ll see that each time the user clicks on the next or previous buttons on the pagination toolbar the component sends directOptions
as a part of the request.
The default Ext Direct request doesn’t carry any information about the page size. Clear JS has the client side extension of the Ext JS framework that adds some extra functionality to Ext.data.DirectStore
component to pass the page start
and limit
values to the server side. At this point, the directOptions
request property (see Request payload details) can be extracted on the server side to get the information about the page boundaries. Let’s add some code to the PersonServiceImpl.java. At this point the pagination doesn’t work. The server sends the entire thousand records, because it doesn’t know that the data has to be paginated. We’ll fix it in the following listing.
package service;
import java.util.ArrayList;
import java.util.List;
import clear.djn.DirectOptions; //(1)
import dto.Person;
import service.generated.*;
public class PersonServiceImpl extends _PersonServiceImpl {
@Override
public List<Person> getPersons() {
List<Person> result = new ArrayList<>();
for (int i=0; i<1000; i++){
result.add(new Person(i, "Joe", "Doe", "555-55-55","1111-11-1111"));
}
//(2)
int start = ((Double)DirectOptions.getOption("start")).intValue();
int limit = ((Double)DirectOptions.getOption("limit")).intValue();
limit = Math.min(start+limit, result.size() ); //(3)
DirectOptions.setOption("total", result.size()); //(4)
result = result.subList(start, limit); //(5)
return result;
}
}
-
On the server side there is a special object called
DirectOptions
, which comes with Clear Toolkit. -
We want to monitor the
start
and inlimit
values (see Request payload details). -
Calculate the actual limit. Assign the size of the data collection to the
limit
variable if it’s less than the page size (start+limit
). -
Notify the component about the total number of elements on the server side by using
DirectOptions.setOption()
method withtotal
option. -
Before returning the result, create a subset, an actual page of data using the method
java.util.List.sublist()
which produces the view of the portion of this list between indexes specified by thestart
and thelimit
parameters.
As you can see from the Network tab in Scaffolded CRUD application template, we’ve limited the data load to 25 elements per page. Clicking on next
or previous
buttons will get you only a page worth of data. The Google Chrome Developers Tools Network tab shows that that we are sending the start
and limit
values with every request, and the response contains the object with 25 elements.
If you’d like to repeat all of the above steps on you own, watch the screencasts where we demonstrate all the actions described in the section on CDB.
Writing the enterprise web applications can be a tedious and time-consuming process. A developer needs to set up frameworks, boilerplates, abstractions, dependency management, build processes and the list of requirements for a front-end workflow appears to grow each year. In this chapter we introduced several tools that could help you with automating a lot of mundane tasks and make you more productive.