martes, 4 de diciembre de 2012

GWT and PHP

This time I will describe a CRUD application using GXT Grid and HttpProxy on the client side and on the server side I use PHP and PDO to access a PostgreSQL database.
The communication between client and server side is using JSON then you can use whatever language on the server side that produce a JSON output.
The source code is on github https://github.com/poseidonjm/GXT3-PHP is structured using GWTP and GWT Editor for binding.



Database table
create table users(
id serial,
login varchar(60),
first varchar(150),
last varchar(150),
primary key(id)
);
insert into users(login, first, last)values('jacy','Jacinto','Miraval');
insert into users(login, first, last)values('richard','Richard','Salazar');
insert into users(login, first, last)values('marcelo','Marcelo','Bailón');
insert into users(login, first, last)values('hector','Hector','Jara');
insert into users(login, first, last)values('jose','Jose','Ventura');
view raw users_php.sql hosted with ❤ by GitHub

GXT and HttpProxy
First of all the next interfaz and class are required

MyAutoBeanFactory.java
package com.crud.shared.util;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.sencha.gxt.data.shared.SortInfo;
import com.sencha.gxt.data.shared.loader.FilterConfig;
import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfig;
public interface MyAutoBeanFactory extends AutoBeanFactory {
AutoBean<FilterPagingLoadConfig> loadConfig();
AutoBean<SortInfo> getSortInfo();
AutoBean<FilterConfig> getFilterConfig();
}

MyHttpProxy.java
package com.crud.shared.util;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.http.client.RequestBuilder;
import com.sencha.gxt.data.client.loader.HttpProxy;
import com.sencha.gxt.data.shared.SortInfo;
import com.sencha.gxt.data.shared.loader.FilterConfig;
public class MyHttpProxy<C> extends HttpProxy<C> {
public MyHttpProxy(RequestBuilder builder) {
super(builder);
}
protected List<SortInfo> createRequestSortInfo(MyAutoBeanFactory factory, List<? extends SortInfo> original) {
List<SortInfo> sortInfo = new ArrayList<SortInfo>();
for (int i = 0; i < original.size(); i++) {
SortInfo originalSortInfo = original.get(i);
SortInfo reqSortInfo = factory.getSortInfo().as();
reqSortInfo.setSortDir(originalSortInfo.getSortDir());
reqSortInfo.setSortField(originalSortInfo.getSortField());
sortInfo.add(reqSortInfo);
}
return sortInfo;
}
protected List<FilterConfig> createRequestFilterConfig(MyAutoBeanFactory factory, List<? extends FilterConfig> original) {
List<FilterConfig> sortInfo = new ArrayList<FilterConfig>();
for (int i = 0; i < original.size(); i++) {
FilterConfig originalSortInfo = original.get(i);
FilterConfig reqSortInfo = factory.getFilterConfig().as();
reqSortInfo.setComparison(originalSortInfo.getComparison());
reqSortInfo.setField(originalSortInfo.getField());
reqSortInfo.setType(originalSortInfo.getType());
reqSortInfo.setValue(originalSortInfo.getValue());
sortInfo.add(reqSortInfo);
}
return sortInfo;
}
}

GWT AutoBean
With GWT AutoBean is easy to serialize/deserialize GWT Objects to json and vice versa,
on the client side there is the UserProxy interface that represents the model

package com.crud.shared.proxy;
public interface UserProxy {
String getFirst();
void setFirst(String first);
String getLast();
void setLast(String last);
String getLogin();
void setLogin(String login);
Long getId();
void setId(Long id);
}
view raw UserProxy.java hosted with ❤ by GitHub
UserListProxy represent a set of users
package com.crud.shared.listproxy;
import java.util.List;
import com.crud.shared.proxy.UserProxy;
public interface UserListProxy {
List<UserProxy> getData();
int getTotalCount();
void setData(List<UserProxy> data);
}

With AutoBean the objects are created from a factory

package com.crud.shared.factory;
import com.crud.shared.listproxy.UserListProxy;
import com.crud.shared.proxy.UserProxy;
import com.crud.shared.util.MyAutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBean;
public interface AppAutoBeanFactory extends MyAutoBeanFactory {
AutoBean<UserListProxy> users();
AutoBean<UserProxy> newUser();
}

//Create a object
UserProxy user = factory.newUser().as();
//Serialize an object
AutoBean<UserProxy> bean = AutoBeanUtils.getAutoBean(user);
String json = AutoBeanCodex.encode(bean).getPayload();
//Send to server
String path = "users/users.php?action=save";
RequestBuilder builder = new RequestBuilder(RequestBuilder.PUT, path);
try {
builder.sendRequest(json, new RequestCallback() {
@Override
public void onResponseReceived(Request request, Response response) {
Info.display("Users", "Save Successful");
getView().load();
}
@Override
public void onError(Request request, Throwable exception) {
}
});
} catch (RequestException e) {
e.printStackTrace();
}
GXT Reader
GXT Grid use a reader to parse the output of the server.

package com.crud.shared.jsonreader;
import com.crud.shared.listproxy.UserListProxy;
import com.crud.shared.proxy.UserProxy;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfig;
import com.sencha.gxt.data.shared.loader.JsonReader;
import com.sencha.gxt.data.shared.loader.PagingLoadResult;
import com.sencha.gxt.data.shared.loader.PagingLoadResultBean;
public class UserJsonReader extends JsonReader<PagingLoadResult<UserProxy>, UserListProxy> {
public UserJsonReader(AutoBeanFactory factory,
Class<UserListProxy> rootBeanType) {
super(factory, rootBeanType);
}
@Override
protected PagingLoadResult<UserProxy> createReturnData(Object loadConfig, UserListProxy incomingData) {
FilterPagingLoadConfig pagingLoadConfig = (FilterPagingLoadConfig)loadConfig;
return new PagingLoadResultBean<UserProxy>(incomingData.getData(), incomingData.getTotalCount(), pagingLoadConfig.getOffset());
}
}
GXT Grid
public class UserView extends ViewImpl implements UserPresenter.MyView {
interface UserProxyProperties extends PropertyAccess<UserProxy> {
ModelKeyProvider<UserProxy> id();
ValueProvider<UserProxy, String> login();
ValueProvider<UserProxy, String> first();
ValueProvider<UserProxy, String> last();
}
private final Widget widget;
public interface Binder extends UiBinder<Widget, UserView> {
}
private ListStore<UserProxy> store;
private PagingLoader<FilterPagingLoadConfig, PagingLoadResult<UserProxy>> loader;
@UiField(provided = true)
Grid<UserProxy> grid;
@UiField(provided = true)
PagingToolBar pagToolBar;
@UiField
ContentPanel panel;
@UiField
TextButton add;
@UiField
TextButton edit;
@UiField
TextButton delete;
UserProxy user;
@Inject
public UserView(final Binder binder) {
final AppAutoBeanFactory factory = GWT.create(AppAutoBeanFactory.class);
UserJsonReader reader = new UserJsonReader(factory, UserListProxy.class);
String path = "users/users.php";
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, path);
MyHttpProxy<FilterPagingLoadConfig> proxy = new MyHttpProxy<FilterPagingLoadConfig>(builder){
@Override
public void load(FilterPagingLoadConfig loadConfig,
Callback<String, Throwable> callback) {
List<SortInfo> sortInfo = createRequestSortInfo(factory, loadConfig.getSortInfo());
List<FilterConfig> filterConfig = createRequestFilterConfig(factory, loadConfig.getFilters());
loadConfig.setSortInfo(sortInfo);
loadConfig.setFilters(filterConfig);
super.load(loadConfig, callback);
}
};
//proxy.setWriter(new UrlEncodingWriter<FilterPagingLoadConfig>(factory, FilterPagingLoadConfig.class));
proxy.setWriter(new JsonWriter<FilterPagingLoadConfig>(factory, FilterPagingLoadConfig.class));
loader = new PagingLoader<FilterPagingLoadConfig, PagingLoadResult<UserProxy>>(proxy, reader){
@Override
protected FilterPagingLoadConfig newLoadConfig() {
return new FilterPagingLoadConfigBean();
}
};
loader.setRemoteSort(true);
loader.useLoadConfig(factory.loadConfig().as());
UserProxyProperties props = GWT.create(UserProxyProperties.class);
store = new ListStore<UserProxy>(props.id());
loader.addLoadHandler(new LoadResultListStoreBinding<FilterPagingLoadConfig, UserProxy, PagingLoadResult<UserProxy>>(store));
pagToolBar = new PagingToolBar(3);
pagToolBar.bind(loader);
pagToolBar.getElement().getStyle().setProperty("borderBottom", "none");
IdentityValueProvider<UserProxy> identity = new IdentityValueProvider<UserProxy>();
final CheckBoxSelectionModel<UserProxy> sm = new CheckBoxSelectionModel<UserProxy>(identity);
sm.setSelectionMode(SelectionMode.MULTI);
ColumnConfig<UserProxy, String> usuarioColumn = new ColumnConfig<UserProxy, String>(props.login(), 80, "User Name");
ColumnConfig<UserProxy, String> nombresColumn = new ColumnConfig<UserProxy, String>(props.first(), 150, "First Name");
ColumnConfig<UserProxy, String> apellidosColumn = new ColumnConfig<UserProxy, String>(props.last(), 150, "Last Name");
List<ColumnConfig<UserProxy, ?>> l = new ArrayList<ColumnConfig<UserProxy, ?>>();
l.add(sm.getColumn());
l.add(usuarioColumn);
l.add(nombresColumn);
l.add(apellidosColumn);
ColumnModel<UserProxy> cm = new ColumnModel<UserProxy>(l);
grid = new Grid<UserProxy>(store, cm) {
@Override
protected void onAfterFirstAttach() {
super.onAfterFirstAttach();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
public void execute() {
loader.load();
}
});
}
};
grid.setLoader(loader);
grid.setSelectionModel(sm);
grid.getView().setStripeRows(true);
GridFilters<UserProxy> filters = new GridFilters<UserProxy>(loader);
filters.initPlugin(grid);
filters.setLocal(false);
filters.addFilter(new StringFilter<UserProxy>(props.login()));
filters.addFilter(new StringFilter<UserProxy>(props.first()));
filters.addFilter(new StringFilter<UserProxy>(props.last()));
grid.getSelectionModel().addSelectionChangedHandler(new SelectionChangedHandler<UserProxy>() {
public void onSelectionChanged(
SelectionChangedEvent<UserProxy> event) {
int size = event.getSelection().size();
if(size == 0){
edit.setEnabled(false);
delete.setEnabled(false);
user = null;
}else if(size == 1){
edit.setEnabled(true);
delete.setEnabled(true);
user = event.getSelection().get(0);
}else if(size > 1){
edit.setEnabled(false);
delete.setEnabled(true);
user = null;
}
}
});
widget = binder.createAndBindUi(this);
}
@Override
public Widget asWidget() {
return widget;
}
}

UserView.ui.xml
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
xmlns:toolbar="urn:import:com.sencha.gxt.widget.core.client.toolbar"
xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button"
xmlns:grid="urn:import:com.sencha.gxt.widget.core.client.grid">
<ui:with type="com.crud.client.resources.Resources" field="resources" />
<ui:with field="verticalLayoutData" type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" >
<ui:attributes width="1" height="-1" />
</ui:with>
<ui:with field="centerLayoutData" type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" >
<ui:attributes width="1" height="1" />
</ui:with>
<gxt:ContentPanel headerVisible="false" borders="false" bodyBorder="false" >
<gxt:ContentPanel ui:field="panel" headingText="Users" >
<container:VerticalLayoutContainer>
<container:child layoutData="{verticalLayoutData}">
<toolbar:ToolBar ui:field="toolBar">
<button:TextButton text="Add" ui:field="add" icon="{resources.add}" />
<toolbar:SeparatorToolItem />
<button:TextButton text="Edit" ui:field="edit" icon="{resources.edit}" enabled="false" />
<toolbar:SeparatorToolItem />
<button:TextButton text="Delete" ui:field="delete" icon="{resources.delete}" enabled="false" />
</toolbar:ToolBar>
</container:child>
<container:child layoutData="{centerLayoutData}" >
<grid:Grid ui:field="grid" />
</container:child>
<container:child layoutData="{verticalLayoutData}">
<toolbar:PagingToolBar ui:field="pagToolBar" />
</container:child>
</container:VerticalLayoutContainer>
</gxt:ContentPanel>
</gxt:ContentPanel>
</ui:UiBinder>
view raw UserView.ui.xml hosted with ❤ by GitHub

Run the proyect
You have to start your Apache Server and enable the following extensions for PostgreSQL and PDO:
php_pdo_pgsql
php_pgsql
You have to set the root of your web server to the war of the project
for example I use XAMPP and change the httpd.conf
...
#DocumentRoot "C:/xampp/htdocs"
DocumentRoot "D:\juan\workspace\proyectos\phpcrud\war"
...
#
# This should be changed to whatever you set DocumentRoot to.
#
<Directory "D:\juan\workspace\proyectos\phpcrud\war">
...
restart Apache Server.
Set the parameters of connection to database PostgreSQL in war/config/connect.php
I usually work on Firefox for GWT DevMode




Compile GWT code to JS
I compile GWT with Google Plugin for Eclipse GPE

By default the compiled JS code is compressed.

Then enter the url http://localhost/phpcrud.html#users

The Server Side PHP
I use simple PHP and PDO
connect.php
users.php
paginate.php
Sorry if I am missing explain PHP code.

I hope you can use GWT with any backend and AutoBean is a nice feature of  GWT.

2 comentarios:

  1. Hi,

    I tried to run your example but I have an exception in "createRequestFilterConfig", "original" is null.

    Do you have an idea ?

    Thks,

    JB

    ResponderEliminar
  2. Could you be more specific.
    I use Eclipse Juno
    I downloaded and imported the eclipse project using Egit.
    I have the GPE(Google plugin for eclipse) only for GWT
    I have a GWT 2.5 as SDK.
    And tested with firefox in dev mode with the Google Developer Plugin and is OK.
    Maybe you need configure the eclipse project.

    ResponderEliminar