Top Banner
Spring 2012 – Demo Project 3 The purpose of this project, called demo-proj3, is to set up an application that does similar things to what has to be done in project 3 but using PHP instead of Rails as the middleware. This will allow sharing of the PHP code with the students without giving away how to do this for the actual project 3. Database backend We will create a database called demo_proj3, that has a user named 'bob', and a password called 'somepass'. This database will have a table called users, to hold the users and authentication information. This database will also have a table called items, that keeps track of implementation items for an organization that is trying to improve its operation. A table called items_users will keep track of the users that are allowed to edit an item. Database creation and addition of pgcrypto We can start by creating the database: psql -U cent285man -W cent285db Password for user cent285man: psql (8.4.9) Type "help" for help. cent285db=# create database demo_proj3 owner bob; CREATE DATABASE cent285db=# \c demo_proj3 Password for user cent285man: psql (8.4.9) You are now connected to database "demo_proj3". demo_proj3=# \i /usr/share/postgresql/8.4/contrib/pgcrypto.sql That will be accompanied by a SET, and a lot of CREATE FUNCTION commands. Recall that the pgcrypto functions are added so that we can use SHA-256 encryption. Here is a start into a SQL script that creates the tables: -- createTables.sql -- create sequences drop sequence if exists user_id_seq cascade; create sequence user_id_seq; drop sequence if exists item_id_seq cascade; create sequence item_id_seq; -- create tables drop table if exists users cascade; create table users( id integer not null default nextval('user_id_seq'), name text, username text unique, 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14
106

A GWT application using PHP and PostgreSQL

Oct 30, 2014

Download

Documents

vmanscribe

How to create a GWT application that uses PHP to connect to a PostgreSQL database
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: A GWT application using PHP and PostgreSQL

Spring 2012 – Demo Project 3

The purpose of this project, called demo-proj3, is to set up an application that does similar things to what has to be done in project 3 but using PHP instead of Rails as the middleware. This will allow sharing of the PHP code with the students without giving away how to do this for the actual project 3.

Database backend

We will create a database called demo_proj3, that has a user named 'bob', and a password called 'somepass'. This database will have a table called users, to hold the users and authentication information. This database will also have a table called items, that keeps track of implementation items for an organization that is trying to improve its operation. A table called items_users will keep track of the users that are allowed to edit an item.

Database creation and addition of pgcrypto

We can start by creating the database:

psql -U cent285man -W cent285dbPassword for user cent285man: psql (8.4.9)Type "help" for help.

cent285db=# create database demo_proj3 owner bob;CREATE DATABASEcent285db=# \c demo_proj3 Password for user cent285man: psql (8.4.9)You are now connected to database "demo_proj3".demo_proj3=# \i /usr/share/postgresql/8.4/contrib/pgcrypto.sql

That will be accompanied by a SET, and a lot of CREATE FUNCTION commands. Recall that the pgcrypto functions are added so that we can use SHA-256 encryption.

Here is a start into a SQL script that creates the tables:

-- createTables.sql

-- create sequencesdrop sequence if exists user_id_seq cascade;create sequence user_id_seq;drop sequence if exists item_id_seq cascade;create sequence item_id_seq;

-- create tablesdrop table if exists users cascade;create table users( id integer not null default nextval('user_id_seq'), name text, username text unique,

1

1

234

56

7

8910

1112

1314

Page 2: A GWT application using PHP and PostgreSQL

password text, enc_pass text, salt text, division text, department text, primary key (id));drop table if exists items cascade;create table items( id integer not null default nextval('item_id_seq'), description text, justification text, origin_department text, created_at timestamp, updated_at timestamp, num_low integer default 0, num_med integer default 0, num_high integer default 0, primary key (id));drop table if exists items_users cascade;create table items_users( user_id integer references users(id), item_id integer references items(id) on delete cascade, unique (user_id,item_id));

-- some test data

/*insert into users (name,username) values ('Jane Doe','janedoe');insert into users (name,username) values ('John Doe','johndoe');insert into items (description, created_at, updated_at) values ('item1',now(),now());insert into items (description, created_at, updated_at) values ('item2',now(),now());insert into items_users (user_id, item_id) values (1,1);insert into items_users (user_id, item_id) values (1,2);insert into items_users (user_id, item_id) values (2,1);select * from items_users;delete from items where id=2;select * from items_users;delete from users where id=2;select * from users;*/

Lines 4-7 create the sequences needed for the users and items tables. Lines 11-21 create the users table. Lines 23-33 create the items table. Each item will eventually be ranked. The num_low, num_med and num_high fields will hold the number of low votes, the number of medium votes and the number of high votes, respectively.

LInes 35-40 define the items_users table. This is the table that will keep track of the user(s) that can edit an item. Note, on line 37, that only the item_id is defined to cascade on delete. This means that when an item is deleted, any entries in the items_users table that refers to that deleted item, will also be deleted. Conversely, no such relation is made for the

2

15

1617

1819

2021

2223

2425

2627

2829

3031

3233

3435

3637

3839

40

4142

434445

4647

4849

5051

5253

5455

5657

5859

Page 3: A GWT application using PHP and PostgreSQL

user_id field. This will prevent a user from being deleted, if that user has entries in the items_users table. Also note on line 39 that we require the combination of the user_id and item_id to be unique. This will prevent duplicate records in this table.

The test data shown from line 42 on is designed to test the tables. After the tables are shown to work okay, this test data will be deleted. As expected, deleting item with id=2 causes the items_users table to have rows with item_id =2 to be deleted. Also, the attempt to delete user id = 2 fails, as this user is still referenced by the items_users table.

Adding a trigger for the updated_at field

When, we update an item, we want the updated_at field to reflect the time of update. This can be done with a trigger. Here is a second SQL script called, “createTrigger.sql” that does this:

-- createTrigger.sql drop language plpgsql cascade; create language plpgsql; create or replace function update_timestamp() returns trigger as $func$ begin NEW.updated_at := now(); return NEW; end; $func$ language 'plpgsql';

create trigger update_items_updatedat before update on items for each row execute procedure update_timestamp(); -- test data insert into items (description, created_at, updated_at) values ('item1',now(),now()); insert into items (description, created_at, updated_at) values ('item2',now(),now()); select description,created_at,updated_at from items; update items set description='item2 updated' where id=2; select description,created_at,updated_at from items;

Lines 4-12 define the function update_timestamp(). This function uses the now() function to obtain the new value for the updated_at field. In terms of how the plpgsql language works, NEW refers to the new record set. Returning this record set to the calling program causes the record to be updated.

On lines 14-16, a trigger is created. On lines 14-15, we create a trigger that is called anytime the items table is updated. On line 16, the update_timestamp() function defined earlier is called to update the updated_at field.

3

1

23

45

67

89

1011

12

131415

1617

1819

2021

2223

2425

Page 4: A GWT application using PHP and PostgreSQL

Lines 18 on just test this trigger. Reloading this script will show the updated_at field from the items table being updated properly. After verifying that this works, those lines can be commented out.

Creating functions for inserting users

Now we can add a script that adds functions that helps us to add a user, and authenticates a user. Here is the script, “userFuncs.sql”:

-- userFuncs.sql

create or replace function doEncrypt(_text text) returns text as $func$ begin return encode(digest(_text,'sha256'),'hex'); end; $func$ language 'plpgsql'; create or replace function makeSalt(_text text) returns text as $func$ begin return doEncrypt(_text||now()); end; $func$ language 'plpgsql'; create or replace function makePass(_text text) returns text as $func$ begin return doEncrypt(_text||makeSalt(_text)); end; $func$ language 'plpgsql'; create or replace function addUser(_name text,_user text, _pass text, _div text, _dept text) returns boolean as $func$ begin insert into users (name, username, enc_pass, salt, division, department) values (_name, _user, makePass(_pass),makeSalt(_pass), _div, _dept); return 't'; end; $func$ language 'plpgsql'; create or replace function getID(_user text, _pass text) returns integer as $func$

4

1

234

56

78

910

1112

1314

1516

1718

1920

2122

2324

2526

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

Page 5: A GWT application using PHP and PostgreSQL

declare rec record; begin select into rec * from users where username=_user; if found then if doEncrypt(_pass||rec.salt) = rec.enc_pass then return rec.id; else return -1; end if; else return -1; end if; end; $func$ language 'plpgsql'; select doEncrypt('mypass'); select makeSalt('mypass'); select makePass('mypass'); select addUser('Vern Takebayashi','vern','mypass','div1','deptA'); select addUser('Jane Doe','janedoe','janey','div1','deptB'); select getID('vern','mypass'); select getID('vern','mYpass'); select getID('vurn','mypass'); select getID('janedoe','janey');

Lines 3-10 define the doEncrypt() function. This function uses the digest() function, defined in pgcrypto. That digest() function encrypts the passed text using SHA-256. Then, the encode() function creates hexadecimal hash from the encrypted text.

Lines 12-19 are used to create the salt. This is done by concatenating the passed text with the string returned by the now() function and encrypting the resulting string.

Lines 21-28 define the makePass() function. This function just encrypts the string that results from concatenating the passed string with the result of calling makeSalt() on the passed string. This general scheme for creating the encrypted password makes it difficult to brute-force the password using rainbow tables. The rainbow tables cannot take into account a salt that depends on the time the original password was entered.

LInes 30-42 define and addUser() function that inserts the user with an encrypted password. The salt is stored at the same time. Note that the password field is not even inserted as part of the record. Only the encrypted password is inserted, and the value of the encrypted password is useless without also knowing the salt.

Lines 44-62 define the getID() function. This function will check to see if the username exists. If the username is not found, then a -1 is returned on lines 57-58. If the username is found, the function obtains the string that results from encrypting the concatenated string formed by combining the passed string with the stored salt. The resulting encrypted string is compared to the stored password. If this matches, the function returns the id value for the

5

47

4849

5051

5253

5455

5657

5859

6061

6263

6465

6667

6869

7071

72

Page 6: A GWT application using PHP and PostgreSQL

record. If this does not match, the function returns a -1.

The rest of the lines from 64 on just insert some users and verifies that the getID() function returns the user's id if the username and password match. Otherwise, a -1 is returned.

Adding initial data

Now that we can add users, let's create a script called “initUsers.sql” to add the initial users. This will allow us to make the initial user (id = 1), the admin user. Here is that script:

-- initUsers.sql - adds initial users, makes vern adminselect addUser('Vern Takebayashi','vern','vern','admin','admin');select addUser('Jane Doe','janedoe','janey','div1','div1');select addUser('John Doe','johndoe','john','div1','deptA');select addUser('Bill Gates','billgates','bill','div1','deptB');select addUser('Bruce Perens','perensb','bruce','div2','div2');select addUser('Eric Raymond','esr','esr','div2','deptC');select addUser('Linus Torvalds','linus','linux','div2','deptD');

create or replace view users_view as select id,name,username,division,department from users;

On lines 10-12 a view that will select only the fields that would be needed by the application. There is no need to get the enc_pass or salt fields. This will allow gettting the pertinent information with a simple query:select * from users_view;

At this point, it makes sense to have a SQL script that calls the other SQL scripts. This will make it easy to call all the right scripts in the right order. Here is the master SQL script, “makeAll.sql”:

-- makeAll.sql - calls all SQL scripts\i ./createTables.sql\i ./createTrigger.sql\i ./userFuncs.sql\i ./initUsers.sql

After reloading “makeAll.sql”, the database is ready to be accessed via PHP.

Using PHP to show the users

The first step is to create a PHP file that allows connecting to the database. Normally this file would be located outside the Apache document root, but to simplify things for demo purposes we will just place this file in the same location as the other project files. Here is the PHP file to include to allow connections, “demo_proj3.php”:

<?php function connect()

6

1

23

45

67

8

91011

12

1

23

45

1

2

Page 7: A GWT application using PHP and PostgreSQL

{ $pdoString = "pgsql:host=localhost dbname=demo_proj3 user=bob " . "password=somepass"; $pdo = new PDO($pdoString); return $pdo; }?>

showUsers.php

Now, we can create a PHP script that returns the users in JSON format. Here is the PHP script, “showUsers.php”:

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect"); } $query = "select * from users_view"; $results = $pdo->query($query); $dataArray = array(); while ($row = $results->fetch(PDO::FETCH_ASSOC)) { $dataArray[] = $row; } echo json_encode($dataArray);?>

If this is run from the command line you would see:

php -f showUsers.php[{"id":1,"name":"Vern Takebayashi","username":"vern","division":"admin","department":"admin"},{"id":2,"name":"Jane Doe","username":"janedoe","division":"div1","department":"div1"},{"id":3,"name":"John Doe","username":"johndoe","division":"div1","department":"deptA"},{"id":4,"name":"Bill Gates","username":"billgates","division":"div1","department":"deptB"},{"id":5,"name":"Bruce Perens","username":"perensb","division":"div2","department":"div2"},{"id":6,"name":"Eric Raymond","username":"esr","division":"div2","department":"deptC"},{"id":7,"name":"Linus Torvalds","username":"linus","division":"div2","department":"deptD"}]

Showing users in a GWT application

Create a project called “DemoProj3” with a package name of “test”. After removing all the servlet files, modify “DemoProj3.gwt.xml”:

src/test/DemoProj3.gwt.xml

<?xml version="1.0" encoding="UTF-8"?>

7

3

45

67

89

12

34

56

78

910

1112

1314

1

Page 8: A GWT application using PHP and PostgreSQL

<module rename-to='demoproj3'> <inherits name='com.google.gwt.user.User'/> <inherits name='com.google.gwt.http.HTTP'/> <inherits name='com.google.gwt.user.theme.clean.Clean'/> <collapse-all-properties /> <!-- Specify the app entry point class. --> <entry-point class='test.client.DemoProj3'/></module>

Line 3 is the standard GWT module that is used for the standard interface widgets. Line 4 is needed to make HTTP requests. Line 6 is used so that support for the different browsers is combined. This is mainly to save compilation time.

Now let's modify the main HTML file, “DemoProj3.html”.

war/DemoProj3.html

<!doctype html><html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link type="text/css" rel="stylesheet" href="DemoProj3.css"> <title>Demo Project 3</title> <script type="text/javascript" language="javascript" src="demoproj3/demoproj3.nocache.js"></script> </head> <body> <!-- JavaScript must be enabled --> <noscript> <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif"> Your web browser must have JavaScript enabled in order for this application to display correctly. </div> </noscript>

<h1>Demo Proj3</h1> </body></html>

The main things that are changed from the default values are the removal of the code needed for browser history support and the table. Line 6 and line 22 have been changed to reflect the name of this project.

Now, let's edit the main java file, “DemoProj3.java”

src/test/client/DemoProj3.java

package test.client;

import com.google.gwt.core.client.EntryPoint;import com.google.gwt.http.client.Request;

8

2

34

56

78

9

12

34

56

78

910

1112

1314

1516

1718

1920

2122

2324

1

23

4

Page 9: A GWT application using PHP and PostgreSQL

import com.google.gwt.http.client.RequestBuilder;import com.google.gwt.http.client.RequestCallback;import com.google.gwt.http.client.Response;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.ui.VerticalPanel;

public class DemoProj3 implements EntryPoint { VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; public void onModuleLoad() { String url = baseURL + "showUsers.php"; getRequest(url, "getUsers"); } private void getRequest(String url, String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,url); try { rb.sendRequest(null, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { Window.alert(response.getText()); } }); } catch (final Exception e) { Window.alert(e.getMessage()); } } // end getRequest}

Right-click on DemoProj3 in the Package Explorer, select Google, and the GWT Compile. Click on the Advanced drop down and add to the “Additional compiler arguments” the switch:-draftCompile

Then, click Apply, and then Compile.

Copying files to apache server directory

The GWT compiler converts the Java source into JavaScript files. These files need to be copied to the directory where they can be served out by the Apache server.

cp -r /home/vern/workspace/DemoProj3/war/demoproj3 /var/www/docs/cent285/demo-proj3/cp /home/vern/workspace/DemoProj3/war/DemoProj3.html

9

5

67

89

1011

1213

1415

1617

1819

2021

2223

2425

2627

2829

3031

3233

3435

3637

3839

4041

4243

Page 10: A GWT application using PHP and PostgreSQL

/var/www/docs/cent285/demo-proj3/cp /home/vern/workspace/DemoProj3/war/DemoProj3.css /var/www/docs/cent285/demo-proj3/

Each copy command should be on one line. Next, you can run the program by starting a browser and going to “http://localhost/demo-proj3/DemoProj3.html”:

When this is run, an alert showing the user data in JSON format should be displayed.

Now, we need to add a new class file to allow reading of the JSON data directly into a Java ArrayList. We also need to modify the main Java program so that it can store the data in a CellTable.

Adding the User class

We need to define a Java class that can be used to get the JSON data into our GWT program. This class will extends JavaScriptObject, so that we can use native JavaScript methods to read from the JSON data into this Java class.

Right-click on /src/test/client and select New, then Class. Set the name to “User”. Make the contents of this file as follows:

package test.client;

import com.google.gwt.core.client.JavaScriptObject;class User extends JavaScriptObject{ protected User() {} public final native int getID() /*-{ return this.id; }-*/; public final native String getName() /*-{

10

1

234

56

78

910

1112

13

Page 11: A GWT application using PHP and PostgreSQL

return this.name; }-*/; public final native String getUsername() /*-{ return this.username; }-*/; public final native String getDivision() /*-{ return this.division; }-*/; public final native String getDepartment() /*-{ return this.department; }-*/;}

Whenever you are creating classes for this purpose, the construct must have a visibility modifier of protected, and the constructor's body must be empty. This is shown on lines 6-7. Lines 8-11 define the getID() method. The body of this method must start with:/*-{

and end with:}-*/;

The body just takes the current JavaScriptObject (this) and returns the value for the appropriate key. In this case, on line 10, we are returning the value of the id key.

The same type of definition is used for all the other methods.

Now, let's modify the main Java file, “DemoProj3.java”:

package test.client;

import com.google.gwt.core.client.EntryPoint;import com.google.gwt.http.client.Request;import com.google.gwt.http.client.RequestBuilder;import com.google.gwt.http.client.RequestCallback;import com.google.gwt.http.client.Response;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.ui.VerticalPanel;import com.google.gwt.user.client.ui.RootPanel;import com.google.gwt.core.client.JsonUtils;import java.util.ArrayList;import com.google.gwt.core.client.JsArray;import com.google.gwt.user.cellview.client.CellTable;import com.google.gwt.user.cellview.client.TextColumn;

public class DemoProj3 implements EntryPoint { VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); JsArray<User> jsonData; private static class MyUser

11

14

1516

1718

1920

2122

2324

2526

2728

1

234

56

78

910

1112

1314

15

161718

1920

2122

2324

25

Page 12: A GWT application using PHP and PostgreSQL

{ private int id; private String name; private String username; private String division; private String department; public MyUser(int id, String name, String username, String division, String department) { this.id = id; this.name = name; this.username = username; this.division = division; this.department = department; } } public void onModuleLoad() { String url = baseURL + "showUsers.php"; getRequest(url, "getUsers"); RootPanel.get().add(mainPanel); } private void getRequest(String url, final String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,url); try { rb.sendRequest(null, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("getUsers")) { showUsers(response.getText()); } } }); } catch (final Exception e) { Window.alert(e.getMessage()); } } // end getRequest() private void showUsers(String responseText) { jsonData = getData(responseText); User user = null; for (int i = 1; i < jsonData.length(); i++) { user = jsonData.get(i); users.add(new MyUser(user.getID(), user.getName(),user.getUsername(),user.getDivision(), user.getDepartment())); }

12

26

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

4748

4950

5152

5354

5556

5758

5960

6162

6364

6566

6768

6970

7172

7374

7576

7778

7980

8182

83

Page 13: A GWT application using PHP and PostgreSQL

CellTable<MyUser> table = new CellTable<MyUser>(); TextColumn<MyUser> nameColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.name; } }; TextColumn<MyUser> usernameColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.username; } };

table.addColumn(nameColumn,"Name"); table.addColumn(usernameColumn,"Username"); table.setRowCount(users.size(),true); table.setRowData(0,users); mainPanel.add(table); } // end showUsers() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); }}

The new lines, lines 10-15, lines 21-23, lines 25-43, line 47, line 64-66, lines 74-109, and lines 110-113, are shown shaded.

Lines 10-15 add the import statements needed for the Java classes that we are adding for this version of the program. Line 10 adds the RootPanel class. This class is needed to insert anything into the main window of the application. The RootPanel class will be added for just about every program. Line 11 adds the JsonUtils class. We will use the safeEval() static method from this class on line 112, to safely evaluate the JSON formatted string. Line 12 adds the ArrayList class. The ArrayList class will be what we use to hold the data within the GWT application. Line 14 adds the CellTable class. We will use a CellTable to display the users data. Line 15 adds the TextColumn class that will be used to populate the CellTable.

Lines 21-22 construct an ArrayList<MyUser> to hold the users data within the GWT application. Although we will initially read our data into a JsArray, the ArrayList is the more flexible storage format. Line 23 defines the JsArray<User> called jsonData.

At this point, it is a good idea to try to understand how these two data types will be used. When the server returns the data, the data is formatted as JSON. To get the JSON data into the program, we need to read it in as an array of JavaScript objects. That is where the User

13

84

8586

8788

8990

9192

9394

9596

9798

99100

101102

103104

105106

107108

109110

111112

113114

Page 14: A GWT application using PHP and PostgreSQL

class comes into play. The User class uses JSNI (JavaScript Native Interface) methods, to extract the data from the JSON object. This is why the data is read in initially as a JsArray<User>. Although a JsArray is an array, it is not as useful to our GWT program as an ArrayList would be. Therefore, we need to pull the data from the JsArray<User> and put it into an ArrayList<MyUser>. The MyUser class is a private class defined inside the DemoProj3 class. Defining MyUser as an inner class makes it so that you don't have to provide accessor methods for the instance variables. Once we have the data in the ArrayList<MyUser>, it will be easier to manipulate the data, such as putting the data into a CellTable.

The aforementioned MyUser class is defined on lines 25-42. As stated earlier, this is defined as an inner class so that accessor methods are not needed to get to the instance variables.

Line 47 adds the VerticalPanel called “mainPanel” to the RootPanel.

Lines 64-66 call the showUsers() method. This technique is used so that the getRequest() method can be reused for returning other data from the database server. Additional selection statements can be added in the onResponseReceived() method to handle other requests. In this way, the response data can be passed off to the appropriate method for handling.

Lines 75-109 define the showUsers() method. Line 76 calls the getData() method that gets the JSON data and returns it as a JsArray<User>. The getData() method is defined on lines 110-113. Lines 78-83 define a for loop that populates the ArrayList<MyUser> called “users”. This is the ArrayList<MyUser> that was defined and initialized on lines 21-22. Lines 84 initializes the CellTable. Lines 85-93 construct a TextColumn that will be used for the name column of the CellTable. All this code does is override the getValue() method for the TextColumn class. Note on line 91 how the variable name can be accessed directly because MyUser is defined as an inner class. Similarly the TextColumn for the username is constructed on lines 94-102. Lines 104 and 105 add the TextColumns that were constructed earlier. Finally, on line 107, the data from the ArrayList<MyUser> is added into the table. Line 108 adds the table into the mainPanel.

Lines 110-113 define the getData() method. In some examples of using JSON with GWT, you will see this type of method use the native JavaScript eval() method. This is considered to be okay by many people since the data is coming from the database server, and is supposedly a trusted source. But, since attacks involving JSON often use JsArrays, it doesn't hurt to be a little safer and use JsonUtils.safeEval() instead. This will prevent attacks that put JavaScript commands inside the JSON data.

At this point, the CellTable only shows two columns. This was done to keep the code a

14

Page 15: A GWT application using PHP and PostgreSQL

little shorter. The amount of code to get the JSON data from the server into a GWT CellTable may seem lengthy, but most of the code is simple once you get used to this method. It is possible to use GWT's JSON parser and extract the data individually, but this method is simpler.

Here is the code updated to show all of the columns:

package test.client;

import com.google.gwt.core.client.EntryPoint;import com.google.gwt.http.client.Request;import com.google.gwt.http.client.RequestBuilder;import com.google.gwt.http.client.RequestCallback;import com.google.gwt.http.client.Response;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.ui.VerticalPanel;import com.google.gwt.user.client.ui.RootPanel;import com.google.gwt.core.client.JsonUtils;import java.util.ArrayList;import com.google.gwt.core.client.JsArray;import com.google.gwt.user.cellview.client.CellTable;import com.google.gwt.user.cellview.client.TextColumn;

public class DemoProj3 implements EntryPoint { VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); JsArray<User> jsonData; private static class MyUser { private int id; private String name; private String username; private String division; private String department; public MyUser(int id, String name, String username, String division, String department) { this.id = id; this.name = name; this.username = username; this.division = division; this.department = department; } } public void onModuleLoad() { String url = baseURL + "showUsers.php"; getRequest(url, "getUsers"); RootPanel.get().add(mainPanel); } private void getRequest(String url, final String requestName)

15

1

234

56

78

910

1112

1314

15

161718

1920

2122

2324

2526

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

4748

49

Page 16: A GWT application using PHP and PostgreSQL

{ final RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,url); try { rb.sendRequest(null, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("getUsers")) { showUsers(response.getText()); } } }); } catch (final Exception e) { Window.alert(e.getMessage()); } } // end getRequest() private void showUsers(String responseText) { jsonData = getData(responseText); User user = null; for (int i = 1; i < jsonData.length(); i++) { user = jsonData.get(i); users.add(new MyUser(user.getID(), user.getName(),user.getUsername(),user.getDivision(), user.getDepartment())); } CellTable<MyUser> table = new CellTable<MyUser>(); TextColumn<MyUser> idColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return "" + user.id; } }; TextColumn<MyUser> nameColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.name; } }; TextColumn<MyUser> usernameColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user)

16

50

5152

5354

5556

5758

5960

6162

6364

6566

6768

6970

7172

7374

7576

7778

7980

8182

8384

8586

8788

8990

9192

9394

9596

9798

99100

101102

103104

105106

107

Page 17: A GWT application using PHP and PostgreSQL

{ return user.username; } }; TextColumn<MyUser> divColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.division; } }; TextColumn<MyUser> deptColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.department; } }; table.addColumn(idColumn,"id"); table.addColumn(nameColumn,"Name"); table.addColumn(usernameColumn,"Username"); table.addColumn(divColumn,"Division"); table.addColumn(deptColumn,"Department"); table.setRowCount(users.size(),true); table.setRowData(0,users); mainPanel.add(table); } // end showUsers() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); }}

The new lines, lines 85-93, lines 112-129, line 130, lines 133-134, are shown shaded. This just adds in three more columns for the CellTable.

Ability to login to application

Let's add the ability to login to the application. In the GWT application, let's start by adding a panel that contains a TextBox widget and a PasswordTextBox widget to get the username and password respectively. Then, we will add the ability to send a post request to a PHP script.

Here is the new code for “DemoProj3.java”:

package test.client;

import com.google.gwt.core.client.EntryPoint;import com.google.gwt.http.client.Request;import com.google.gwt.http.client.RequestBuilder;import com.google.gwt.http.client.RequestCallback;import com.google.gwt.http.client.Response;

17

108

109110

111112

113114

115116

117118

119120

121122

123124

125126

127128

129130

131132

133134

135136

137138

139140

141142

143

1

23

45

67

Page 18: A GWT application using PHP and PostgreSQL

import com.google.gwt.user.client.Window;import com.google.gwt.user.client.ui.VerticalPanel;import com.google.gwt.event.dom.client.ClickEvent;import com.google.gwt.event.dom.client.ClickHandler;import com.google.gwt.user.client.ui.HorizontalPanel;import com.google.gwt.user.client.ui.Label;import com.google.gwt.user.client.ui.Button;import com.google.gwt.user.client.ui.TextBox;import com.google.gwt.user.client.ui.PasswordTextBox;import com.google.gwt.http.client.URL;import com.google.gwt.user.client.ui.RootPanel;import com.google.gwt.core.client.JsonUtils;import java.util.ArrayList;import com.google.gwt.core.client.JsArray;import com.google.gwt.user.cellview.client.CellTable;import com.google.gwt.user.cellview.client.TextColumn;

public class DemoProj3 implements EntryPoint, ClickHandler{ VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); JsArray<User> jsonData; VerticalPanel loginPanel = new VerticalPanel(); TextBox usernameBox; PasswordTextBox passBox; Button loginButton = new Button("Login"); private static class MyUser { private int id; private String name; private String username; private String division; private String department; public MyUser(int id, String name, String username, String division, String department) { this.id = id; this.name = name; this.username = username; this.division = division; this.department = department; } } public void onClick(ClickEvent e) { Object source = e.getSource(); if (source == loginButton) { String encData = URL.encode("username") + "=" + URL.encode(usernameBox.getText()) + "&"; encData += URL.encode("password") + "=" + URL.encode(passBox.getText()); String url = baseURL + "login.php"; postRequest(url,encData,"login"); }

18

8

910

1112

1314

1516

1718

1920

2122

23

242526

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

4748

4950

5152

5354

5556

5758

5960

6162

6364

65

Page 19: A GWT application using PHP and PostgreSQL

} public void onModuleLoad() { String url = baseURL + "showUsers.php"; //getRequest(url, "getUsers"); RootPanel.get().add(mainPanel); init(); } private void getRequest(String url, final String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,url); try { rb.sendRequest(null, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("getUsers")) { showUsers(response.getText()); } } }); } catch (final Exception e) { Window.alert(e.getMessage()); } } // end getRequest() private void postRequest(String url, String data, final String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,url); rb.setHeader("Content-type", "application/x-www-form-urlencoded"); try { rb.sendRequest(data, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("login")) { String resp = response.getText().trim(); int id = Integer.parseInt(resp); if (id < 1) { usernameBox.setText(""); passBox.setText(""); }

19

66

6768

6970

7172

7374

7576

7778

7980

8182

8384

8586

8788

8990

9192

9394

9596

9798

99100

101102

103104

105106

107108

109110

111112

113114

115116

117118

119120

121122

123

Page 20: A GWT application using PHP and PostgreSQL

else { mainPanel.clear(); Label status = new Label("id: " + id + " logged in"); mainPanel.add(status); } } } }); } catch (final Exception e) { Window.alert(e.getMessage()); } } // postRequest() ends private void showUsers(String responseText) { jsonData = getData(responseText); User user = null; for (int i = 1; i < jsonData.length(); i++) { user = jsonData.get(i); users.add(new MyUser(user.getID(), user.getName(),user.getUsername(),user.getDivision(), user.getDepartment())); } CellTable<MyUser> table = new CellTable<MyUser>(); TextColumn<MyUser> idColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return "" + user.id; } }; TextColumn<MyUser> nameColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.name; } }; TextColumn<MyUser> usernameColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.username; } }; TextColumn<MyUser> divColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) {

20

124

125126

127128

129130

131132

133134

135136

137138

139140

141142

143144

145146

147148

149150

151152

153154

155156

157158

159160

161162

163164

165166

167168

169170

171172

173174

175176

177178

179180

181

Page 21: A GWT application using PHP and PostgreSQL

return user.division; } }; TextColumn<MyUser> deptColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.department; } }; table.addColumn(idColumn,"id"); table.addColumn(nameColumn,"Name"); table.addColumn(usernameColumn,"Username"); table.addColumn(divColumn,"Division"); table.addColumn(deptColumn,"Department"); table.setRowCount(users.size(),true); table.setRowData(0,users); mainPanel.add(table); } // end showUsers() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); } private void init() { Label usernameLabel = new Label("Username: "); usernameBox = new TextBox(); HorizontalPanel userRow = new HorizontalPanel(); userRow.add(usernameLabel); userRow.add(usernameBox); loginPanel.add(userRow); Label passLabel = new Label("Password"); passBox = new PasswordTextBox(); HorizontalPanel passRow = new HorizontalPanel(); passRow.add(passLabel); passRow.add(passBox); loginPanel.add(passRow); loginButton.addClickHandler(this); loginPanel.add(loginButton); mainPanel.add(loginPanel); }}

The new lines, lines 10-17, line 25, lines 32-35, lines 55-66, line 70, lines 99-137 and lines 207-224, are shown shaded.

Lines 10-17 are the import statements for the new classes being used.

Line 10 imports the ClickEvent class. This is needed to be able to respond to the mouse click events. In our case this means responding to the mouse clicking on the “Login” button. Line 11 imports the ClickHandler interface. Note on line 25 that we implement this interface. This also is needed to respond to the click events.

21

182

183184

185186

187188

189190

191192

193194

195196

197198

199200

201202

203204

205206

207208

209210

211212

213214

215216

217218

219220

221222

223224

225

Page 22: A GWT application using PHP and PostgreSQL

Line 12 imports the HorizontalPanel class. This is used when we are setting up the input boxes for the username and password. In the init() method, on lines 207-224, the HorizontalPanel is used to place the label and the textbox on the same line. This is done on lines 211-213 and again on lines 217-219.

Line 13 imports the Label class. This is used in the init() method on lines 209 and 215. These Labels are just used to label the input boxes for username and password.

Line 14 imports the Button class. A Button is used as the button the user presses when logging in. This is added in the init() method on lines 221-222.

Line 15 imports the TextBox class. This is used in the init() method on lines 210 and 213. Also, that TextBox object is initialized on line 33. The value of this TextBox is used on line 60 to get the username that is entered.

Line 16 imports the PasswordTextBox class. This is a special TextBox that puts special characters to the screen so that the password is not visible to someone looking over the user's shoulder when they login. The PasswordTextBox object is initialized on line 34, and is used on lines 216 and 219 in the init() method. The value of this PasswordTextBox is used on line 62 to get the value of the password that is entered.

Line 17 imports the URL class. The URL.encode method is used on lines 59-62 to encode the string values so they can be passed by HTTP post methods.

On line 25, we add the ClickHandler interface so that our application can respond to ClickEvents. The onClick() method defined on lines 55-66 is used to respond to ClickEvents.

Line 32 adds an additional VerticalPanel object that will be used for the login panel. Line 33 and line 34 are used to declare the TextBox and PasswordTextBox objects that will be used for the username and password respectively. The reason why they are declared here, is so that they will be visible inside the init() method, the onClick() method, and the postRequest() method (on lines 121 and 122).

Lines 55-66 define the onClick() method that is required whenever the ClickHandler interface is implemented. On line 57, we get the object that initiated the ClickEvent. If that object is the loginButton, the if statement defined on lines 58-64 is executed. Lines 59-62 set up the encoded string that will pass the username and password via POST. The names of the keys and the values are encoded so that they can be sent via HTTP POST. Line 63 forms the URL by adding the baseURL to “login.php”. This is the PHP script that will handle the login. Finally, on line 64, the postRequest() method is called.

On line 70, the getRequest() call is commented out so that it does not get called at that point.

22

Page 23: A GWT application using PHP and PostgreSQL

Lines 99-137 define the postRequest() method. The first parameter for this method is a String that contains the URL to post to. The second parameter for this method is the encoded String data that is to be posted. The third parameter is a String that identifies the specific POST request that is being made. This last parameter is what is used inside the onResponseReceived() method, defined on lines 114-131. If you make additional post requests, you can respond to those using the same postRequest() method by changing the three parameters.

In this case, the important steps are in the if statement defined on lines 117-130. This will not make sense without understanding what is returned by the “login.php” script. The “login.php” script will return an integer. If that integer is greater than 0, it means that the user has been authenticated and the number is the user's id. If the number is less than 1, the “login.php” script has returned a -1, meaning a failed authentication. The details of how this is accomplished will be gone over when the “login.php” script is explained later. So, if “login.php” returns a -1, the if statement on lines 120-123 is executed. That if statement will just clear the input boxes. On the other hand, if a number 1 or larger is returned, the mainPanel is cleared on line 125. Then a Label is created and displayed showing the id number and a message showing the user logged in.

Lines 207-224 define the init() method that sets up the input boxes and button for the user to attempt login. Most of what goes on here has already been explained. The one line to take note of is line 221. Without the addClickHandler() method, the application cannot respond to the ClickEvent that is generated in response to clicking on loginButton.

login.php

Now, let's take a look at the PHP script, “login.php”, that is posted to in the authentication process.

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect\n"); } $out = "-1"; if (count($_POST) > 0) { $username = trim($_POST["username"]); $pass = trim($_POST["password"]); $query = "select getID(:user,:pass)"; $statement = $pdo->prepare($query); $result = $statement->execute(array(':user' => $username, ':pass' => $pass)); $row = $statement->fetch(PDO::FETCH_ASSOC); $out = $row["getid"]; } echo $out;

23

12

34

56

78

910

1112

1314

1516

1718

Page 24: A GWT application using PHP and PostgreSQL

?>

Lines 2-3 connects us to the database. Line 7 sets the value of $out to -1. Lines 8-17 define an if statement that is executed as long as values have been posted in the $_POST array. Lines 9 and 10 get the values that have been posted for username and password, respectively. Line 11 defines a SQL query that calls the getID() plpgsql function. On lines 12, 13 and 14, we prepare the SQL statement, and then execute it. By using the prepare/execute technique, SQL injection attacks are blocked. Even if someone tried to use a typical SQL injection, the getID() function would just return a -1 anyway. On line 15, we fetch the result of running that SQL statement. On line 16, we obtain the value of the “getid” key. This value will either be a number 1 or greater if authenticated, or a -1 if not authenticated. Finally, on line 19 that value is echoed to the screen for the GWT application to pick up as the response that is received.

Now that the basic login mechanism works, we can make the GWT application do something different depending on which user logs in. If the admin user logs in, a user id of 1 is returned. In that case, let's make the GWT application show the users view. If someone else logs in successfully, the GWT application will just show the id for now.

Showing the users view if admin logs in

Here is the modified part of the code:

// lines above here unchanged } // end getRequest() private void postRequest(String url, String data, final String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,url); rb.setHeader("Content-type", "application/x-www-form-urlencoded"); try { rb.sendRequest(data, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("login")) { String resp = response.getText().trim(); int id = Integer.parseInt(resp); if (id < 1) { usernameBox.setText(""); passBox.setText(""); }

24

19

Page 25: A GWT application using PHP and PostgreSQL

else if (id == 1) { mainPanel.clear(); String url = baseURL + "showUsers.php"; getRequest(url,"getUsers"); } else { mainPanel.clear(); Label status = new Label("id: " + id + " logged in"); mainPanel.add(status); } } // ends if (requestName.equals("login")) } // onResponseReceived() ends }); // new RequestCallback ends } catch (final Exception e) { Window.alert(e.getMessage()); } } // postRequest() ends// lines below here unmodified

The new lines are shown shaded. All they do is if the id returned is a 1, then the getRequest() that brings up the users view is called.

The next thing to do is to create a view for the items that are editable for the logged in user. Before we do this on the GWT side, we can return to the database and create a function for adding an item for a user, that checks to see if the user id is valid, then inserts the item and updates the items_users table.

Adding items

Adding an item is more involved than adding a user. Every item is associated with a user. So, we have to check if the user id is valid before inserting an item. In addition, the item can be set as editable by other departments. An item will be editable by the division chair of the original item submittor. But, the item is not editable by other division chairs, only be other departments.

The steps involved in submitting an item would be:

1. Check if the user id for the submittor is valid.

2. If the user id is valid, insert the item into the items table and save the newly generated item id.

3. Update the items_users table by inserting a record for the submittor's id and the item id. Add in a record for the division chair of the submittor's department.

4. Finally, add in records for the departments that have been designated as additional editors for that item.

This means we need functions at the database level that can handle this type of insertion.

25

Page 26: A GWT application using PHP and PostgreSQL

We will also need to build up an entry form in GWT that can collect this kind of information.

SQL script containing functions for inserting an item

Let's add another SQL script that adds functions that make it easier to insert an item.

-- itemFuncs.sql

create or replace function addItemUser(_uid integer, _id integer) returns boolean as $func$ begin insert into items_users (user_id, item_id) values (_uid, _id); return 't'; end; $func$ language 'plpgsql';

create or replace function addOtherEditors(_others text, _id integer) returns boolean as $func$ declare v_id integer; my_array text[]; begin my_array := string_to_array(_others,','); for i in 1..array_length(my_array,1) loop v_id := cast (my_array[i] as integer); perform addItemUser(v_id,_id); end loop; return 't'; end; $func$ language 'plpgsql'; create or replace function addItem(_uid integer, _desc text, _just text, _others text) returns boolean as $func$ declare rec record; rec2 record; div text; dept text; chair_id integer; it_id integer; begin select into rec * from users where id = _uid; if not found then return 'f'; else insert into items (description,justification, origin_department,created_at,updated_at) values (_desc, _just, rec.department,now(), now()); it_id := currval('item_id_seq');

26

1

234

56

78

910

1112

1314

1516

1718

1920

2122

2324

2526

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

4748

4950

51

Page 27: A GWT application using PHP and PostgreSQL

div := rec.division; select into rec2 * from users where department=div; chair_id := rec2.id; perform addItemUser(_uid, it_id); if rec.id <> chair_id then perform addItemUser(chair_id, it_id); end if; if _others <> '' then perform addOtherEditors(_others,it_id); end if; return 't'; end if; end; $func$ language 'plpgsql';/*select addItem(2,'item1','just1','4,5');select addItem(3,'item2','just2','4');select addItem(3,'item3','just3','');select addItem(10,'fakeitem','nojust','');*/

On lines 3-12 the addItemUser() function is defined. This function will be used to insert records into the items_users table. There is nothing fancy about this function in terms of SQL. However, this function does not return any useful result when it is run. This can be a problem when it is called inside another plpgsql function.

When calling a plpgsql function inside another plpgsql function, be careful if the called function does not return anything that is used by the calling program. If you do this, you need to use call the function using perform. That is, you call the function this way:perform func(arg1, arg2,...);

where func() is the plpgsql function that does not return anything used by the calling program. This will be shown in the next function.

Lines 14-30 define the addOtherEditors() function. This function will be used to add the other users that can edit an item. The first argument to be passed to this function is a comma-delimited string that contains a list of all the additional user ids. So, if you wanted the users with the ids 3, 4 and 6 to be added, you would pass a string like '3,4,6'. The second argument that is passed to addOtherEditors() is the id of the item that has just been added. So, if you wanted to call addOtherEditors() to add the users with ids 3,5,6 for an item with an id of 4, you would make this call:addOtherEditors('3,5,6',4);

Lines 18 and 19 declare local variables that are used in this function. Lines 21-28 form the body of this function. On line 22, we use the string_to_array() function to split the

27

52

5354

5556

5758

5960

6162

6364

6566

6768

6970

7172

Page 28: A GWT application using PHP and PostgreSQL

string with the id numbers apart, and obtain an array of strings. In this call:string_to_array(_others,',')

the first argument, _others, is the comma-delimited string of id numbers. The second argument, the comma, is the delimiter used to perform the split.

Line 23-26 define a for loop that iterates over the user id numbers. Note that arrays in plpgsql start with an index of 1 (not zero). On line 24, we CAST the string into an integer that we store in the v_id variable. We need to perform this conversion as the string_to_array () function returns an array of strings. On line 25, we call the addItemUser() function by using the perform clause. If you just called addItemUser() without preceding the function name with perform, you would get an error that looks like this:

psql:itemFuncs.sql:30: ERROR: syntax error at or near "addItemUser"LINE 1: addItemUser( $1 , $2 ) ^QUERY: addItemUser( $1 , $2 )CONTEXT: SQL statement in PL/PgSQL function "addothereditors" near line 8

This error message does not identify the error properly, and points towards the wrong line. The error is not anywhere near line 8, and it is not really a syntax error. So, if you see something like this mysterious error, think about if you are calling a plpgsql function from within another plpgsql function. If the called plpgsql function does not return anything that is used by the calling function, then you need to make the call using perform.

Another important thing to note is the choice of local variable names. Make sure that you don't choose any local variable names that match field names from the database table(s) that you are querying. This will result in difficult to track down errors. So, we use names like v_id instead of id, since id is the name of a field in the users and items tables. If you have a plpgsql function that has an unexplained syntax error or simply produces the wrong result, double-check to make sure your local variable names (defined in the declare section), don't match any field names from your database.

Paying attention to the last two paragraphs can help avoid wasting a lot of time trying to get a plpgsql function to work. Using perform to call any plpgsql function that does not return a result that is used, and avoiding local variable names that match database field names are very important techniques.

Lines 32-65 define the addItem() function. This function does a lot of things, but it is easier to perform those tasks at the database level, than in a PHP or Rails script. Lines 36-41 just declares the local variables needed by this function. On line 43, we attempt to return a

28

Page 29: A GWT application using PHP and PostgreSQL

row from the users table where the id matches the passed id. On lines 44-45, we end the function and return a False if no users have that id. If the user id is valid, the else statement defined on lines 46-61 is executed.

On lines 47-49, we perform the insertion of the item into the items table. Note on lines 48-49 we insert the department of the submitter into the origin_department field. On line 50, we store the newly generated item id in the variable, it_id. On lines 51-52, we get the division for the submitter, and get the user whose department matches this division. Only division chairs have a department that matches the division name. On line 53, we store the id for the division chair.

On line 54, we use the perform clause to call the addItemUser() function to insert a record into the items_users table. This is the insertion for the submitting user and the newly inserted item.

On lines 55-57, an if statement is used to insert a record into the items_users table for the division chair. But, if the submitter is the division chair, this insertion is not performed.

On lines 58-60, the addOtherEditors() function is called, using perform. Note that this call is only made if the string containing the id numbers for additional editors is not an empty string.

If we wanted to test these functions we could use SQL statements like:select addItem(3,'item1','just1','4,5');

This would add an item for a user with an id of 3. The item description would be 'item1', and the item's justification would be 'just1'. The additional users allowed to edit this item would be the users with id numbers of 4 and 5. If the user with an id of 3 is not the division chair, the division chair for that user's department would also be added as an editor.

Generating initial data and creating an item_view

To make it easier to develop the GWT code that displays the items, we should add some items for the users. This can be done with a SQL script, called “itemData.sql”:

-- itemData.sqlselect addItem(2,'item1','justification1','3,5');select addItem(3,'item2','justification2','5');select addItem(3,'item3','justification3','');select addItem(4,'item4','justification4','3');select addItem(6,'item5','justification5','3');select addItem(6,'item6','justification6','4,7');select addItem(7,'item7','justification7','3,6');

We should create another script, called “itemView.sql”, that creates a database view that can be used to display a user's items. Here is the code for that script:

29

1

23

45

67

8

Page 30: A GWT application using PHP and PostgreSQL

-- itemView.sqlcreate or replace view item_view as select users.id as uid, users.name, users.username, users.division, users.department, items.id as iid, items.description, items.justification, items.origin_department, items.created_at, items.updated_at from users inner join items_users on users.id=items_users.user_id inner join items on items_users.item_id=items.id; -- test queriesselect name,department,origin_department,description from item_view where uid=2;

The key thing about generating a view that involves joining several tables is to make sure that if you have repeated field names, as we do in this case with the id fields, to use aliases for those fields. So on line 3, we create an alias for users.id called “uid”. Similarly on line 4, we create an alias for items.id called “iid”. Lines 10 on just show a sample query using this view. Here is the result of loading the “itemView.sql” script:

\i itemView.sqlCREATE VIEW name | department | origin_department | description ----------+------------+-------------------+------------- Jane Doe | div1 | div1 | item1 Jane Doe | div1 | deptA | item2 Jane Doe | div1 | deptA | item3 Jane Doe | div1 | deptB | item4(4 rows)

Jane Doe is a division chair, so she can see items submitted by her (item1), as well as items submitted by departments in her division (item2, item3 and item4).

Creating a PHP script that can send the items

Now that we created a view in the database for viewing items, let's create a PHP script that will return the items in JSON format. Here is the script “showUserItems.php”:

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect"); } //$_POST['uid'] = 6; if (count($_POST) > 0) { $uid = $_POST["uid"]; $query = "select * from item_view where uid=:uid"; $results = $pdo->prepare($query); $results->execute(array(':uid' => $uid)); $dataArray = array(); while ($row = $results->fetch(PDO::FETCH_ASSOC)) { $dataArray[] = $row; }

30

1

23

45

67

89

1011

12

1

23

45

67

89

1011

1213

1415

16

Page 31: A GWT application using PHP and PostgreSQL

echo json_encode($dataArray); }?>

Line 7 is commented out, but if this were uncommented, this would produce the following output:[{"uid":6,"name":"Eric Raymond","username":"esr","division":"div2","department":"deptC","iid":5,"description":"item5","justification":"justification5","origin_department":"deptC","created_at":"2012-01-04 10:00:58.822101","updated_at":"2012-01-04 10:00:58.822101"},{"uid":6,"name":"Eric Raymond","username":"esr","division":"div2","department":"deptC","iid":6,"description":"item6","justification":"justification6","origin_department":"deptC","created_at":"2012-01-04 10:00:58.825481","updated_at":"2012-01-04 10:00:58.825481"},{"uid":6,"name":"Eric Raymond","username":"esr","division":"div2","department":"deptC","iid":7,"description":"item7","justification":"justification7","origin_department":"deptD","created_at":"2012-01-04 10:00:58.8306","updated_at":"2012-01-04 10:00:58.8306"}]

Lines 2-3 just obtain a connection to the database. The if statement defined on lines 1-18 will query the database and write the output to the screen in JSON format. Line 10 sets up a query where we specify the user's id to limit the rows returned to just the records that this particular user is allowed to edit. Lines 11 and 12 use prepare/execute to protect against SQL Injection attacks.

Creating panel in GWT to view items

Once a user, other than the administrator, logs in, he/she should be presented with a view of their current items. This view should also provide a way to add new items. Let's start with generating a panel that can view a user's current items.

Since we will pass the data in JSON format, we can add a new class, Item.java, that contains the JSNI functions needed for reading the data into a JsArray. Then, we can create an inner class MyItem that will be used for the ArrayList that we need to get the data into. We also can add a method called “showUserItems” that can be called from within the onResponseReceived() method within the getRequest() method. The showUserItems() method will populate a CellTable with the items.

To create Item.java, right-click on the src.test.client and choose New=>Class. Here is the contents of Item.java:

package test.client;

import com.google.gwt.core.client.JavaScriptObject;import java.util.Date;class Item extends JavaScriptObject

31

17

1819

1

23

45

Page 32: A GWT application using PHP and PostgreSQL

{ protected Item() {} public final native int getUserID() /*-{ return this.uid; }-*/; public final native String getName() /*-{ return this.name; }-*/; public final native String getUsername() /*-{ return this.username; }-*/; public final native String getDivision() /*-{ return this.division; }-*/; public final native String getDepartment() /*-{ return this.department; }-*/; public final native int getItemID() /*-{ return this.iid; }-*/; public final native String getDescription() /*-{ return this.description; }-*/; public final native String getJustification() /*-{ return this.justification; }-*/; public final native String getOriginalDeparment() /*-{ return this.origin_department; }-*/; public final native Date getCreatedAt() /*-{ return this.created_at; }-*/; public final native Date getUpdatedAt() /*-{ return this.updated_at; }-*/;}

There is nothing fancy going on with this class. Let's look at the modified DemoProj3.java file next:

package test.client;

import com.google.gwt.core.client.EntryPoint;import com.google.gwt.http.client.Request;import com.google.gwt.http.client.RequestBuilder;

32

6

78

910

1112

1314

1516

1718

1920

2122

2324

2526

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

4748

4950

5152

53

1

23

45

Page 33: A GWT application using PHP and PostgreSQL

import com.google.gwt.http.client.RequestCallback;import com.google.gwt.http.client.Response;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.ui.VerticalPanel;import com.google.gwt.event.dom.client.ClickEvent;import com.google.gwt.event.dom.client.ClickHandler;import com.google.gwt.user.client.ui.HorizontalPanel;import com.google.gwt.user.client.ui.Label;import com.google.gwt.user.client.ui.Button;import com.google.gwt.user.client.ui.TextBox;import com.google.gwt.user.client.ui.PasswordTextBox;import com.google.gwt.http.client.URL;import com.google.gwt.user.client.ui.RootPanel;import com.google.gwt.core.client.JsonUtils;import java.util.ArrayList;import com.google.gwt.core.client.JsArray;import com.google.gwt.user.cellview.client.CellTable;import com.google.gwt.user.cellview.client.TextColumn;import java.util.Date;

public class DemoProj3 implements EntryPoint, ClickHandler{ VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); JsArray<User> jsonData; VerticalPanel loginPanel = new VerticalPanel(); TextBox usernameBox; PasswordTextBox passBox; Button loginButton = new Button("Login"); int user_id; private static class MyUser { private int id; private String name; private String username; private String division; private String department; public MyUser(int id, String name, String username, String division, String department) { this.id = id; this.name = name; this.username = username; this.division = division; this.department = department; } } private static class MyItem { private int uid; private String name; private String username; private String division; private String department;

33

6

78

910

1112

1314

1516

1718

1920

2122

2324

2526

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

4748

4950

5152

5354

5556

5758

5960

6162

63

Page 34: A GWT application using PHP and PostgreSQL

private int iid; private String description; private String justification; private String origin_department; private Date created_at; private Date updated_at; public MyItem(int uid, String name, String user, String div, String dept, int iid, String desc, String just, String orig_dept, Date create, Date update) { this.uid = uid; this.name = name; username = user; division = div; department = dept; this.iid = iid; description = desc; justification = just; origin_department = orig_dept; created_at = create; updated_at = update; } } public void onClick(ClickEvent e) { Object source = e.getSource(); if (source == loginButton) { String encData = URL.encode("username") + "=" + URL.encode(usernameBox.getText()) + "&"; encData += URL.encode("password") + "=" + URL.encode(passBox.getText()); String url = baseURL + "login.php"; postRequest(url,encData,"login"); } } public void onModuleLoad() { RootPanel.get().add(mainPanel); init(); } private void getRequest(String url, final String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.GET,url); try { rb.sendRequest(null, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("getUsers")) {

34

64

6566

6768

6970

7172

7374

7576

7778

7980

8182

8384

8586

8788

8990

9192

9394

9596

9798

99100

101102

103104

105106

107108

109110

111112

113114

115116

117118

119120

121

Page 35: A GWT application using PHP and PostgreSQL

showUsers(response.getText()); } } }); } catch (final Exception e) { Window.alert(e.getMessage()); } } // end getRequest() private void postRequest(String url, String data, final String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,url); rb.setHeader("Content-type", "application/x-www-form-urlencoded"); try { rb.sendRequest(data, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("login")) { String resp = response.getText().trim(); user_id = Integer.parseInt(resp); if (user_id < 1) { usernameBox.setText(""); passBox.setText(""); } else if (user_id == 1) { mainPanel.clear(); String url = baseURL + "showUsers.php"; getRequest(url,"getUsers"); } else { mainPanel.clear(); String encData = URL.encode("uid") + "=" + URL.encode("" + user_id); String url = baseURL + "showUserItems.php"; postRequest(url,encData,"getUserItems"); } } // ends if (requestName.equals("login")) else if (requestName.equals("getUserItems")) { Window.alert(response.getText()); } // ends else if requestName is "getUserItems" } // onResponseReceived() ends }); // new RequestCallback ends } catch (final Exception e) { Window.alert(e.getMessage()); } } // postRequest() ends private void showUsers(String responseText)

35

122

123124

125126

127128

129130

131132

133134

135136

137138

139140

141142

143144

145146

147148

149150

151152

153154

155156

157158

159160

161162

163164

165166

167168

169170

171172

173174

175176

177178

179

Page 36: A GWT application using PHP and PostgreSQL

{ jsonData = getData(responseText); User user = null; for (int i = 1; i < jsonData.length(); i++) { user = jsonData.get(i); users.add(new MyUser(user.getID(), user.getName(),user.getUsername(),user.getDivision(), user.getDepartment())); } CellTable<MyUser> table = new CellTable<MyUser>(); TextColumn<MyUser> idColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return "" + user.id; } }; TextColumn<MyUser> nameColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.name; } }; TextColumn<MyUser> usernameColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.username; } }; TextColumn<MyUser> divColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.division; } }; TextColumn<MyUser> deptColumn = new TextColumn<MyUser>() { @Override public String getValue(MyUser user) { return user.department; } }; table.addColumn(idColumn,"id number"); table.addColumn(nameColumn,"Name"); table.addColumn(usernameColumn,"Username");

36

180

181182

183184

185186

187188

189190

191192

193194

195196

197198

199200

201202

203204

205206

207208

209210

211212

213214

215216

217218

219220

221222

223224

225226

227228

229230

231232

233234

235236

237

Page 37: A GWT application using PHP and PostgreSQL

table.addColumn(divColumn,"Division"); table.addColumn(deptColumn,"Department"); table.setRowCount(users.size(),true); table.setRowData(0,users); mainPanel.add(table); } // end showUsers() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); } private void init() { Label usernameLabel = new Label("Username: "); usernameBox = new TextBox(); HorizontalPanel userRow = new HorizontalPanel(); userRow.add(usernameLabel); userRow.add(usernameBox); loginPanel.add(userRow); Label passLabel = new Label("Password"); passBox = new PasswordTextBox(); HorizontalPanel passRow = new HorizontalPanel(); passRow.add(passLabel); passRow.add(passBox); loginPanel.add(passRow); loginButton.addClickHandler(this); loginPanel.add(loginButton); mainPanel.add(loginPanel); }}

The new lines are shown shaded. The are line 24, line 37, lines 57-88, line 151, line 152, line 156, lines 163-166 and lines 169-171.

Line 24 just adds an import statement for the Date class. Line 37 defines an instance variable, user_id, to hold the id of the logged in user.

Lines 57-88 define the inner class, MyItem. This is the class that will be used to hold the data when it is transferred into an ArrayList from a JsArray.

Line 151 and line 152 have been changed to use the instance variable, user_id instead of id. Line 156 has also been changed to use the instance variable, user_id instead of id.

Lines 163-166 have been modified so that we can send the user's id via POST to the PHP script. Line 166 calls the postRequest() method and passed the user's id.

Lines 169-171 respond to the last postRequest() call. At this point, all that is done is to show the JSON formatted data that is returned in an alert window.

The next step is to take the JSON formatted data and read this into a JsArray. Then, the data will be transferred from the JsArray<Item> to an ArrayList<MyItem>. The ArrayList<MyItem> will be used to populate a CellTable. Here are the portions of the code that get changed:

37

238

239240

241242

243244

245246

247248

249250

251252

253254

255256

257258

259260

261262

263264

265266

Page 38: A GWT application using PHP and PostgreSQL

// lines above unmodified

public class DemoProj3 implements EntryPoint, ClickHandler{ VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); ArrayList<MyItem> items = new ArrayList<MyItem>(); JsArray<User> jsonData; JsArray<Item> jsonItemData; VerticalPanel loginPanel = new VerticalPanel(); TextBox usernameBox; PasswordTextBox passBox; Button loginButton = new Button("Login"); int user_id;// lines in between here unmodified

// lines in between here unmodified

private void postRequest(String url, String data, final String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,url); rb.setHeader("Content-type", "application/x-www-form-urlencoded"); try { rb.sendRequest(data, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("login")) { String resp = response.getText().trim(); user_id = Integer.parseInt(resp); if (user_id < 1) { usernameBox.setText(""); passBox.setText(""); } else if (user_id == 1) { mainPanel.clear(); String url = baseURL + "showUsers.php"; getRequest(url,"getUsers"); } else { mainPanel.clear(); String encData = URL.encode("uid") + "=" + URL.encode("" + user_id); String url = baseURL + "showUserItems.php"; postRequest(url,encData,"getUserItems"); } } // ends if (requestName.equals("login"))

38

2627

2829

3031

3233

3435

3637

3839

133134

135136

137138

139140

141142

143144

145146

147148

149150

151152

153154

155156

157158

159160

161162

163164

165166

167168

169170

Page 39: A GWT application using PHP and PostgreSQL

else if (requestName.equals("getUserItems")) { showUserItems(response.getText()); } // ends else if requestName is "getUserItems" } // onResponseReceived() ends }); // new RequestCallback ends } catch (final Exception e) { Window.alert(e.getMessage()); } } // postRequest() ends private void showUsers(String responseText)// code for showUsers() not shown // but is unmodified } // end showUsers() private void showUserItems(String responseText) { jsonItemData = getItemData(responseText); Item item = null; for (int i = 0; i < jsonItemData.length(); i++) { item = jsonItemData.get(i); items.add(new MyItem(item.getUserID(), item.getName(), item.getUsername(), item.getDivision(),item.getDepartment(), item.getItemID(),item.getDescription(), item.getJustification(),item.getOriginalDeparment(), item.getCreatedAt(),item.getUpdatedAt())); } CellTable<MyItem> table = new CellTable<MyItem>(); TextColumn<MyItem> nameColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.name; } }; TextColumn<MyItem> origDeptColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.origin_department; } }; table.addColumn(nameColumn,"Name"); table.addColumn(origDeptColumn,"Orig Dept"); table.setRowCount(items.size(),true); table.setRowData(0,items); mainPanel.add(table); } // end showUserItems() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); } private JsArray<Item> getItemData(String json) {

39

171

172173

174175

176177

178179

180181

245246

247248

249250

251252

253254

255256

257258

259260

261262

263264

265266

267268

269270

271272

273274

275276

277278

279280

281282

283284

285286

287288

289

Page 40: A GWT application using PHP and PostgreSQL

return JsonUtils.safeEval(json); } private void init() { Label usernameLabel = new Label("Username: "); usernameBox = new TextBox(); HorizontalPanel userRow = new HorizontalPanel(); userRow.add(usernameLabel); userRow.add(usernameBox); loginPanel.add(userRow); Label passLabel = new Label("Password"); passBox = new PasswordTextBox(); HorizontalPanel passRow = new HorizontalPanel(); passRow.add(passLabel); passRow.add(passBox); loginPanel.add(passRow); loginButton.addClickHandler(this); loginPanel.add(loginButton); mainPanel.add(loginPanel); }}

Large amounts of code are omitted because those lines are unchanged from the previous version. The new lines are shown shaded. The new lines are line 32, line 34, line 172, lines 246-283 and lines 288-291.

Line 32 adds the instance variable of type ArrayList<MyItem> called items. This is where we will store the item data that will be used within the GWT program.

Line 34 adds the instance variable of type JsArray<Item> called jsonItemData. This is where we store the item when we initially read in the JSON data.

Line 172 calls the new method showUserItems(). The showUserItems() method is defined on lines 246-283. On line 248, we call the getItemData() method. This will read the JSON data into the JsArray<Item>. Lines 249-258 are used to read the data from the JsArray<Item> into the ArrayList<MyItem> called items. This puts the data into a format that can be easily used with the GWT program. For example, this is the format needed to populate a CellTable.

Line 259 constructs the CellTable object that will be used to display the items. Lines 260-268 create a column that will be used to include the user's name. Lines 269-277 create a column that will be used to include the originating department. Although we will eventually show more columns, this is good enough to see if things are working correctly.

Line 278 and line 279 add the columns for the name and the originating department, respectively, into the CellTable.

Line 280 sets the row count for the CellTable, and line 281 adds the table to the main panel.

40

290

291292

293294

295296

297298

299300

301302

303304

305306

307308

309310

Page 41: A GWT application using PHP and PostgreSQL

Lines 288-291 define the getItemData() method. All this method does is call JsonUtils.safeEval() to safely evaluate the JSON-formatted data, and read the data into a JsArray<Item>.

Once you confirm that the CellTable is working correctly, we can add in more of the columns.

Handling date fields

When we create the JSON data with our PHP script, it turns out that we have a problem with date fields. Even though the data is stored as type timestamp, JSON has no format for dates. This means that the dates are passed as strings. This can be a problem when putting the data into the GWT CellTable. The strings that are passed to the GWT application cannot be properly converted into java.util.Date objects.

One way to deal with this is to select the data from the database and reformat the date fields as UNIX timestamps. Passing a UNIX timestamp as a String allows us to eventually get a java.util.Date object. Here is the basic process:

1. Convert the postgresql date value into a UNIX timestamp.

2. Use the PHP function json_encode() to pass this UNIX timestamp as a string.

3. In GWT read the UNIX timestamp as a java.lang.String.

4. Use Double.parseDouble() to convert the String into a double.

5. Multiply that double by 1,000.

6. Round the resulting double using Math.round() to get a long.

7. Use the java.util.Date constructor that takes a long as the argument.

As an example, suppose the value stored in the database for a date is:2012-01-02 9:30:21.1278

This can be converted to a UNIX timestamp using the date_part() function with 'epoch' as the first argument, and the date as the second argument. This can be visualized inside psql in the following way:

select date_part('epoch', timestamp '2012-01-01 9:30:21.1278'); date_part ----------------- 1325446221.1278(1 row)

If this value is read into the PHP script in a query and encoded using json_encode(), the result will look like this:"created_at":"1325446221.1278"

Inside the GWT program, this is first read into the JsArray as a String. Then, inside the

41

Page 42: A GWT application using PHP and PostgreSQL

constructor for MyItem, this can be converted in the following way:

double numdate = Double.parseDouble(create); numdate = numdate * 1000; created_at = new Date(Math.round(numdate));

That code assumes that create is the name of the parameter that holds the created_at value.

To make these changes, let's start by changing some of our SQL scripts. First, let's change “createTables.sql”:

-- createTables.sql

-- create sequencesdrop sequence if exists user_id_seq cascade;create sequence user_id_seq;drop sequence if exists item_id_seq cascade;create sequence item_id_seq;

-- create tablesdrop table if exists users cascade;create table users( id integer not null default nextval('user_id_seq'), name text, username text unique, password text, enc_pass text, salt text, division text, department text, primary key (id));drop table if exists items cascade;create table items( id integer not null default nextval('item_id_seq'), description text, justification text, origin_department text, created_at timestamp default now(), updated_at timestamp default now(), num_low integer default 0, num_med integer default 0, num_high integer default 0, primary key (id));drop table if exists items_users cascade;create table items_users( user_id integer references users(id), item_id integer references items(id) on delete cascade, unique (user_id,item_id));

The modified lines are lines 28 and 29. This just sets a default value for both timestamp

42

1

23

45

67

89

1011

1213

1415

1617

1819

2021

2223

2425

2627

2829

3031

3233

3435

3637

3839

40

Page 43: A GWT application using PHP and PostgreSQL

fields. This will make it so that we don't have to insert the values explicitly when adding an item.

Let's also modify the “itemFuncs.sql” script:

-- itemFuncs.sql

create or replace function addItemUser(_uid integer, _id integer) returns boolean as $func$ begin insert into items_users (user_id, item_id) values (_uid, _id); return 't'; end; $func$ language 'plpgsql';

create or replace function addOtherEditors(_others text, _id integer) returns boolean as $func$ declare v_id integer; my_array text[]; begin my_array := string_to_array(_others,','); for i in 1..array_length(my_array,1) loop v_id := cast (my_array[i] as integer); perform addItemUser(v_id,_id); end loop; return 't'; end; $func$ language 'plpgsql'; create or replace function addItem(_uid integer, _desc text, _just text, _others text) returns boolean as $func$ declare rec record; rec2 record; div text; chair_id integer; it_id integer; begin select into rec * from users where id = _uid; if not found then return 'f'; else insert into items (description,justification, origin_department) values (_desc, _just, rec.department); it_id := currval('item_id_seq'); div := rec.division; select into rec2 * from users where department=div;

43

1

234

56

78

910

1112

1314

1516

1718

1920

2122

2324

2526

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

4748

4950

5152

Page 44: A GWT application using PHP and PostgreSQL

chair_id := rec2.id; perform addItemUser(_uid, it_id); if rec.id <> chair_id then perform addItemUser(chair_id, it_id); end if; if _others <> '' then perform addOtherEditors(_others,it_id); end if; return 't'; end if; end; $func$ language 'plpgsql';

The changes to this file are on lines 47-49. This just reflects the simpler insert statement due to the default values for the created_at and updated_at fields.

Finally, we can modify “makeAll.sql” to load all of the SQL scripts we have at this point:

-- makeAll.sql - calls all SQL scripts\i ./createTables.sql\i ./createTrigger.sql\i ./userFuncs.sql\i ./initUsers.sql\i ./itemFuncs.sql\i ./itemData.sql\i ./itemView.sql

In the GWT project, we can start by modifying “Item.java” so that it reads in the date fields as Strings.

package test.client;

import com.google.gwt.core.client.JavaScriptObject;class Item extends JavaScriptObject { protected Item() {} public final native int getUserID() /*-{ return this.uid; }-*/; public final native String getName() /*-{ return this.name; }-*/; public final native String getUsername() /*-{ return this.username; }-*/; public final native String getDivision() /*-{ return this.division; }-*/; public final native String getDepartment()

44

53

5455

5657

5859

6061

6263

6465

1

23

45

67

8

1

23

45

67

89

1011

1213

1415

1617

1819

2021

2223

24

Page 45: A GWT application using PHP and PostgreSQL

/*-{ return this.department; }-*/; public final native int getItemID() /*-{ return this.iid; }-*/; public final native String getDescription() /*-{ return this.description; }-*/; public final native String getJustification() /*-{ return this.justification; }-*/; public final native String getOriginalDeparment() /*-{ return this.origin_department; }-*/; public final native String getCreatedAt() /*-{ return this.created_at; }-*/; public final native String getUpdatedAt() /*-{ return this.updated_at; }-*/;}

The only lines that are modified are lines 44 and 48. The return type has been changed to String, instead of Date. Also, the import statement for java.util.Date has been removed.

Now, we can modify “DemoProj3.java” in our GWT application so that we can see how a column that has Date data can be included in a CellTable.

// lines 1-24 unmodifiedimport com.google.gwt.cell.client.DateCell;import com.google.gwt.user.cellview.client.Column;// lines 27-60 unmodified private static class MyItem { private int uid; private String name; private String username; private String division; private String department; private int iid; private String description; private String justification; private String origin_department; private Date created_at; private Date updated_at; public MyItem(int uid, String name, String user, String div, String dept, int iid, String desc, String just, String orig_dept,

45

25

2627

2829

3031

3233

3435

3637

3839

4041

4243

4445

4647

4849

5051

52

25

26

6162

6364

6566

6768

6970

7172

7374

7576

77

Page 46: A GWT application using PHP and PostgreSQL

String create, String update) { this.uid = uid; this.name = name; username = user; division = div; department = dept; this.iid = iid; description = desc; justification = just; origin_department = orig_dept; double numdate = Double.parseDouble(create); numdate = numdate*1000; created_at = new Date(Math.round(numdate)); numdate = Double.parseDouble(update)*1000; updated_at = new Date(Math.round(numdate)); } }//// lines 96-273 are unmodified// TextColumn<MyItem> origDeptColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.origin_department; } }; DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override public Date getValue(MyItem item) { return (Date)item.created_at; } }; table.addColumn(nameColumn,"Name"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(createDateColumn,"Created"); table.setRowCount(items.size(),true); table.setRowData(0,items); mainPanel.add(table); } // end showUserItems() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); }// Lines 304 on are unmodified

The modified lines are shaded. The modified lines are lines 25-26, line 78, lines 89-93, lines 284-292 and line 295.

Lines 25 and 26 import the new classes needed for to create a CellTable column that can

46

78

7980

8182

8384

8586

8788

8990

9192

9394

95

274275

276277

278279

280281

282283

284285

286287

288289

290291

292293

294295

296297

298299

300301

302303

Page 47: A GWT application using PHP and PostgreSQL

hold a java.util.Date object.

Line 78 is modified by changing the variable type from Date to String for both the create and update parameters. This is because we are now passing the data as Strings.

Lines 89-93 show the new lines inside the MyItem constructor. For each date field, the passed String is converted into a double, multiplied by 1000, and rounded into the nearest long value. That long value is used to construct a Date object.

Lines 284-292 construct the column that the created_at dates will be inserted into. This shows how to insert values other than Strings. The other columns are TextColumns since they hold String data.

Finally, on line 295, we add the newly created column to the table.

Here is another modification to “DemoProj3.java” to add a column that shows the updated_at date:

DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override public Date getValue(MyItem item) { return item.created_at; } }; DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; table.addColumn(nameColumn,"Name"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); mainPanel.add(table); } // end showUserItems() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); }// code below unmodified

Lines 293-302 create the column for inserting the update_at data. This is very similar to the column created for the created_at data. Line 306 just adds this column into the table.

47

284285

286287

288289

290291

292293

294295

296297

298299

300301

302303

304305

306307

308309

310311

312313

314

Page 48: A GWT application using PHP and PostgreSQL

Making the CellTable sortable

The extra effort to get the columns for the created_at and updated_at fields to be entered as Date objects, is to make those columns sortable. This is so you can see which items have been modified most recently, for example. So, let's make additions to “DemoProj3.java” to make those columns sortable.

package test.client;

import com.google.gwt.core.client.EntryPoint;import com.google.gwt.http.client.Request;import com.google.gwt.http.client.RequestBuilder;import com.google.gwt.http.client.RequestCallback;import com.google.gwt.http.client.Response;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.ui.VerticalPanel;import com.google.gwt.event.dom.client.ClickEvent;import com.google.gwt.event.dom.client.ClickHandler;import com.google.gwt.user.client.ui.HorizontalPanel;import com.google.gwt.user.client.ui.Label;import com.google.gwt.user.client.ui.Button;import com.google.gwt.user.client.ui.TextBox;import com.google.gwt.user.client.ui.PasswordTextBox;import com.google.gwt.http.client.URL;import com.google.gwt.user.client.ui.RootPanel;import com.google.gwt.core.client.JsonUtils;import java.util.ArrayList;import com.google.gwt.core.client.JsArray;import com.google.gwt.user.cellview.client.CellTable;import com.google.gwt.user.cellview.client.TextColumn;import java.util.Date;import com.google.gwt.cell.client.DateCell;import com.google.gwt.user.cellview.client.Column;import java.util.Comparator;import java.util.List;import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;import com.google.gwt.view.client.ListDataProvider;

public class DemoProj3 implements EntryPoint, ClickHandler{ VerticalPanel mainPanel = new VerticalPanel();//// lines 35-296 are unmodified// DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>();

48

1

23

45

67

89

1011

1213

1415

1617

1819

2021

2223

2425

2627

2829

30

313233

34

297298

299300

301302

303304

305306

307308

Page 49: A GWT application using PHP and PostgreSQL

dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) { list.add(itm); } makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update"); table.addColumn(nameColumn,"Name"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); mainPanel.add(table); } // end showUserItems() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); } private JsArray<Item> getItemData(String json) { return JsonUtils.safeEval(json); } private void makeSortableDateColumn(CellTable<MyItem> table, Column<MyItem,Date> col, List<MyItem> list, final String type) { col.setSortable(true); ListHandler<MyItem> handler = new ListHandler<DemoProj3.MyItem>(list); handler.setComparator(col, new Comparator<DemoProj3.MyItem>() { @Override public int compare(DemoProj3.MyItem i1, DemoProj3.MyItem i2) { if (i1 == i2) { return 0; } if (i1 != null) { if (type.equals("create")) { return (i2 != null) ? i1.created_at.compareTo(i2.created_at) : 1; } else if (type.equals("update")) { return (i2 != null) ? i1.updated_at.compareTo(i2.updated_at) : 1; } } return -1; } }); table.addColumnSortHandler(handler); } private void init() {// Lines 365 on are unmodified

49

309

310311

312313

314315

316317

318319

320321

322323

324325

326327

328329

330331

332333

334335

336337

338339

340341

342343

344345

346347

348349

350351

352353

354355

356357

358359

360361

362363

364

Page 50: A GWT application using PHP and PostgreSQL

The new lines are shown shaded. These are lines 27-30, lines 307-315 and lines 332-362.

Lines 27-30 just add the import statements for the new classes needed. Line 27 includes the Comparator class. This is the class used to make comparisons for sorting purposes. We will use this class on line 340, to make allow sorting on a column. Line 28 adds the List class. This is used on line 310 when we create a List that holds the same data as the CellTable. Since a List object is sortable, we can sort the data. If we can tie the List to the CellTable, then the CellTable will be sorted as well. On line 30 we import the ListDataProvider class. This is the class that is used to tie the List to the CellTable. On line 29 we import the ListHandler class. This is the class that is used to attach a method for handling the sorting, when we click on a column.

Lines 307-308 construct a ListDataProvider<MyItem> called dataProvider. On line 309 this object is connected to the CellTable. Lines 310-313 construct and populate a List for dataProvider that contains all the same data as the CellTable. These steps connect a sortable object to the CellTable.

Lines 314 and 315 call the makeSortableDateColumn() method. This method is defined on lines 332-362. This method will add a ListHandler to a column and make that column sortable. This method is geared specifically to handle a column of type Column<MyItem, Date>.

Inside the makeSortableDateColumn() method, on line 336 we set the column to be sortable. However, this by itself does not make the column sort, because the program needs to know how to compare two objects in that column. To do that, we need to create a ListHandler that will be called when you click on the column header. Lines 337-338 just construct a ListHandler object that is tied to the List constructed on line 310. Lines 340-360 just create a new Comparator object to use to sort the data. A Comparator object has a compare() method, and this is defined on lines 343-359. On lines 345-347, an if statement that tests to see if the objects are the same object is defined. This will return the number zero if this is the case (that is they are equal). On lines 350-351 and on lines 354-355, a ternary operator is used to compare the Date objects. A ternary operator works like this:(a < b) ? a : b

If the (a < b) is true, this returns a. Otherwise, it returns b. So, in our case, the program check to see if the objects (i1 and i2) are null. If both are not null, the program returns i1.created_at.compareTo(i2.created_at) and returns the result. This works because the Date object has a compareTo() method that performs this comparison. A similar thing is done on lines 354-355, but the updated_at value is used instead.

Sorting by a TextColumn

We can also sort by columns that contain Strings. Here are the modifications needed to sort

50

Page 51: A GWT application using PHP and PostgreSQL

by the origin_department field column.

\\ Lines 1-315 are unmodified makeSortableTextColumn(table,origDeptColumn,list,"orig_dept"); table.addColumn(nameColumn,"Name"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); mainPanel.add(table); } // end showUserItems() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json); }//// Lines 329-361 are unmodified// table.addColumnSortHandler(handler); } private void makeSortableTextColumn(CellTable<MyItem> table, TextColumn<MyItem> col, List<MyItem> list, final String type) { col.setSortable(true); ListHandler<MyItem> handler = new ListHandler<DemoProj3.MyItem>(list); handler.setComparator(col, new Comparator<DemoProj3.MyItem>() { @Override public int compare(DemoProj3.MyItem i1, DemoProj3.MyItem i2) { if (i1 == i2) { return 0; } if (i1 != null) { if (type.equals("orig_dept")) { return (i2 != null) ? i1.origin_department.compareTo( i2.origin_department) : 1; } } return -1; } }); table.addColumnSortHandler(handler); } private void init() {// Lines 394 on are unmodified

The new lines are line 316 and lines 364-391. Line 316 just calls the makeSortableTextColumn() method. That method is defined on lines 364-391. This

51

316317

318319

320321

322323

324325

326327

328

362363

364365

366367

368369

370371

372373

374375

376377

378379

380381

382383

384385

386387

388389

390391

392393

Page 52: A GWT application using PHP and PostgreSQL

method is very similar to the makeSortableDateColumn() method. In fact, these methods could be combined if it were not for the fact that they work on a different type of column. The makeSortableDateColumn() method uses a Column<MyItem, Date>, while the makeSortableTextColumn() method uses a TextColumn<MyItem>. Otherwise, they do just about the same thing.

Allowing a user to add a new item

Let's start by adding a Button on top of the CellTable that displays the items. To give us more space, let's modify “DemoProj3.css”. Simply delete all the lines. We will add some lines later to clean up buttons and text boxes. But for now, just delete all the lines.

Run the GWT application in the browser to see how the empty lines below the heading1 element are gone.

Inside “DemoProj3.java”, make the following changes:

// Lines 1-31 are unmodifiedpublic class DemoProj3 implements EntryPoint, ClickHandler{ VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); ArrayList<MyItem> items = new ArrayList<MyItem>(); JsArray<User> jsonData; JsArray<Item> jsonItemData; VerticalPanel loginPanel = new VerticalPanel(); TextBox usernameBox; PasswordTextBox passBox; Button loginButton = new Button("Login"); int user_id; Button addItemButton = new Button("Add new item");//// Lines 47- 256 are unmodified // private void showUserItems(String responseText) { jsonItemData = getItemData(responseText); Item item = null; for (int i = 0; i < jsonItemData.length(); i++) { item = jsonItemData.get(i); items.add(new MyItem(item.getUserID(), item.getName(), item.getUsername(), item.getDivision(),item.getDepartment(), item.getItemID(),item.getDescription(), item.getJustification(),item.getOriginalDeparment(), item.getCreatedAt(),item.getUpdatedAt())); } CellTable<MyItem> table = new CellTable<MyItem>(); TextColumn<MyItem> nameColumn = new TextColumn<MyItem>() {

52

32

3334

3536

3738

3940

4142

4344

4546

257

258259

260261

262263

264265

266267

268269

270271

272273

Page 53: A GWT application using PHP and PostgreSQL

@Override public String getValue(MyItem item) { return item.name; } }; TextColumn<MyItem> origDeptColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.origin_department; } }; DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override public Date getValue(MyItem item) { return item.created_at; } }; DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>(); dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) { list.add(itm); } makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update"); makeSortableTextColumn(table,origDeptColumn,list,"orig_dept"); table.addColumn(nameColumn,"Name"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); addItemButton.addClickHandler(this); mainPanel.add(addItemButton); mainPanel.add(table); } // end showUserItems() private JsArray<User> getData(String json) { return JsonUtils.safeEval(json);

53

274

275276

277278

279280

281282

283284

285286

287288

289290

291292

293294

295296

297298

299300

301302

303304

305306

307308

309310

311312

313314

315316

317318

319320

321322

323324

325326

327328

329330

331

Page 54: A GWT application using PHP and PostgreSQL

}// Lines 333 on are unmodified

So far, only three lines are new. These are line 46 and lines 325-326. Line 46 just adds an instance variable addItemButton that will be the button that is clicked on to add a new item.

Line 325 just makes it so that this application to respond to the ClickEvent that is generated when the addItemButton is clicked on. Line 326 adds this button right above the CellTable.

After verifying that the button shows up, modify “DemoProj3.java” to check to see if the application responds to clicking on that button:

public void onClick(ClickEvent e) { Object source = e.getSource(); if (source == loginButton) { String encData = URL.encode("username") + "=" + URL.encode(usernameBox.getText()) + "&"; encData += URL.encode("password") + "=" + URL.encode(passBox.getText()); String url = baseURL + "login.php"; postRequest(url,encData,"login"); } else if (source == addItemButton) { Window.alert("addItem Button clicked"); } }

The new lines, shown shaded, just show an Alert box when the button is clicked. Next, let's put up some text boxes and a button that will be used to insert a new item into the database.

First, let's modify “DemoProj3.java” so that it has some new instance variables to perform this task:

package test.client;

import com.google.gwt.core.client.EntryPoint;import com.google.gwt.http.client.Request;import com.google.gwt.http.client.RequestBuilder;import com.google.gwt.http.client.RequestCallback;import com.google.gwt.http.client.Response;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.ui.VerticalPanel;import com.google.gwt.event.dom.client.ClickEvent;import com.google.gwt.event.dom.client.ClickHandler;import com.google.gwt.user.client.ui.HorizontalPanel;import com.google.gwt.user.client.ui.Label;import com.google.gwt.user.client.ui.Button;import com.google.gwt.user.client.ui.TextBox;import com.google.gwt.user.client.ui.PasswordTextBox;import com.google.gwt.http.client.URL;

54

332

101

102103

104105

106107

108109

110111

112113

114115

1

23

45

67

89

1011

1213

1415

1617

Page 55: A GWT application using PHP and PostgreSQL

import com.google.gwt.user.client.ui.RootPanel;import com.google.gwt.core.client.JsonUtils;import java.util.ArrayList;import com.google.gwt.core.client.JsArray;import com.google.gwt.user.cellview.client.CellTable;import com.google.gwt.user.cellview.client.TextColumn;import java.util.Date;import com.google.gwt.cell.client.DateCell;import com.google.gwt.user.cellview.client.Column;import java.util.Comparator;import java.util.List;import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;import com.google.gwt.view.client.ListDataProvider;import com.google.gwt.user.client.ui.TextArea;import com.google.gwt.user.client.ui.CheckBox;import com.google.gwt.user.client.ui.HTML;

public class DemoProj3 implements EntryPoint, ClickHandler{ VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); ArrayList<MyItem> items = new ArrayList<MyItem>(); JsArray<User> jsonData; JsArray<Item> jsonItemData; VerticalPanel loginPanel = new VerticalPanel(); TextBox usernameBox; PasswordTextBox passBox; Button loginButton = new Button("Login"); int user_id; Button addItemButton = new Button("Add new item"); TextArea descBox; TextArea justBox; CheckBox deptA_cbox; CheckBox deptB_cbox; CheckBox deptC_cbox; CheckBox deptD_cbox; Button submitItemButton = new Button("Submit item"); private static class MyUser // lines below unmodified so far

The new lines are shown shaded. The new lines are lines 31-33 and lines 50-56. Line 31 just imports the TextArea class. We will use this class to enter long text data like the description and justification for an item. Line 32 imports the CheckBox class. This class can be used to select additional users that can edit the item. Line 33 imports the HTML class. This class is used to add text that functions as headings. Lines 50-56 add in the instance variables that we will use to input the item information needed to insert a new item.

Now, let's add these text areas, check boxes and button to the mainPanel, after first clearing the mainPanel.

// Lines 1-110 unchanged

55

18

1920

2122

2324

2526

2728

2930

3132

33

343536

3738

3940

4142

4344

4546

4748

4950

5152

5354

5556

5758

Page 56: A GWT application using PHP and PostgreSQL

public void onClick(ClickEvent e) { Object source = e.getSource(); if (source == loginButton) { String encData = URL.encode("username") + "=" + URL.encode(usernameBox.getText()) + "&"; encData += URL.encode("password") + "=" + URL.encode(passBox.getText()); String url = baseURL + "login.php"; postRequest(url,encData,"login"); } else if (source == addItemButton) { mainPanel.clear(); showAddItemPanel(); } } public void onModuleLoad() {//// Lines 129-409 are unmodified// private void init() { Label usernameLabel = new Label("Username: "); usernameBox = new TextBox(); HorizontalPanel userRow = new HorizontalPanel(); userRow.add(usernameLabel); userRow.add(usernameBox); loginPanel.add(userRow); Label passLabel = new Label("Password"); passBox = new PasswordTextBox(); HorizontalPanel passRow = new HorizontalPanel(); passRow.add(passLabel); passRow.add(passBox); loginPanel.add(passRow); loginButton.addClickHandler(this); loginPanel.add(loginButton); mainPanel.add(loginPanel); } private void showAddItemPanel() { HTML heading = new HTML("<h2>Enter new item information:</h2>"); mainPanel.add(heading); Label descLabel = new Label("Description"); mainPanel.add(descLabel); descBox = new TextArea(); descBox.setSize("500", "100"); mainPanel.add(descBox); Label justLabel = new Label("Justification"); mainPanel.add(justLabel); justBox = new TextArea(); justBox.setSize("500","100"); mainPanel.add(justBox); HTML addUserHeading = new HTML("<h3>Check additional editors</h3>"); mainPanel.add(addUserHeading); deptA_cbox = new CheckBox("deptA"); deptB_cbox = new CheckBox("deptB");

56

111

112113

114115

116117

118119

120121

122123

124125

126127

128

410411

412413

414415

416417

418419

420421

422423

424425

426427

428429

430431

432433

434435

436437

438439

440441

442443

444445

446

Page 57: A GWT application using PHP and PostgreSQL

deptC_cbox = new CheckBox("deptC"); deptD_cbox = new CheckBox("deptD"); HorizontalPanel checkboxRow = new HorizontalPanel(); checkboxRow.add(deptA_cbox); checkboxRow.add(deptB_cbox); checkboxRow.add(deptC_cbox); checkboxRow.add(deptD_cbox); mainPanel.add(checkboxRow); submitItemButton.addClickHandler(this); mainPanel.add(submitItemButton); }}

The new lines are shown shaded. They are lines 123-124 and lines 428-457. Line 123 just clears the mainPanel, and line 124 calls the showAddItemPanel() method that is defined on lines 428-457.

Lines 427-457 define the showAddItemPanel() method that displays the input widgets that are used to collect information for adding a new item. Note on lines 434 and 435 how the TextArea object for the item description is created and then sized. A similar thing is done for the item justification TextArea on lines 439 and 440. Lines 430 and 442-443 are used to put some HTML into this panel. HTML objects are used because they are easier to format than Label objects. Lines 445-448 construct the CheckBox objects. Note how the constructor is passed the String that serves as the CheckBox label. On line 455 this application is set as the handler for ClickEvents generated by click on the submitItemButton.

Posting the data to a PHP script that performs the insert

The next thing we have to do is collect the input data and call the postRequest() method to send the data to a PHP script. Let's start by setting up to call the postRequest() method. Here are the changes to “DemoProj3.java”:// LInes 1-110 are unmodified111 public void onClick(ClickEvent e) { Object source = e.getSource(); if (source == loginButton) { String encData = URL.encode("username") + "=" + URL.encode(usernameBox.getText()) + "&"; encData += URL.encode("password") + "=" + URL.encode(passBox.getText()); String url = baseURL + "login.php"; postRequest(url,encData,"login"); } else if (source == addItemButton) { mainPanel.clear(); showAddItemPanel(); } else if (source == submitItemButton) { String encData = URL.encode("description") + "=" + URL.encode(descBox.getText()) + "&"; encData += URL.encode("justification") + "=" +

57

447

448449

450451

452453

454455

456457

458

111

112113

114115

116117

118119

120121

122123

124125

126127

128129

Page 58: A GWT application using PHP and PostgreSQL

URL.encode(justBox.getText()) + "&"; String checkBoxString = ""; int numBoxesChecked = 0; if (deptA_cbox.getValue()) { numBoxesChecked++; } if (deptB_cbox.getValue()) { numBoxesChecked++; } if (deptC_cbox.getValue()) { numBoxesChecked++; } if (deptD_cbox.getValue()) { numBoxesChecked++; } if (deptA_cbox.getValue()) { checkBoxString += "deptA"; numBoxesChecked--; if (numBoxesChecked > 0) { checkBoxString += ","; } } if (deptB_cbox.getValue()) { checkBoxString += "deptB"; numBoxesChecked--; if (numBoxesChecked > 0) { checkBoxString += ","; } } if (deptC_cbox.getValue()) { checkBoxString += "deptC"; numBoxesChecked--; if (numBoxesChecked > 0) { checkBoxString += ","; } } if (deptD_cbox.getValue()) { checkBoxString += "deptD"; numBoxesChecked--; if (numBoxesChecked > 0) { checkBoxString += ","; } } encData += URL.encode("otherDepts") + "=" + URL.encode(checkBoxString) + "&"; encData += URL.encode("uid") + "=" + URL.encode("" + user_id); String url = baseURL + "createItem.php"; postRequest(url, encData, "createItem"); } // else if source == addItemButton ends } public void onModuleLoad() {//// Lines 182-210 are unmodified// private void postRequest(String url, String data, final String requestName)

58

130

131132

133134

135136

137138

139140

141142

143144

145146

147148

149150

151152

153154

155156

157158

159160

161162

163164

165166

167168

169170

171172

173174

175176

177178

179180

181182

211

212

Page 59: A GWT application using PHP and PostgreSQL

{ final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,url); rb.setHeader("Content-type", "application/x-www-form-urlencoded"); try { rb.sendRequest(data, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("login")) { String resp = response.getText().trim(); user_id = Integer.parseInt(resp); if (user_id < 1) { usernameBox.setText(""); passBox.setText(""); } else if (user_id == 1) { mainPanel.clear(); String url = baseURL + "showUsers.php"; getRequest(url,"getUsers"); } else { mainPanel.clear(); String encData = URL.encode("uid") + "=" + URL.encode("" + user_id); String url = baseURL + "showUserItems.php"; postRequest(url,encData,"getUserItems"); } } // ends if (requestName.equals("login")) else if (requestName.equals("getUserItems")) { showUserItems(response.getText()); } // ends else if requestName is "getUserItems" else if (requestName.equals("createItem")) { showUserItems(response.getText()); } // ends elseif requestName is "createItem" } // onResponseReceived() ends }); // new RequestCallback ends } catch (final Exception e) { Window.alert(e.getMessage()); } } // postRequest() ends private void showUsers(String responseText) {// Lines 264 on are unmodified

Now, let's create the PHP script, “createItem.php” to see if we are passing the correct data. At this early stage, just place this into “createItem.php”:

<?php

59

213

214215

216217

218219

220221

222223

224225

226227

228229

230231

232233

234235

236237

238239

240241

242243

244245

246247

248249

250251

252253

254255

256257

258259

260261

262263

Page 60: A GWT application using PHP and PostgreSQL

?>

Run the GWT application, but make sure that FireBug is activated. You will see an error but you should be able to see the “Post” values. Check to see that what you were trying to submit is actually being submitted:

Note how the Post values match what was submitted in the window above. So, things are working from the GWT side.

Now, let's work on the PHP side. We can start by hardcoding the values in the $_POST array. Then, we can take a look at how hard it would be to get the user ids for the additional users that are allowed to edit the item. Here is the file “createItem.php”:

<?php require_once('demo_proj3.php');

60

1

2

Page 61: A GWT application using PHP and PostgreSQL

$pdo = connect(); if (!$pdo) { die("Could not connect\n"); } $_POST["description"] = "test description 1"; $_POST["justification"] = "test justification 1"; $_POST["otherDepts"] = "deptA,deptC"; $_POST["uid"] = 3; if (count($_POST) > 0) { $description = $_POST["description"]; $justification = $_POST["justification"]; $otherDepts = $_POST["otherDepts"]; $uid = $_POST["uid"]; $deptArray = explode(",",$otherDepts); $query = "select id from users where department=:dept"; $statement = $pdo->prepare($query); for ($i = 0; $i < count($deptArray); $i++) { echo $deptArray[$i] . "<br />\n"; $statement->execute(array(':dept' => $deptArray[$i])); $row = $statement->fetch(PDO::FETCH_ASSOC); echo "id: " . $row["id"] . "<br />\n"; } }?>

Lines 7-10 hardcode the data to simulate the actual POST event. Line 16 break the $otherDepts string into an array of strings using a comma as the delimiter. Lines 17-24 will obtain the user id for each of the departments. Line 17 sets up the select query to obtain the id. Line 18 prepares this statement for execution. The for loop defined on lines 19-24, will show the department, then run the query for that department. Then, the returned id will be printed. Running this script from the command line shows:

php -f createItem.php

deptA<br />id: 3<br />deptC<br />id: 6<br />

This returns the correct id numbers. This is a good way to solve the problem if we were always going to use PHP. The prepare/execute method prevents SQL injection attacks, and is very efficient when you are calling multiple executes for the same prepared statement. However, since we are assuming that we might not always use PHP, let's see if we can write a plpgsql function that returns a string that contains the id numbers. To do this, let's add to itemFuncs.sql:

// Lines 1-31 are unmodified create or replace function addItem(_uid integer, _desc text, _just text, _others text) returns boolean as $func$

61

3

45

67

89

1011

1213

1415

1617

1819

2021

2223

2425

26

3233

3435

Page 62: A GWT application using PHP and PostgreSQL

declare rec record; rec2 record; div text; chair_id integer; it_id integer; begin select into rec * from users where id = _uid; if not found then return 'f'; else insert into items (description,justification, origin_department) values (_desc, _just, rec.department); it_id := currval('item_id_seq'); div := rec.division; select into rec2 * from users where department=div; chair_id := rec2.id; perform addItemUser(_uid, it_id); if rec.id <> chair_id then perform addItemUser(chair_id, it_id); end if; if _others <> '' then perform addOtherEditors(_others,it_id); end if; return 't'; end if; end; $func$ language 'plpgsql'; create or replace function makeIDString(_deptString text) returns text as $func$ declare myarray text[]; rec record; _idstring text; _count integer; _idtext text; begin _idstring := ''; myarray := string_to_array(_deptString,','); _count = array_length(myarray,1); for i in 1.._count loop select into rec * from users where department=myarray[i]; if not found then return "error department not found"; else _idtext := trim(to_char(rec.id,'9d')); _idstring := _idstring||_idtext; if i < _count then _idstring := _idstring||','; end if; end if; end loop; return _idstring; end;

62

36

3738

3940

4142

4344

4546

4748

4950

5152

5354

5556

5758

5960

6162

6364

6566

6768

6970

7172

7374

7576

7778

7980

8182

8384

8586

8788

8990

9192

93

Page 63: A GWT application using PHP and PostgreSQL

$func$ language 'plpgsql';

The new lines are shown shaded. They are lines 67-95. Those lines define the makeIDString() function. The purpose of this function is to return a string that has theuser id numbers for all the other users allowed to edit the item. In the returned string, the id numbers are separated by commas.

Line 77 just sets the string _idstring to an empty string. Line 78 splits the passed string using a comma, and stores the resulting array of strings in myarray. Line 79 just stores the size of the array in _count. Lines 80-91 define a for loop that iterates over the elements in myarray. On line 81 we select the record from the users table that has a department that matches. If no match is found, the if statement on lines 82-83 returns an error message. Otherwise, the id number from that record is converted into a string with the whitespace removed on line 85. Line 86 appends the converted and trimmed id on to the end of _idstring. Lines 87-88 add a comma in between the converted id. Finally, on line 92, the string of id numbers is returned. We can test this new function in the following way:

select makeIDString('deptA,deptC,deptD'); makeidstring -------------- 3,6,7(1 row)

Now, let's go back to the PHP script, “createItem.php” and make use of this newly created function.

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect\n"); } $_POST["description"] = "test description 1"; $_POST["justification"] = "test justification 1"; $_POST["otherDepts"] = "deptA,deptC"; $_POST["uid"] = 3; if (count($_POST) > 0) { $description = $_POST["description"]; $justification = $_POST["justification"]; $otherDepts = $_POST["otherDepts"]; $uid = $_POST["uid"]; $query = "select makeIDString(:other)"; $statement = $pdo->prepare($query); $statement->execute(array(':other' => $otherDepts)); $row = $statement->fetch(PDO::FETCH_ASSOC); $idString = $row["makeidstring"]; echo $idString; echo "\n"; }

63

94

95

1

23

45

67

89

1011

1213

1415

1617

1819

2021

2223

Page 64: A GWT application using PHP and PostgreSQL

?>

The new lines are shown shaded. They are lines 16-22. Line 16 creates a SQL select query statement that will call the makeIDString() function. Line 17 prepares that query and line 18 executes that query. Lines 19-20 obtain the id string. Line 21 just echoes this id string to the screen. Here is the run from the command line:

php -f createItem.php3,6

This is the correct string for the hardcoded value on line 9. Now that this works, we can add to “createItem.php” so that it can do insert an item. Recall that the function addItem() is called in this way:select addItem(2,'desc1','just1','3,6')

where 2 is the submitting user's id, 'desc1' is the item description, 'just1' is the item justification, and '3,6' is the string with the additional user id numbers allowed to edit this item. Here is the new version of “createItem.php”:

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect\n"); } $_POST["description"] = "test description 1"; $_POST["justification"] = "test justification 1"; $_POST["otherDepts"] = "deptA,deptC"; $_POST["uid"] = 3; if (count($_POST) > 0) { $description = $_POST["description"]; $justification = $_POST["justification"]; $otherDepts = $_POST["otherDepts"]; $uid = $_POST["uid"]; $query = "select makeIDString(:other)"; $statement = $pdo->prepare($query); $statement->execute(array(':other' => $otherDepts)); $row = $statement->fetch(PDO::FETCH_ASSOC); $idString = $row["makeidstring"]; $insertQuery = "select addItem(:id,:desc,:just,:idstring)"; $statement = $pdo->prepare($insertQuery); $statement->execute(array(':id' => $uid, ':desc' => $description, ':just' => $justification, ':idstring' => $idString)); }?>

While running this from the command line, it seems that it did not insert any new records. The problem is that the hardcoded data is not really correct. It turns out that in the original data, the user with id=3, is also from 'deptA'. So, trying to insert the additional

64

24

12

34

56

78

910

1112

1314

1516

1718

1920

2122

2324

2526

27

Page 65: A GWT application using PHP and PostgreSQL

editors resulted in a violation for the items_users table. The items_users table has a constraint that the combination of user_id, item_id must be unique. This generates an error that prevents any of the insertions to take place.

This shows how important it is to place such constraints on the table, as this prevents duplicate data from being entered. It is easy to see that the user of the GWT application could make this mistake. They might mistakenly choose their own department when setting additional editors. This should be stopped at the database level, because at the GWT level an additional query has to be made.

Since all insertions are prevented when the uniqueness violation occurs, even the items table is not updated. What this means, is that we have to go back and fix the addItemUser() function in the “itemFuncs.sql” script. Here is the new version of “itemFuncs.sql”:

-- itemFuncs.sql

create or replace function addItemUser(_uid integer, _id integer) returns boolean as $func$ declare rec record; begin select into rec * from items_users where user_id=_uid and item_id=_id; if not found then insert into items_users (user_id, item_id) values (_uid, _id); else return 'f'; end if; return 't'; end; $func$ language 'plpgsql';

create or replace function addOtherEditors(_others text, _id integer)// lines 24 on are unmodified

The modified lines are shown shaded. These lines are lines 6-7 and lines 9-16. Lines 6-7 declare a local variable for holding a database table record. Lines 9-10 attempt to return a record where the user_id/item_id pair, have already been entered. If the combination of user_id/item_id don't exist, then the user's id and item's id will be inserted into the items_users table on lines 12-13. If the combination exists, then the function returns False on line 15.

These changes make it so that if someone makes a mistake at the GWT application level and adds their own department to the list of additional editors, only the insertion that would have resulted in a duplicate record would be prevented. All the other insertions into the

65

1

23

45

67

89

1011

1213

1415

1617

1819

20

212223

Page 66: A GWT application using PHP and PostgreSQL

items_users table would take place.

To make it easier to reset the database with data that is useful to start with, the “itemData.sql” script has been changed to this:

-- itemData.sqlinsert into items (description, justification, origin_department, created_at, updated_at) values ('item1', 'justification1','div1',timestamp '2012-01-01 9:30:22.1135', timestamp '2012-01-03');insert into items_users values(2,currval('item_id_seq'));insert into items_users values(3,currval('item_id_seq'));insert into items_users values(5,currval('item_id_seq'));insert into items (description, justification, origin_department, created_at, updated_at) values ('item2', 'justification2','deptA',timestamp '2012-01-02 10:30:22.1135', timestamp '2012-01-04');insert into items_users values(3,currval('item_id_seq'));insert into items_users values(2,currval('item_id_seq'));insert into items_users values(5,currval('item_id_seq'));insert into items (description, justification, origin_department, created_at, updated_at) values ('item3', 'justification3','deptA',timestamp '2012-01-03 10:30:22.1135', timestamp '2012-01-04');insert into items_users values(3,currval('item_id_seq'));insert into items_users values(2,currval('item_id_seq'));insert into items (description, justification, origin_department, created_at, updated_at) values ('item4', 'justification4','deptB',timestamp '2012-01-04 10:30:22.1135', timestamp '2012-01-05');insert into items_users values(4,currval('item_id_seq'));insert into items_users values(2,currval('item_id_seq'));insert into items_users values(3,currval('item_id_seq'));insert into items (description, justification, origin_department, created_at, updated_at) values ('item5', 'justification5','deptC',timestamp '2012-01-05 10:30:22.1135', timestamp '2012-01-06');insert into items_users values(6,currval('item_id_seq'));insert into items_users values(5,currval('item_id_seq'));insert into items_users values(3,currval('item_id_seq'));insert into items (description, justification, origin_department, created_at, updated_at) values ('item6', 'justification6','deptC',timestamp '2012-01-05 10:30:22.1135', timestamp '2012-01-07');insert into items_users values(6,currval('item_id_seq'));insert into items_users values(5,currval('item_id_seq'));insert into items_users values(4,currval('item_id_seq'));insert into items_users values(7,currval('item_id_seq'));insert into items (description, justification, origin_department, created_at, updated_at) values ('item7', 'justification7','deptD',timestamp '2012-01-05 10:30:22.1135', timestamp '2012-01-07');insert into items_users values(7,currval('item_id_seq'));insert into items_users values(5,currval('item_id_seq'));insert into items_users values(3,currval('item_id_seq'));insert into items_users values(6,currval('item_id_seq'));/*

66

12

34

56

78

910

1112

1314

1516

1718

1920

2122

2324

2526

2728

2930

3132

3334

3536

3738

3940

4142

4344

4546

4748

4950

5152

Page 67: A GWT application using PHP and PostgreSQL

select addItem(2,'item1','justification1','3,5');select addItem(3,'item2','justification2','5');select addItem(3,'item3','justification3','');select addItem(4,'item4','justification4','3');select addItem(6,'item5','justification5','3');select addItem(6,'item6','justification6','4,7');select addItem(7,'item7','justification7','3,6');*/

The insertions that go from line 2 to line 51 basically replace the old insertions that go from line 53-59. This shows you how useful the addItem() function is. But, by doing things this way, we can insert more interesting dates for the created_at and updated_at fields.

Now we can go back and modify “createItem.php”, and try to run the GWT application to insert a new item.

Here is the new version of “createItem.php”:

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect\n"); } //$_POST["description"] = "test description 1"; //$_POST["justification"] = "test justification 1"; //$_POST["otherDepts"] = "deptA,deptC"; //$_POST["uid"] = 3; if (count($_POST) > 0) { $description = $_POST["description"]; $justification = $_POST["justification"]; $otherDepts = $_POST["otherDepts"]; $uid = $_POST["uid"]; $query = "select makeIDString(:other)"; $statement = $pdo->prepare($query); $statement->execute(array(':other' => $otherDepts)); $row = $statement->fetch(PDO::FETCH_ASSOC); $idString = $row["makeidstring"]; $insertQuery = "select addItem(:id,:desc,:just,:idstring)"; $statement = $pdo->prepare($insertQuery); $statement->execute(array(':id' => $uid, ':desc' => $description, ':just' => $justification, ':idstring' => $idString)); }?>

All that has been changed is that the hardcoded data has been commented out. Here are the changes to make to “DemoProj3.java”:

// Lines 1-210 are unmodified private void postRequest(String url, String data, final String requestName) { final RequestBuilder rb =

67

53

5455

5657

5859

60

1

23

45

67

89

1011

1213

1415

1617

1819

2021

2223

2425

2627

211

212213

214

Page 68: A GWT application using PHP and PostgreSQL

new RequestBuilder(RequestBuilder.POST,url); rb.setHeader("Content-type", "application/x-www-form-urlencoded"); try { rb.sendRequest(data, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("login")) { String resp = response.getText().trim(); user_id = Integer.parseInt(resp); if (user_id < 1) { usernameBox.setText(""); passBox.setText(""); } else if (user_id == 1) { mainPanel.clear(); String url = baseURL + "showUsers.php"; getRequest(url,"getUsers"); } else { mainPanel.clear(); String encData = URL.encode("uid") + "=" + URL.encode("" + user_id); String url = baseURL + "showUserItems.php"; postRequest(url,encData,"getUserItems"); } } // ends if (requestName.equals("login")) else if (requestName.equals("getUserItems")) { showUserItems(response.getText()); } // ends else if requestName is "getUserItems" else if (requestName.equals("createItem")) { mainPanel.clear(); redrawUserItems(); } // ends elseif requestName is "createItem" } // onResponseReceived() ends }); // new RequestCallback ends } catch (final Exception e) { Window.alert(e.getMessage()); } } // postRequest() ends private void showUsers(String responseText) {//// Lines 265-328 are unmodified// private void showUserItems(String responseText) { mainPanel.clear(); jsonItemData = getItemData(responseText); items = new ArrayList<MyItem>();

68

215

216217

218219

220221

222223

224225

226227

228229

230231

232233

234235

236237

238239

240241

242243

244245

246247

248249

250251

252253

254255

256257

258259

260261

262263

264

329330

331332

333

Page 69: A GWT application using PHP and PostgreSQL

Item item = null; for (int i = 0; i < jsonItemData.length(); i++) { item = jsonItemData.get(i); items.add(new MyItem(item.getUserID(), item.getName(), item.getUsername(), item.getDivision(),item.getDepartment(), item.getItemID(),item.getDescription(), item.getJustification(),item.getOriginalDeparment(), item.getCreatedAt(),item.getUpdatedAt())); } CellTable<MyItem> table = new CellTable<MyItem>(); TextColumn<MyItem> nameColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.name; } }; TextColumn<MyItem> origDeptColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.origin_department; } }; DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override public Date getValue(MyItem item) { return item.created_at; } }; DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>(); dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) { list.add(itm); } makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update");

69

334

335336

337338

339340

341342

343344

345346

347348

349350

351352

353354

355356

357358

359360

361362

363364

365366

367368

369370

371372

373374

375376

377378

379380

381382

383384

385386

387388

389390

391

Page 70: A GWT application using PHP and PostgreSQL

makeSortableTextColumn(table,origDeptColumn,list,"orig_dept"); table.addColumn(nameColumn,"Name"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); addItemButton.addClickHandler(this); mainPanel.add(addItemButton); mainPanel.add(table); } // end showUserItems() private void redrawUserItems() { String encData = URL.encode("uid") + "=" + URL.encode("" + user_id); String url = baseURL + "showUserItems.php"; postRequest(url, encData,"getUserItems"); } private JsArray<User> getData(String json)411 {// Lines 412 on are unmodified

The new lines are shown shaded. They are lines 253-254, line 331, line 333 and lines 403-409. Lines 253-254 change what is done when the “createItem” postRequest is called. Clearing the mainPanel whenever you need to redraw a table is always a good idea. On line 254 we call the method redrawUserItems(), rather than just try to call a postRequest() directly. Calling a postRequest() from with the postRequest() would cause an unwanted recursion. The redrawUserItems()method is defined on lines 403-409.

Line 331 and line 333 are used to clear the mainPanel and clear the ArrayList<MyItem>, respectively, before each time the items CellTable is redrawn.

Lines 403-409 define the redrawUserItems() method. This method just repeats the same instructions as when a nonadministrative user logs in successfully. The key thing is to take the postRequest() call outside of the postRequest() method to avoid recursive calls.

At this point, the CellTable for displaying a user's items only had a few columns just to test things out. Let's add the description, justification, and iid (item id) columns. Here are the changes to “DemoProj3.java”:

package test.client;

import com.google.gwt.core.client.EntryPoint;import com.google.gwt.http.client.Request;import com.google.gwt.http.client.RequestBuilder;import com.google.gwt.http.client.RequestCallback;import com.google.gwt.http.client.Response;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.ui.VerticalPanel;import com.google.gwt.event.dom.client.ClickEvent;

70

392

393394

395396

397398

399400

401402

403404

405406

407408

409410

411

1

23

45

67

89

10

Page 71: A GWT application using PHP and PostgreSQL

import com.google.gwt.event.dom.client.ClickHandler;import com.google.gwt.user.client.ui.HorizontalPanel;import com.google.gwt.user.client.ui.Label;import com.google.gwt.user.client.ui.Button;import com.google.gwt.user.client.ui.TextBox;import com.google.gwt.user.client.ui.PasswordTextBox;import com.google.gwt.http.client.URL;import com.google.gwt.user.client.ui.RootPanel;import com.google.gwt.core.client.JsonUtils;import java.util.ArrayList;import com.google.gwt.core.client.JsArray;import com.google.gwt.user.cellview.client.CellTable;import com.google.gwt.user.cellview.client.TextColumn;import java.util.Date;import com.google.gwt.cell.client.DateCell;import com.google.gwt.user.cellview.client.Column;import java.util.Comparator;import java.util.List;import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;import com.google.gwt.view.client.ListDataProvider;import com.google.gwt.user.client.ui.TextArea;import com.google.gwt.user.client.ui.CheckBox;import com.google.gwt.user.client.ui.HTML;import com.google.gwt.cell.client.NumberCell;

public class DemoProj3 implements EntryPoint, ClickHandler{//// Lines 38-329 unmodified// private void showUserItems(String responseText) { mainPanel.clear(); jsonItemData = getItemData(responseText); items = new ArrayList<MyItem>(); Item item = null; for (int i = 0; i < jsonItemData.length(); i++) { item = jsonItemData.get(i); items.add(new MyItem(item.getUserID(), item.getName(), item.getUsername(), item.getDivision(),item.getDepartment(), item.getItemID(),item.getDescription(), item.getJustification(),item.getOriginalDeparment(), item.getCreatedAt(),item.getUpdatedAt())); } CellTable<MyItem> table = new CellTable<MyItem>(); TextColumn<MyItem> nameColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.name; } }; TextColumn<MyItem> origDeptColumn = new TextColumn<MyItem>() {

71

11

1213

1415

1617

1819

2021

2223

2425

2627

2829

3031

3233

34

353637

330

331332

333334

335336

337338

339340

341342

343344

345346

347348

349350

351352

353354

355356

357

Page 72: A GWT application using PHP and PostgreSQL

@Override public String getValue(MyItem item) { return item.origin_department; } }; TextColumn<MyItem> descColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.description; } }; TextColumn<MyItem> justColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.justification; } }; NumberCell numCell = new NumberCell(); Column<MyItem, Number> iidColumn = new Column<MyItem, Number>(numCell) { @Override public Integer getValue(MyItem item) { return item.iid; } }; DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override public Date getValue(MyItem item) { return item.created_at; } }; DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>(); dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) {

72

358

359360

361362

363364

365366

367368

369370

371372

373374

375376

377378

379380

381382

383384

385386

387388

389390

391392

393394

395396

397398

399400

401402

403404

405406

407408

409410

411412

413414

415

Page 73: A GWT application using PHP and PostgreSQL

list.add(itm); } makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update"); makeSortableTextColumn(table,origDeptColumn,list,"orig_dept"); table.addColumn(nameColumn,"Name"); table.addColumn(iidColumn,"Item id"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(descColumn,"Description"); table.addColumn(justColumn,"Justification"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); addItemButton.addClickHandler(this); mainPanel.add(addItemButton); mainPanel.add(table); } // end showUserItems() private void redrawUserItems() {// Lines 436 on are unmodified

The new lines are shown shaded. They are line 34, lines 364-391, line 422 and lines 424-425. Line 34 just adds the import statement for the NumberCell class. This is the class that we need to help construct a CellTable column that contains a number.

Lines 364-391 define additional columns for the CellTable. Lines 364-372 define a column that the description data can be inserted into. Since this is a TextColumn, this is similar to the other columns containing Strings. Lines 373-381 define a column that the justification data can be inserted into. This, like the column for description data is just a TextColumn.

Lines 382-391 define a column for the item id. Since the id is a number, we use a column that is composed of NumberCell objects. On line 382, we construct a NumberCell object. On line 383 we start the construction of a column that has a row type of MyItem and a column type of Number (java.lang.Number). Lines 384 complete the construction of this column.

Lines 422, 424 and 425 just add these columns to the CellTable. Note that the order these are added to the table is the order the columns are displayed in for the CellTable. Here is a view of the CellTable. See Figure 1 on page 74.

73

416

417418

419420

421422

423424

425426

427428

429430

431432

433434

435

Page 74: A GWT application using PHP and PostgreSQL

Now, let's add the capability to sort by the item id. Here are the changes to “DemoProj3.java” that can do that:

// Lines 1-410 are unmodified ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>(); dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) { list.add(itm); } makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update"); makeSortableTextColumn(table,origDeptColumn,list,"orig_dept"); makeSortableNumberColumn(table,iidColumn,list,"iid"); table.addColumn(nameColumn,"Name"); table.addColumn(iidColumn,"Item id"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(descColumn,"Description"); table.addColumn(justColumn,"Justification"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); addItemButton.addClickHandler(this); mainPanel.add(addItemButton); mainPanel.add(table); } // end showUserItems() private void redrawUserItems() {

//// Lines 437-480 are unmodified//481 private void makeSortableTextColumn(CellTable<MyItem> table, TextColumn<MyItem> col, List<MyItem> list,

74

Figure 1: Partial screenshot showing user's items sorted by modified date in descending order.

411412

413414

415416

417418

419420

421422

423424

425426

427428

429430

431432

433434

435436

437

481482

Page 75: A GWT application using PHP and PostgreSQL

final String type) { col.setSortable(true); ListHandler<MyItem> handler = new ListHandler<DemoProj3.MyItem>(list); handler.setComparator(col, new Comparator<DemoProj3.MyItem>() { @Override public int compare(DemoProj3.MyItem i1, DemoProj3.MyItem i2) { if (i1 == i2) { return 0; } if (i1 != null) { if (type.equals("orig_dept")) { return (i2 != null) ? i1.origin_department.compareTo( i2.origin_department) : 1; } } return -1; } }); table.addColumnSortHandler(handler); } private void makeSortableNumberColumn(CellTable<MyItem> table, Column<MyItem,Number> col, List<MyItem> list, final String type) { col.setSortable(true); ListHandler<MyItem> handler = new ListHandler<DemoProj3.MyItem>(list); handler.setComparator(col, new Comparator<DemoProj3.MyItem>() { @Override public int compare(DemoProj3.MyItem i1, DemoProj3.MyItem i2) { if (i1 == i2) { return 0; } if (i1 != null) { if (type.equals("iid")) { return i1.iid - i2.iid; } } return -1; } }); table.addColumnSortHandler(handler); } private void init()536 {// Lines 537 on are unmodified

The new lines are shown shaded. They are line421 and lines 509-534. LIne 421 calls the

75

483

484485

486487

488489

490491

492493

494495

496497

498499

500501

502503

504505

506507

508509

510511

512513

514515

516517

518519

520521

522523

524525

526527

528529

530531

532533

534535

536

Page 76: A GWT application using PHP and PostgreSQL

makeSortableNumberColumn() method. This method is used to make a numeric column sortable and is defined on lines 509-534.

On line 513, we set the column to be sortable, although this by itself does not make the column sortable until we create a ListHandler that defines the way to perform the sorting. Lines 514-515 attach the handler to the List<MyItem>. Lines 517-532 define the compare() method that will be used to sort the column. Line 527 holds the main part of the compare() method. It simply returns the difference between the iid values for the two items being compared. Line 533 adds the sort handler to the table.

Now, our CellTable of items can be sorted by the item id number.

Editing an existing item

It is usually nice to be able to edit an existing item. Someone may want to change a description or add to the justification for an item. Someone may want to change which additional users are allowed to edit an item. To edit an item, you need to have a way of selecting that item first.

One way to handle selection of an item is to make use of a SelectionModel. Since we want to only edit one item at a time we will make use of the com.google.gwt.view.client.SingleSelectionModel class. We can start by implementing the SingleSelectionModel, and just having a Window.alert() display the item id for the row that is clicked on. The changes to “DemoProj3.java” to do this are shown next:

// Lines 1-31 are unmodifiedimport com.google.gwt.user.client.ui.CheckBox;import com.google.gwt.user.client.ui.HTML;import com.google.gwt.cell.client.NumberCell;import com.google.gwt.view.client.SelectionChangeEvent;import com.google.gwt.view.client.SingleSelectionModel;

public class DemoProj3 implements EntryPoint, ClickHandler{//// Lines 40-331// private void showUserItems(String responseText) { mainPanel.clear(); jsonItemData = getItemData(responseText); items = new ArrayList<MyItem>(); Item item = null; for (int i = 0; i < jsonItemData.length(); i++) { item = jsonItemData.get(i); items.add(new MyItem(item.getUserID(), item.getName(), item.getUsername(), item.getDivision(),item.getDepartment(), item.getItemID(),item.getDescription(), item.getJustification(),item.getOriginalDeparment(),

76

3233

3435

36

373839

332

333334

335336

337338

339340

341342

343344

Page 77: A GWT application using PHP and PostgreSQL

item.getCreatedAt(),item.getUpdatedAt())); } CellTable<MyItem> table = new CellTable<MyItem>(); TextColumn<MyItem> nameColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.name; } }; TextColumn<MyItem> origDeptColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.origin_department; } }; TextColumn<MyItem> descColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.description; } }; TextColumn<MyItem> justColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.justification; } }; NumberCell numCell = new NumberCell(); Column<MyItem, Number> iidColumn = new Column<MyItem, Number>(numCell) { @Override public Integer getValue(MyItem item) { return item.iid; } }; DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override public Date getValue(MyItem item) { return item.created_at; } };

77

345

346347

348349

350351

352353

354355

356357

358359

360361

362363

364365

366367

368369

370371

372373

374375

376377

378379

380381

382383

384385

386387

388389

390391

392393

394395

396397

398399

400401

402

Page 78: A GWT application using PHP and PostgreSQL

DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>(); dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) { list.add(itm); } final SingleSelectionModel<MyItem> selectionModel = new SingleSelectionModel<MyItem>(); table.setSelectionModel(selectionModel); selectionModel.addSelectionChangeHandler( new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent e) { MyItem selected = selectionModel.getSelectedObject(); if (selected != null) { Window.alert("" +selected.iid); } } }); makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update");// Lines 436 on are unmodified

The new lines are shown shaded. They are lines 35-36 and lines 420-433. Line 35 imports the SelectionChangeEvent class. This class is the type of event that is sent to the GWT application when a selection change occurs. Line 36 imports the SingleSelectionModel class. This class is implemented because we only want to allow selection of a single row of the CellTable.

Lines 420-433 allow the SingleSelectionModel to be applied to the CellTable. Lines 420-421 construct a SingleSelectionModel<MyItem> called selectionModel. The SelectionModel should have the same type as the row type of the CellTable. Note that this object is declared as final. This is because any objects that will be used inside an inner class need to be declared as final. Lines 424-433 define the Handler() object. This is the inner class that requires the SingleSelectionModel<MyItem> object to be declared as final. At this point, all this handler does is when there is a selection change caused by the mouse clicking on a different row, the row, which is of type MyItem, gets selected on line 428. Then, the item id of that row is shown in a Window.alert().

78

403

404405

406407

408409

410411

412413

414415

416417

418419

420421

422423

424425

426427

428429

430431

432433

434435

Page 79: A GWT application using PHP and PostgreSQL

The next step would be to modify what is done in that handler so that we can display the field values of the item that needs to be edited. Here is the basic process that takes place:

1. The person running the program clicks on a table row to select it. This triggers the SelectionChangeEvent that is handled by the selection model's handler.

2. The selection model's handler responds by storing the item's id in the instance variable item_id.

3. If the user click on the “Edit item” button (that is added to the view of the items), a postRequest() is made to a PHP script that queries the database for the record from the item_view view that has that item id.

4. The PHP script returns that data, so that the postRequest()'s responseReceived() method can use the returned data to populate an input form that is similar to the input form for adding a new item.

5. When that input form is submitted, by clicking on a button, another postRequest() is made to a PHP script that updates the database.

6. Finally, the updated items for that user is redisplayed.

To carry this out, we need to do the following:

1. Add an instance variable of type int called item_id that can hold the item_id for the selected row. Create another instance variable of type Button called editItemButton with a label of “Edit item”. Create an instance variable of type Button called submitEditItemButton with a label of “Submit”.

2. In the showUserItems() method add editItemButton in a HorizontalPanel with the addItemButton.

3. In the selection model's handler, store the item id in item_id.

4. In the onClick() method, make the click handler of the editItemButton send a postRequest() to send the value of item_id.

5. Write a PHP script called “editItem.php” that gets the record from the item_view view that matches the item_id. The returned record is encoded as JSON to be read in the responseReceived() method of the calling postRequest().

6. In that responseReceived() that handles the record to be edited, call a method named “showEditItemPanel()” to display the item data. This panel will have include the submitEditItemButton that will be handled in the onClick() method by calling another postRequest() that sends the updated data to a PHP script called “updateItem.php”.

7. In the responseReceived() method of the postRequest() that updates the database, clear the main panel and call redrawUserItems() to redisplay the user's items.

79

Page 80: A GWT application using PHP and PostgreSQL

In reviewing the steps above, it seems like some steps could be saved if the the other editors was saved to the items table. The SQL scripts would have to be adjusted to accommodate this, but this would make it so that a MyItem object would contain all the information needed to fill in the input form for editing existing data.

Adjustments to the SQL scripts

Here is the change to “createTables.sql”:

// Lines 1-21 are unmodifieddrop table if exists items cascade;create table items( id integer not null default nextval('item_id_seq'), description text, justification text, origin_department text, created_at timestamp default now(), updated_at timestamp default now(), others text, num_low integer default 0, num_med integer default 0, num_high integer default 0, primary key (id));// Lines 36 on are unmodified

Line 30 is the only new line. It is added so we can keep track of the others string. For example, if deptB and deptD are the additional editors, the value of others would be 'deptB,deptD'.

-- itemFuncs.sql

create or replace function addItemUser(_uid integer, _id integer) returns boolean as $func$ declare rec record; begin select into rec * from items_users where user_id=_uid and item_id=_id; if not found then insert into items_users (user_id, item_id) values (_uid, _id); else return 'f'; end if; return 't'; end; $func$ language 'plpgsql';

create or replace function addOtherEditors(_others text, _id integer)

80

22

2324

2526

2728

2930

3132

3334

35

1

234

56

78

910

1112

1314

1516

1718

1920

2122

23

Page 81: A GWT application using PHP and PostgreSQL

returns boolean as $func$ declare v_id integer; my_array text[]; begin my_array := string_to_array(_others,','); for i in 1..array_length(my_array,1) loop v_id := cast (my_array[i] as integer); perform addItemUser(v_id,_id); end loop; return 't'; end; $func$ language 'plpgsql';

// Lines 1-39 unmodifiedcreate or replace function addItem(_uid integer, _desc text, _just text, _others text) returns boolean as $func$ declare rec record; rec2 record; div text; chair_id integer; it_id integer; begin select into rec * from users where id = _uid; if not found then return 'f'; else insert into items (description,justification, origin_department,others) values (_desc, _just, rec.department,_others); it_id := currval('item_id_seq'); div := rec.division; select into rec2 * from users where department=div; chair_id := rec2.id; perform addItemUser(_uid, it_id); if rec.id <> chair_id then perform addItemUser(chair_id, it_id); end if; if _others <> '' then perform addOtherEditors(_others,it_id); end if; return 't'; end if; end; $func$ language 'plpgsql';// Lines 74 on are unmodified

The only additions are on lines 56 and 57. These just add in the others field and the value for that field.

Here are the changes to “itemData.sql”:

81

24

2526

2728

2930

3132

3334

3536

3738

40

4142

4344

4546

4748

4950

5152

5354

5556

5758

5960

6162

6364

6566

6768

6970

7172

73

Page 82: A GWT application using PHP and PostgreSQL

-- itemData.sqlinsert into items (description, justification, origin_department, others, created_at, updated_at) values ('item1', 'justification1','div1','deptA,deptC', timestamp '2012-01-01 9:30:22.1135',timestamp '2012-01-03');insert into items_users values(2,currval('item_id_seq'));insert into items_users values(3,currval('item_id_seq'));insert into items_users values(6,currval('item_id_seq'));

insert into items (description, justification, origin_department, others, created_at, updated_at) values ('item2', 'justification2','deptA','deptC', timestamp '2012-01-02 10:30:22.1135', timestamp '2012-01-04');insert into items_users values(3,currval('item_id_seq'));insert into items_users values(2,currval('item_id_seq'));insert into items_users values(6,currval('item_id_seq'));

insert into items (description, justification, origin_department, others, created_at, updated_at) values ('item3', 'justification3','deptA','', timestamp '2012-01-03 10:30:22.1135', timestamp '2012-01-04');insert into items_users values(3,currval('item_id_seq'));insert into items_users values(2,currval('item_id_seq'));

insert into items (description, justification, origin_department, others, created_at, updated_at) values ('item4', 'justification4','deptB','deptA', timestamp '2012-01-04 10:30:22.1135', timestamp '2012-01-05');insert into items_users values(4,currval('item_id_seq'));insert into items_users values(2,currval('item_id_seq'));insert into items_users values(3,currval('item_id_seq'));insert into items (description, justification, origin_department, others,created_at, updated_at) values ('item5', 'justification5','deptC','deptA', timestamp '2012-01-05 10:30:22.1135', timestamp '2012-01-06');insert into items_users values(6,currval('item_id_seq'));insert into items_users values(5,currval('item_id_seq'));insert into items_users values(3,currval('item_id_seq'));

insert into items (description, justification, origin_department, others,created_at, updated_at) values ('item6', 'justification6','deptC','deptB,deptD', timestamp '2012-01-05 10:30:22.1135', timestamp '2012-01-07');insert into items_users values(6,currval('item_id_seq'));insert into items_users values(5,currval('item_id_seq'));insert into items_users values(4,currval('item_id_seq'));insert into items_users values(7,currval('item_id_seq'));

insert into items (description, justification, origin_department, others,created_at, updated_at) values ('item7', 'justification7','deptD','deptA,deptC', timestamp '2012-01-05 10:30:22.1135', timestamp '2012-01-07');insert into items_users values(7,currval('item_id_seq'));insert into items_users values(5,currval('item_id_seq'));insert into items_users values(3,currval('item_id_seq'));insert into items_users values(6,currval('item_id_seq'));/*select addItem(2,'item1','justification1','3,6');

82

1

23

45

67

8

91011

1213

1415

16

171819

2021

2223

2425

2627

2829

3031

3233

3435

3637

38

394041

4243

4445

4647

4849

5051

5253

5455

5657

58

Page 83: A GWT application using PHP and PostgreSQL

select addItem(3,'item2','justification2','6');select addItem(3,'item3','justification3','');select addItem(4,'item4','justification4','3');select addItem(6,'item5','justification5','3');select addItem(6,'item6','justification6','4,7');select addItem(7,'item7','justification7','3,6');*/

The modifications are shown shaded and occur on lines 3-4, line 8, lines 11-12, line 16, lines 19-20, lines 26-27, lines 33-34, lines 41-42, lines 50-51 and lines 58-59.

Line 3 adds in the others field and line 4 adds in the value for that field. Line 8 was modified to change the user id from 5 to 6, because 5 is the div2 chair.

Line 11 adds in the others field and line 12 adds in the value for that field. Line 16 was modified to change the user id from 5 to 6, because 5 is the div2 chair.

Line 19 adds in the others field and line 20 adds in the value for that field. Similar things are done on lines 26-27, lines 33-34, lines 41-42 and lines 50-51.

Lines 58-59 are modified to change the additional user id of 5 to 6, to reflect the changes on lines 8 and 16. Even though these lines are commented out, these changes were made to be consistent.

Here are the changes to “itemView.sql”:

-- itemView.sqlcreate or replace view item_view as select users.id as uid, users.name, users.username, users.division, users.department, items.id as iid, items.description, items.justification, items.origin_department, items.others, date_part('epoch',items.created_at) as created_at, date_part('epoch',items.updated_at) as updated_at from users inner join items_users on users.id=items_users.user_id inner join items on items_users.item_id=items.id; -- test queriesselect name,department,origin_department,description,others from item_view where uid=2;

Line 6 is added to include the others field. Line 13 is also modified to include the others field.

Modifying the Item class

package test.client;

import com.google.gwt.core.client.JavaScriptObject;class Item extends JavaScriptObject { protected Item()

83

59

6061

6263

6465

1

23

45

67

89

1011

1213

1415

1

234

56

Page 84: A GWT application using PHP and PostgreSQL

{} public final native int getUserID() /*-{ return this.uid; }-*/; public final native String getName() /*-{ return this.name; }-*/; public final native String getUsername() /*-{ return this.username; }-*/; public final native String getDivision() /*-{ return this.division; }-*/; public final native String getDepartment() /*-{ return this.department; }-*/; public final native int getItemID() /*-{ return this.iid; }-*/; public final native String getDescription() /*-{ return this.description; }-*/; public final native String getJustification() /*-{ return this.justification; }-*/; public final native String getOriginalDeparment() /*-{ return this.origin_department; }-*/; public final native String getOthers() /*-{ return this.others; }-*/; public final native String getCreatedAt() /*-{ return this.created_at; }-*/; public final native String getUpdatedAt() /*-{ return this.updated_at; }-*/;}

The lines 44-47 have been added to allow getting the others field.

Modifying Proj3Demo.java

There are a number of changes that needs to be made to “DemoProj3.java”. Instead of

84

7

89

1011

1213

1415

1617

1819

2021

2223

2425

2627

2829

3031

3233

3435

3637

3839

4041

4243

4445

4647

4849

5051

5253

5455

56

Page 85: A GWT application using PHP and PostgreSQL

getting the item id when a table row is clicked on, we need to get the MyItem object that the row holds. Let's start with just a few changes so that we can compile and test to see if the application looks okay. Here is the modified “DemoProj3.java”:

// Lines 1-37 are unmodifiedpublic class DemoProj3 implements EntryPoint, ClickHandler{ VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); ArrayList<MyItem> items = new ArrayList<MyItem>(); JsArray<User> jsonData; JsArray<Item> jsonItemData; VerticalPanel loginPanel = new VerticalPanel(); TextBox usernameBox; PasswordTextBox passBox; Button loginButton = new Button("Login"); int user_id; Button addItemButton = new Button("Add new item"); TextArea descBox; TextArea justBox; CheckBox deptA_cbox; CheckBox deptB_cbox; CheckBox deptC_cbox; CheckBox deptD_cbox; Button submitItemButton = new Button("Submit item"); MyItem selectedItem; Button editItemButton = new Button("Edit item"); Button submitEditItemButton = new Button("Submit"); private static class MyUser { private int id; private String name; private String username; private String division; private String department; public MyUser(int id, String name, String username, String division, String department) { this.id = id; this.name = name; this.username = username; this.division = division; this.department = department; } } private static class MyItem { private int uid; private String name; private String username; private String division; private String department;

85

3839

4041

4243

4445

4647

4849

5051

5253

5455

5657

5859

6061

6263

6465

6667

6869

7071

7273

7475

7677

7879

8081

8283

8485

8687

88

Page 86: A GWT application using PHP and PostgreSQL

private int iid; private String description; private String justification; private String others; private String origin_department; private Date created_at; private Date updated_at; public MyItem(int uid, String name, String user, String div, String dept, int iid, String desc, String just, String orig_dept, String others, String create, String update) { this.uid = uid; this.name = name; username = user; division = div; department = dept; this.iid = iid; description = desc; justification = just; origin_department = orig_dept; this.others = others; double numdate = Double.parseDouble(create); numdate = numdate*1000; created_at = new Date(Math.round(numdate)); numdate = Double.parseDouble(update)*1000; updated_at = new Date(Math.round(numdate)); } } public void onClick(ClickEvent e) { Object source = e.getSource(); if (source == loginButton) { String encData = URL.encode("username") + "=" + URL.encode(usernameBox.getText()) + "&"; encData += URL.encode("password") + "=" + URL.encode(passBox.getText()); String url = baseURL + "login.php"; postRequest(url,encData,"login"); } else if (source == addItemButton) { mainPanel.clear(); showAddItemPanel(); } else if (source == editItemButton) { } else if (source == submitItemButton) { String encData = URL.encode("description") + "=" + URL.encode(descBox.getText()) + "&";//// Lines 141-338 are unmodified// mainPanel.add(table); } // end showUsers() private void showUserItems(String responseText)

86

89

9091

9293

9495

9697

9899

100101

102103

104105

106107

108109

110111

112113

114115

116117

118119

120121

122123

124125

126127

128129

130131

132133

134135

136137

138139

140

339340

341

Page 87: A GWT application using PHP and PostgreSQL

{ mainPanel.clear(); jsonItemData = getItemData(responseText); items = new ArrayList<MyItem>(); Item item = null; for (int i = 0; i < jsonItemData.length(); i++) { item = jsonItemData.get(i); items.add(new MyItem(item.getUserID(), item.getName(), item.getUsername(), item.getDivision(),item.getDepartment(), item.getItemID(),item.getDescription(), item.getJustification(),item.getOriginalDeparment(), item.getOthers(), item.getCreatedAt(),item.getUpdatedAt())); } CellTable<MyItem> table = new CellTable<MyItem>(); TextColumn<MyItem> nameColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.name; } }; TextColumn<MyItem> origDeptColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.origin_department; } }; TextColumn<MyItem> descColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.description; } }; TextColumn<MyItem> justColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.justification; } }; NumberCell numCell = new NumberCell(); Column<MyItem, Number> iidColumn = new Column<MyItem, Number>(numCell) { @Override public Integer getValue(MyItem item)

87

342

343344

345346

347348

349350

351352

353354

355356

357358

359360

361362

363364

365366

367368

369370

371372

373374

375376

377378

379380

381382

383384

385386

387388

389390

391392

393394

395396

397398

399

Page 88: A GWT application using PHP and PostgreSQL

{ return item.iid; } }; DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override public Date getValue(MyItem item) { return item.created_at; } }; DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>(); dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) { list.add(itm); } final SingleSelectionModel<MyItem> selectionModel = new SingleSelectionModel<MyItem>(); table.setSelectionModel(selectionModel); selectionModel.addSelectionChangeHandler( new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent e) { MyItem selected = selectionModel.getSelectedObject(); if (selected != null) { selectedItem = selected; } } }); makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update"); makeSortableTextColumn(table,origDeptColumn,list,"orig_dept"); makeSortableNumberColumn(table,iidColumn,list,"iid"); table.addColumn(nameColumn,"Name"); table.addColumn(iidColumn,"Item id"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(descColumn,"Description"); table.addColumn(justColumn,"Justification"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); addItemButton.addClickHandler(this);

88

400

401402

403404

405406

407408

409410

411412

413414

415416

417418

419420

421422

423424

425426

427428

429430

431432

433434

435436

437438

439440

441442

443444

445446

447448

449450

451452

453454

455456

457

Page 89: A GWT application using PHP and PostgreSQL

editItemButton.addClickHandler(this); HorizontalPanel buttonPanel = new HorizontalPanel(); buttonPanel.add(addItemButton); buttonPanel.add(editItemButton); mainPanel.add(buttonPanel); mainPanel.add(table); } // end showUserItems() private void redrawUserItems() {// Lines 467 on are unmodified

The modified lines are shown shaded. They are lines 60-62, line 92, line 100, line 112, lines 135-137, line 354, line 440 and lines 458-462.

Lines 60-62 add the new instance variables that are needed to edit an item. Line 60 adds a MyItem object called selectedItem. This will be used to hold the table row data when the mouse is used to select a row. Line 61 adds the button that will be clicked on to start the process of editing an item. Line 62 adds the button that will be used to submit data for updating an edited item.

Line 100 adds the others field to the MyItem constructor parameter list. Line 112 is used to set the value for others.

Lines 135-137 show the response to the ClickEvent generated when the editItemButton is hit. At this point, this does not do anything, but code will be added here to start the process to edit an item.

Line 354 is added to get the others value to help in the construction of the MyItem objects that populate the CellTable.

Line 440 stores the MyItem object for the row that was selected by the mouse.

Lines 458-462 are used to add the editItemButton right next to the additemButton whenever the user's items are displayed. Note that these buttons are placed in a HorizontalPanel so that they appear side-by-side.

Now, let's add the changes that will put up the item to be edited.

Lines 1-119 are unmodified120 public void onClick(ClickEvent e) { Object source = e.getSource(); if (source == loginButton) { String encData = URL.encode("username") + "=" + URL.encode(usernameBox.getText()) + "&"; encData += URL.encode("password") + "=" + URL.encode(passBox.getText()); String url = baseURL + "login.php"; postRequest(url,encData,"login"); } else if (source == addItemButton) { mainPanel.clear();

89

458

459460

461462

463464

465466

120

121122

123124

125126

127128

129130

131132

Page 90: A GWT application using PHP and PostgreSQL

showAddItemPanel(); } else if (source == editItemButton) { mainPanel.clear(); showEditItemPanel(); } else if (source == submitItemButton) { String encData = URL.encode("description") + "=" +//// Lines 141-583 are unmodified// private void showAddItemPanel() { HTML heading = new HTML("<h2>Enter new item information:</h2>"); mainPanel.add(heading); Label descLabel = new Label("Description"); mainPanel.add(descLabel); descBox = new TextArea(); descBox.setSize("500", "100"); mainPanel.add(descBox); Label justLabel = new Label("Justification"); mainPanel.add(justLabel); justBox = new TextArea(); justBox.setSize("500","100"); mainPanel.add(justBox); HTML addUserHeading = new HTML("<h3>Check additional editors</h3>"); mainPanel.add(addUserHeading); deptA_cbox = new CheckBox("deptA"); deptB_cbox = new CheckBox("deptB"); deptC_cbox = new CheckBox("deptC"); deptD_cbox = new CheckBox("deptD"); HorizontalPanel checkboxRow = new HorizontalPanel(); checkboxRow.add(deptA_cbox); checkboxRow.add(deptB_cbox); checkboxRow.add(deptC_cbox); checkboxRow.add(deptD_cbox); mainPanel.add(checkboxRow); submitItemButton.addClickHandler(this); mainPanel.add(submitItemButton); } private void showEditItemPanel() { HTML heading = new HTML("<h2>Edit item information:</h2>"); mainPanel.add(heading); Label descLabel = new Label("Description"); mainPanel.add(descLabel); descBox = new TextArea(); descBox.setSize("500", "100"); descBox.setText(selectedItem.description); mainPanel.add(descBox); Label justLabel = new Label("Justification"); mainPanel.add(justLabel); justBox = new TextArea(); justBox.setSize("500","100"); justBox.setText(selectedItem.justification); mainPanel.add(justBox); HTML addUserHeading =

90

133

134135

136137

138139

140

584585

586587

588589

590591

592593

594595

596597

598599

600601

602603

604605

606607

608609

610611

612613

614615

616617

618619

620621

622623

624625

626627

628629

630

Page 91: A GWT application using PHP and PostgreSQL

new HTML("<h3>Check additional editors</h3>"); mainPanel.add(addUserHeading); deptA_cbox = new CheckBox("deptA"); deptB_cbox = new CheckBox("deptB"); deptC_cbox = new CheckBox("deptC"); deptD_cbox = new CheckBox("deptD"); String[] otherEditors = selectedItem.others.split(","); for (int i = 0; i < otherEditors.length; i++) { if (otherEditors[i].equals("deptA")) { deptA_cbox.setValue(true); } else if (otherEditors[i].equals("deptB")) { deptB_cbox.setValue(true); } else if (otherEditors[i].equals("deptC")) { deptC_cbox.setValue(true); } else if (otherEditors[i].equals("deptD")) { deptD_cbox.setValue(true); } } HorizontalPanel checkboxRow = new HorizontalPanel(); checkboxRow.add(deptA_cbox); checkboxRow.add(deptB_cbox); checkboxRow.add(deptC_cbox); checkboxRow.add(deptD_cbox); mainPanel.add(checkboxRow); submitEditItemButton.addClickHandler(this); mainPanel.add(submitEditItemButton); }}

The new lines are shown shaded. They are lines 136-137 and lines 614-660. Lines 136 and 137 clear the main Panel and then call the showEditItemPanel() method. The showEditItemPanel() method is defined on lines 614-660.

The showEditItemPanel() method is similar to the showAddItemPanel() method. The differences are on line 616, line 622, line 628 and lines 637-651. Line 616 is just changed to show that we are editing an item, instead of adding an item. Lines 622, 628 and 637-651 get the values from the selected row and populate the input forms with the current values. Line 638-651 just define a for loop that iterates over the array of type String that is created by splitting the others value.

Displaying the “others” value in the CellTable

Since we now have the value of the others field, we should display this in the CellTable. This is relatively straightforward as this is just a String value.

// Lines 1-394 are unmodified NumberCell numCell = new NumberCell(); Column<MyItem, Number> iidColumn = new Column<MyItem, Number>(numCell) {

91

631

632633

634635

636637

638639

640641

642643

644645

646647

648649

650651

652653

654655

656657

658659

660661

395

396397

398

Page 92: A GWT application using PHP and PostgreSQL

@Override public Integer getValue(MyItem item) { return item.iid; } }; TextColumn<MyItem> othersColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.others; } }; DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override public Date getValue(MyItem item) { return item.created_at; } }; DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>(); dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) { list.add(itm); } final SingleSelectionModel<MyItem> selectionModel = new SingleSelectionModel<MyItem>(); table.setSelectionModel(selectionModel); selectionModel.addSelectionChangeHandler( new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent e) { MyItem selected = selectionModel.getSelectedObject(); if (selected != null) { selectedItem = selected; } } }); makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update"); makeSortableTextColumn(table,origDeptColumn,list,"orig_dept");

92

399

400401

402403

404405

406407

408409

410411

412413

414415

416417

418419

420421

422423

424425

426427

428429

430431

432433

434435

436437

438439

440441

442443

444445

446447

448449

450451

452453

454455

456

Page 93: A GWT application using PHP and PostgreSQL

makeSortableNumberColumn(table,iidColumn,list,"iid"); table.addColumn(nameColumn,"Name"); table.addColumn(iidColumn,"Item id"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(othersColumn,"Other editors"); table.addColumn(descColumn,"Description"); table.addColumn(justColumn,"Justification"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); addItemButton.addClickHandler(this); editItemButton.addClickHandler(this); HorizontalPanel buttonPanel = new HorizontalPanel(); buttonPanel.add(addItemButton); buttonPanel.add(editItemButton); mainPanel.add(buttonPanel); mainPanel.add(table); } // end showUserItems()// Lines 476 on are unmodified

The new lines are shown shaded. They are lines 405-413 and line 461. Lines 405-413 just define a column that the others value can be inserted into. Line 461 adds the column to the CellTable.

Creating a PHP script to handle the item update

Let's create the file “updateItem.php” to handle the update of an item. This should be similar to what is done in the file “createItem.php”. The main difference is that we will do an update query versus an insert query. But, we also have to worry about updating the items_users table. The process for dealing with updating the items_users table has several steps:

1. Get the record from the users table that matches the submitter's uid. If the origin_department for the item does not match the submitter's department, no changes will be allowed to the items_users table. (Changes to the item's description and justification would be allowed for any of the allowed editors.) Get the item's old others value and overwrite the submitted otherDepts value.

2. If the submitter's department matches the item's origin_department, this is the only user allowed to make changes to the additional editors. Delete all records in the items_users table for that item id.

3. Update the items table with the submitted values.

4. Update the items_users table with the others value.

This could be a tricky task within the PHP script as it involves a number of queries. Therefore, it makes sense to write a plpgsql function called updateItem() that will carry out these processes. We will add this function to “itemFuncs.sql”:

93

457

458459

460461

462463

464465

466467

468469

470471

472473

474475

Page 94: A GWT application using PHP and PostgreSQL

// Lines 1-104 are unmodifiedcreate or replace function updateItem(_uid integer, _iid integer, _desc text, _just text, _otherDepts text) returns boolean as $func$ declare itemview_rec record; submitter_rec record; oldEditors text; begin select into itemview_rec * from item_view where uid=_uid and iid=_iid; oldEditors = itemview_rec.others; select into submitter_rec * from users where id=_uid; update items set description=_desc, justification = _just where id=_iid; if itemview_rec.origin_department = submitter_rec.department then update items set others=_otherDepts where id=_iid; perform deleteOtherEditors(oldEditors,_iid); perform addOtherEditors(makeIDString(_otherDepts),_iid); end if; return 't'; end; $func$ language 'plpgsql'; create or replace function deleteOtherEditors(_oldEds text, _iid integer) returns boolean as $func$ declare idString text; myarray text[]; v_id integer; begin if _oldEds <> '' then idString := makeIDString(_oldEds); myarray := string_to_array(idString,','); for i in 1..array_length(myarray,1) loop v_id := cast (myarray[i] as integer); delete from items_users where user_id=v_id and item_id = _iid; end loop; end if; return 't'; end; $func$ language 'plpgsql';

Only the new lines that are added on the bottom are shown. These lines define the updateItem() function and the deleteOtherEditors() function.

Lines 109-112 declare local variables for the updateItem() function. On line 110 we

94

105106

107108

109110

111112

113114

115116

117118

119120

121122

123124

125126

127128

129130

131132

133134

135136

137138

139140

141142

143144

145146

147148

149150

151152

153

Page 95: A GWT application using PHP and PostgreSQL

declare a record variable that will hold the record from the item_view view where the uid matches the passed user id and the iid matches the passed item id. On line 111 we declare a record variable that will hold the record from the users table where the id matches the passed user id. Line 112 will be used to store the value of the others field from the record selected from the item_view view.

On lines 114-115 we obtain the record from the item_view view that matches the passed user id and item id. This record allows us to get the origin_department field and others field values for the existing item. On line 116, we store the value from that others field in oldEditors. On lines 117-118 we obtain the record from the users table where the id matches the passed id. This is basically the record for the submitting user. Lines 117-118 update the description and justification fields. Those fields get updated for any user that is allowed to edit an item.

Only the original submitter is allowed to change the additional editors. So, the if statement defined on lines 121-127 will execute if the submitter is the original user that added the item. Otherwise, the function ends at that point. If the submitter is the original submitter, the others field is updated with the new string of departments that are allowed to edit the item. That is the purpose of the update query on lines 123-124. Line 125 calls the deleteOtherEditors() function to delete any records from the items_users table that were for additional editors.

The items_users table has several records for each inserted item. Suppose John Doe submits an item. John Doe is in deptA, so the division chair for deptA will also be allowed to edit the item. Suppose John Doe also adds deptB and deptD as editors. Based on the way the data was inserted in “itemData.sql”, the user id for Jane Doe, the div1 chair, is 2. John Doe's id is 3. The id for the deptB representative is 4, and the id for the deptD's representative is 7. The item id for John Doe's submission is 2. Therefore the items_users table will have entries like this:

submitter

div chair for submitter

additional editor

additional editor

When we update the additional editors we don't want to change the row for the original submitter and for the original submitter's div chair. We only want to delete the additional editors. Even if some of the original additional editors will remain, it is easier to delete all of

95

user_id item_id

3 2

2 2

4 2

7 2

Page 96: A GWT application using PHP and PostgreSQL

the additional editors, and then add them back in again. So, on line 125 we get rid of all the old additional editors, and on line 126 we add the new additional editors.

The deleteOtherEditors() function defined on lines 133-152 is designed to get rid of all the records for the old additional editors from the items_users table. Lines 137-140 just declare the local variables needed for this function. On line 142, we check to see if the original others was an empty string. If it is, we don't delete any records. If the original others values is not an empty string, lines 143-149 are executed. On line 143, we use the makeIDString() function to convert all the department names into the user id numbers for the respective departments. On line 144 we split apart the string with the id numbers into an array of strings. This sets up the for loop defined on lines 144-149. That for loop will iterate over each of the user id numbers and delete any records from the items_users table that match the user id and the item id.

Now, lets create the PHP script “updateItem.php” so that it makes use of these functions.

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect\n"); } $_POST["description"] = "test description 1"; $_POST["justification"] = "test justification 1"; $_POST["otherDepts"] = "deptB"; $_POST["uid"] = 3; $_POST["iid"] = 1; if (count($_POST) > 0) { $description = $_POST["description"]; $justification = $_POST["justification"]; $otherDepts = $_POST["otherDepts"]; $uid = $_POST["uid"]; $iid = $_POST["iid"]; $query = "select updateItem(:uid,:iid,:desc,:just,:others)"; $statement = $pdo->prepare($query); $statement->execute(array(':uid' => $uid, ':iid' => $iid, ':desc' => $description, ':just' => $justification, ':others' => $otherDepts)); }?>

In this version, lines 7-11 hardcode the POST data. Lines 13-17 just read in the data as though data were actually passed via POST. Lines 18-23 just prepare and call the updateItem() function. If this script is run from the command line using,php -f updateItem.php

the description and justification will be updated, but the others field will not be changed because the user with id number = 3 is not the original submitter. A query in the database

96

1

23

45

67

89

1011

1213

1415

1617

1819

2021

2223

2425

Page 97: A GWT application using PHP and PostgreSQL

would show the results:

select description,justification,others,origin_department from items where id=1; description | justification | others | origin_department --------------------+----------------------+-------------+------------------- test description 1 | test justification 1 | deptA,deptC | div1(1 row)

As can be seen the description and justification are updated, but others is not. It is a good idea to do some further tests within the database. For example:

demo_proj3=> select updateItem(2,1,'desc item1','just item1','deptA'); updateitem ------------ t(1 row)

demo_proj3=> select description,justification,others,origin_department from items where id=1; description | justification | others | origin_department -------------+---------------+--------+------------------- desc item1 | just item1 | deptA | div1(1 row)

The first query calls the updateItem() function. Since the user with an id number = 2 is the original submitter, all of the fields are updated.

After testing to see that things work the way they should, you can just comment out lines 7-11 so that the POST data is no longer hardcoded. Then, we can go back to the GWT application and send the actual POST data. Here is “updateItem.php” with the hardcoded data commented out:

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect\n"); } //$_POST["description"] = "desc 1a"; //$_POST["justification"] = "justification 1a"; //$_POST["otherDepts"] = "deptA,deptC"; //$_POST["uid"] = 2; //$_POST["iid"] = 1; if (count($_POST) > 0) { $description = $_POST["description"]; $justification = $_POST["justification"]; $otherDepts = $_POST["otherDepts"]; $uid = $_POST["uid"]; $iid = $_POST["iid"]; $query = "select updateItem(:uid,:iid,:desc,:just,:others)";

97

12

34

56

78

910

1112

1314

1516

1718

Page 98: A GWT application using PHP and PostgreSQL

$statement = $pdo->prepare($query); $statement->execute(array(':uid' => $uid, ':iid' => $iid, ':desc' => $description, ':just' => $justification, ':others' => $otherDepts)); }?>

In running the GWT application, I discovered that a call to addOtherEditors() inside the addItem() function was not working. Here is the modified “itemFuncs.sql”:

// Lines 1-39 are unmodified create or replace function addItem(_uid integer, _desc text, _just text, _others text) returns boolean as $func$ declare rec record; rec2 record; div text; chair_id integer; it_id integer; begin select into rec * from users where id = _uid; if not found then return 'f'; else insert into items (description,justification, origin_department,others) values (_desc, _just, rec.department,_others); it_id := currval('item_id_seq'); div := rec.division; select into rec2 * from users where department=div; chair_id := rec2.id; perform addItemUser(_uid, it_id); if rec.id <> chair_id then perform addItemUser(chair_id, it_id); end if; if _others <> '' then perform addOtherEditors(makeIDString(_others),it_id); end if; return 't'; end if; end; $func$ language 'plpgsql';// Lines 74 on are unmodified

The change is to line 67. Instead of this,perform addOtherEditors(_others,it_id);

you have to call makeIDString(_others) like this,perform addOtherEditors(makeIDString(_others),it_id);

This was preventing items from being added. The other file that needs to be fixed is

98

19

2021

2223

2425

40

4142

4344

4546

4748

4950

5152

5354

5556

5758

5960

6162

6364

6566

6768

6970

7172

73

Page 99: A GWT application using PHP and PostgreSQL

“createItem.php”:

<?php require_once('demo_proj3.php'); $pdo = connect(); if (!$pdo) { die("Could not connect\n"); } //$_POST["description"] = "test description 1"; //$_POST["justification"] = "test justification 1"; //$_POST["otherDepts"] = "deptA,deptC"; //$_POST["uid"] = 2; if (count($_POST) > 0) { $description = $_POST["description"]; $justification = $_POST["justification"]; $otherDepts = $_POST["otherDepts"]; $uid = $_POST["uid"]; //$query = "select makeIDString(:other)"; //$statement = $pdo->prepare($query); //$statement->execute(array(':other' => $otherDepts)); //$row = $statement->fetch(PDO::FETCH_ASSOC); //$idString = $row["makeidstring"]; $insertQuery = "select addItem(:id,:desc,:just,:others)"; $statement = $pdo->prepare($insertQuery); $statement->execute(array(':id' => $uid, ':desc' => $description, ':just' => $justification, ':others' => $otherDepts)); }?>

Lines 16-20 are commented out because they are not needed. They will be removed later. On line 21, change :idstring to :others. On line 25, make the following changes. Instead of,':idstring' => $idString

replace with, ':others' => $otherDepts

Fixes to DemoProj3.java

The program was starting to do multiple additions of the same item when a new item was added, and the problem would get worse as additional items were added. This was caused by calling the addClickHandler() method inside of methods that get called multiple times to handle ClickEvents. The addClickHandler() method that designates the GWT application to handle the ClickEvents MUST be called inside the onModuleLoad() method. Otherwise, additional ClickHandlers are attached to the buttons each time the methods were called. Since I was calling the addClickHandler() method inside of showEditItemPanel(), showAddItemPanel(), showUserItems(), this caused multiple handlings of the same ClickEvent.

Here is the revised version of “DemoProj3.java”:

99

1

23

45

67

89

1011

1213

1415

1617

1819

2021

2223

2425

2627

Page 100: A GWT application using PHP and PostgreSQL

// public void onModuleLoad() { RootPanel.get().add(mainPanel); init(); addItemButton.addClickHandler(this); editItemButton.addClickHandler(this); submitItemButton.addClickHandler(this); submitEditItemButton.addClickHandler(this); }//

So, all the buttons should have the addClickHandler() method called inside the onModuleLoad() method.

Be sure to remove the addClickHandler() method from anywhere in the program that is not inside the onModuleLoad() or init(), since init() is only called inside onModuleLoad().

Now we can start making the additional changes to handle updating an item in “DemoProj3.java”:

// Lines 1-37 are unmodifiedpublic class DemoProj3 implements EntryPoint, ClickHandler{ VerticalPanel mainPanel = new VerticalPanel(); String baseURL = "http://localhost/demo-proj3/"; ArrayList<MyUser> users = new ArrayList<MyUser>(); ArrayList<MyItem> items = new ArrayList<MyItem>(); JsArray<User> jsonData; JsArray<Item> jsonItemData; VerticalPanel loginPanel = new VerticalPanel(); TextBox usernameBox; PasswordTextBox passBox; Button loginButton = new Button("Login"); int user_id; int item_id; Button addItemButton = new Button("Add new item"); TextArea descBox; TextArea justBox; CheckBox deptA_cbox; CheckBox deptB_cbox; CheckBox deptC_cbox; CheckBox deptD_cbox; Button submitItemButton = new Button("Submit item"); MyItem selectedItem; Button editItemButton = new Button("Edit item"); Button submitEditItemButton = new Button("Submit"); private static class MyUser {//// Lines 67–120 are unmodified//

100

194195

196197

198199

200201

202

3839

4041

4243

4445

4647

4849

5051

5253

5455

5657

5859

6061

6263

6465

66

Page 101: A GWT application using PHP and PostgreSQL

public void onClick(ClickEvent e) { Object source = e.getSource(); if (source == loginButton) { String encData = URL.encode("username") + "=" + URL.encode(usernameBox.getText()) + "&"; encData += URL.encode("password") + "=" + URL.encode(passBox.getText()); String url = baseURL + "login.php"; postRequest(url,encData,"login"); } else if (source == addItemButton) { mainPanel.clear(); showAddItemPanel(); } else if (source == editItemButton) { mainPanel.clear(); showEditItemPanel(); } else if (source == submitItemButton) { String encData = getItemEncData(); String url = baseURL + "createItem.php"; postRequest(url, encData, "createItem"); } // else if source == addItemButton ends else if (source == submitEditItemButton) { String encData = getItemEncData(); encData += "&" + URL.encode("iid") + "=" + URL.encode("" + item_id); String url = baseURL + "updateItem.php"; postRequest(url, encData, "updateItem"); } } public void onModuleLoad() {//// Lines 155-186 are unmodified// private void postRequest(String url, String data, final String requestName) { final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,url); rb.setHeader("Content-type", "application/x-www-form-urlencoded"); try { rb.sendRequest(data, new RequestCallback() { public void onError(final Request request, final Throwable exception) { Window.alert(exception.getMessage()); } public void onResponseReceived(final Request request, final Response response) { if (requestName.equals("login")) { String resp = response.getText().trim(); user_id = Integer.parseInt(resp);

101

121

122123

124125

126127

128129

130131

132133

134135

136137

138139

140141

142143

144145

146147

148149

150151

152153

154

187188

189190

191192

193194

195196

197198

199200

201202

203204

205206

207

Page 102: A GWT application using PHP and PostgreSQL

if (user_id < 1) { usernameBox.setText(""); passBox.setText(""); } else if (user_id == 1) { mainPanel.clear(); String url = baseURL + "showUsers.php"; getRequest(url,"getUsers"); } else { mainPanel.clear(); String encData = URL.encode("uid") + "=" + URL.encode("" + user_id); String url = baseURL + "showUserItems.php"; postRequest(url,encData,"getUserItems"); } } // ends if (requestName.equals("login")) else if (requestName.equals("getUserItems")) { showUserItems(response.getText()); } // ends else if requestName is "getUserItems" else if (requestName.equals("createItem")) { mainPanel.clear(); redrawUserItems(); } // ends elseif requestName is "createItem" else if (requestName.equals("updateItem")) { mainPanel.clear(); redrawUserItems(); } } // onResponseReceived() ends }); // new RequestCallback ends } catch (final Exception e) { Window.alert(e.getMessage()); } } // postRequest() ends private void showUsers(String responseText) {//// Lines 245-308 are unmodified// private void showUserItems(String responseText) { mainPanel.clear(); jsonItemData = getItemData(responseText); items = new ArrayList<MyItem>(); Item item = null; for (int i = 0; i < jsonItemData.length(); i++) { item = jsonItemData.get(i); items.add(new MyItem(item.getUserID(), item.getName(), item.getUsername(), item.getDivision(),item.getDepartment(), item.getItemID(),item.getDescription(), item.getJustification(),item.getOriginalDeparment(), item.getOthers(), item.getCreatedAt(),item.getUpdatedAt())); } CellTable<MyItem> table = new CellTable<MyItem>(); TextColumn<MyItem> nameColumn =

102

208

209210

211212

213214

215216

217218

219220

221222

223224

225226

227228

229230

231232

233234

235236

237238

239240

241242

243244

309

310311

312313

314315

316317

318319

320321

322323

324325

326

Page 103: A GWT application using PHP and PostgreSQL

new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.name; } }; TextColumn<MyItem> origDeptColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.origin_department; } }; TextColumn<MyItem> descColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.description; } }; TextColumn<MyItem> justColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.justification; } }; NumberCell numCell = new NumberCell(); Column<MyItem, Number> iidColumn = new Column<MyItem, Number>(numCell) { @Override public Integer getValue(MyItem item) { return item.iid; } }; TextColumn<MyItem> othersColumn = new TextColumn<MyItem>() { @Override public String getValue(MyItem item) { return item.others; } }; DateCell createDateCell = new DateCell(); Column<MyItem,Date> createDateColumn = new Column<MyItem, Date>(createDateCell) { @Override

103

327

328329

330331

332333

334335

336337

338339

340341

342343

344345

346347

348349

350351

352353

354355

356357

358359

360361

362363

364365

366367

368369

370371

372373

374375

376377

378379

380381

382383

384

Page 104: A GWT application using PHP and PostgreSQL

public Date getValue(MyItem item) { return item.created_at; } }; DateCell updateDateCell = new DateCell(); Column<MyItem,Date> updateDateColumn = new Column<MyItem, Date>(updateDateCell) { @Override public Date getValue(MyItem item) { return item.updated_at; } }; ListDataProvider<MyItem> dataProvider = new ListDataProvider<MyItem>(); dataProvider.addDataDisplay(table); List<MyItem> list = dataProvider.getList(); for (MyItem itm : items) { list.add(itm); } final SingleSelectionModel<MyItem> selectionModel = new SingleSelectionModel<MyItem>(); table.setSelectionModel(selectionModel); selectionModel.addSelectionChangeHandler( new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent e) { MyItem selected = selectionModel.getSelectedObject(); if (selected != null) { selectedItem = selected; item_id = selectedItem.iid; } } }); makeSortableDateColumn(table,createDateColumn,list,"create"); makeSortableDateColumn(table,updateDateColumn,list,"update"); makeSortableTextColumn(table,origDeptColumn,list,"orig_dept"); makeSortableNumberColumn(table,iidColumn,list,"iid"); table.addColumn(nameColumn,"Name"); table.addColumn(iidColumn,"Item id"); table.addColumn(origDeptColumn,"Orig Dept"); table.addColumn(othersColumn,"Other editors"); table.addColumn(descColumn,"Description"); table.addColumn(justColumn,"Justification"); table.addColumn(createDateColumn,"Created"); table.addColumn(updateDateColumn,"Modified"); table.setRowCount(items.size(),true); table.setRowData(0,items); HorizontalPanel buttonPanel = new HorizontalPanel(); buttonPanel.add(addItemButton); buttonPanel.add(editItemButton); mainPanel.add(buttonPanel); mainPanel.add(table); } // end showUserItems() private void redrawUserItems()

104

385

386387

388389

390391

392393

394395

396397

398399

400401

402403

404405

406407

408409

410411

412413

414415

416417

418419

420421

422423

424425

426427

428429

430431

432433

434435

436437

438439

440441

442

Page 105: A GWT application using PHP and PostgreSQL

{//// Lines 444–541 are unmodified// private String getItemEncData() { String encData = URL.encode("description") + "=" + URL.encode(descBox.getText()) + "&"; encData += URL.encode("justification") + "=" + URL.encode(justBox.getText()) + "&"; String checkBoxString = ""; int numBoxesChecked = 0; if (deptA_cbox.getValue()) { numBoxesChecked++; } if (deptB_cbox.getValue()) { numBoxesChecked++; } if (deptC_cbox.getValue()) { numBoxesChecked++; } if (deptD_cbox.getValue()) { numBoxesChecked++; } if (deptA_cbox.getValue()) { checkBoxString += "deptA"; numBoxesChecked--; if (numBoxesChecked > 0) { checkBoxString += ","; } } if (deptB_cbox.getValue()) { checkBoxString += "deptB"; numBoxesChecked--; if (numBoxesChecked > 0) { checkBoxString += ","; } } if (deptC_cbox.getValue()) { checkBoxString += "deptC"; numBoxesChecked--; if (numBoxesChecked > 0) { checkBoxString += ","; } } if (deptD_cbox.getValue()) { checkBoxString += "deptD"; numBoxesChecked--; if (numBoxesChecked > 0) { checkBoxString += ","; } } encData += URL.encode("otherDepts") + "=" + URL.encode(checkBoxString) + "&"; encData += URL.encode("uid") + "=" + URL.encode("" + user_id); return encData; }

105

443

542

543544

545546

547548

549550

551552

553554

555556

557558

559560

561562

563564

565566

567568

569570

571572

573574

575576

577578

579580

581582

583584

585586

587588

589590

591592

593594

595

Page 106: A GWT application using PHP and PostgreSQL

private void init() {Lines 598 on are unmodified}

106

596

597