Move View classes to their own package.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / client / ui / LayoutListView.java
1 /*
2  * Copyright (C) 2010, 2011 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
6  * GWT-Glom is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package org.glom.web.client.ui;
21
22 import java.util.ArrayList;
23
24 import org.glom.web.client.OnlineGlomServiceAsync;
25 import org.glom.web.shared.ColumnInfo;
26 import org.glom.web.shared.GlomField;
27
28 import com.google.gwt.cell.client.AbstractCell;
29 import com.google.gwt.cell.client.ButtonCell;
30 import com.google.gwt.cell.client.CheckboxCell;
31 import com.google.gwt.cell.client.ValueUpdater;
32 import com.google.gwt.dom.client.Element;
33 import com.google.gwt.dom.client.InputElement;
34 import com.google.gwt.dom.client.NativeEvent;
35 import com.google.gwt.event.dom.client.KeyCodes;
36 import com.google.gwt.safehtml.shared.SafeHtml;
37 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
38 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
39 import com.google.gwt.user.cellview.client.CellTable;
40 import com.google.gwt.user.cellview.client.Column;
41 import com.google.gwt.user.cellview.client.ColumnSortEvent.AsyncHandler;
42 import com.google.gwt.user.cellview.client.ColumnSortList;
43 import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
44 import com.google.gwt.user.cellview.client.SafeHtmlHeader;
45 import com.google.gwt.user.cellview.client.SimplePager;
46 import com.google.gwt.user.client.rpc.AsyncCallback;
47 import com.google.gwt.user.client.ui.Composite;
48 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
49 import com.google.gwt.user.client.ui.VerticalPanel;
50 import com.google.gwt.view.client.AsyncDataProvider;
51 import com.google.gwt.view.client.HasData;
52 import com.google.gwt.view.client.Range;
53
54 public class LayoutListView extends Composite {
55
56         private class GlomTextCell extends AbstractCell<GlomField> {
57
58                 // The SafeHtml class is used to escape strings to avoid XSS attacks. This is not strictly
59                 // necessary because the values aren't coming from a user but I'm using it anyway as a reminder.
60                 @Override
61                 public void render(Context context, GlomField value, SafeHtmlBuilder sb) {
62                         if (value == null) {
63                                 return;
64                         }
65
66                         // get the foreground and background colours if they're set
67                         SafeHtml fgcolour = null, bgcolour = null;
68                         String colour = value.getFGColour();
69                         if (colour == null) {
70                                 fgcolour = SafeHtmlUtils.fromSafeConstant("");
71                         } else {
72                                 fgcolour = SafeHtmlUtils.fromString("color:" + colour + ";");
73                         }
74                         colour = value.getBGColour();
75                         if (colour == null) {
76                                 bgcolour = SafeHtmlUtils.fromSafeConstant("");
77                         } else {
78                                 bgcolour = SafeHtmlUtils.fromString("background-color:" + colour + ";");
79                         }
80
81                         // set the text and colours
82                         sb.appendHtmlConstant("<div style=\"" + fgcolour.asString() + bgcolour.asString() + "\">");
83                         sb.append(SafeHtmlUtils.fromString(value.getText()));
84                         sb.appendHtmlConstant("</div>");
85                 }
86         }
87
88         public LayoutListView(ColumnInfo[] columns, int numRows) {
89                 final CellTable<GlomField[]> table = new CellTable<GlomField[]>(20);
90
91                 AsyncDataProvider<GlomField[]> dataProvider = new AsyncDataProvider<GlomField[]>() {
92                         @Override
93                         @SuppressWarnings("unchecked")
94                         protected void onRangeChanged(HasData<GlomField[]> display) {
95                                 // setup the callback object
96                                 final Range range = display.getVisibleRange();
97                                 final int start = range.getStart();
98                                 AsyncCallback<ArrayList<GlomField[]>> callback = new AsyncCallback<ArrayList<GlomField[]>>() {
99                                         public void onFailure(Throwable caught) {
100                                                 // FIXME: need to deal with failure
101                                                 System.out.println("AsyncCallback Failed: OnlineGlomService.getTableData()");
102                                         }
103
104                                         public void onSuccess(ArrayList<GlomField[]> result) {
105                                                 updateRowData(start, result);
106                                         }
107                                 };
108
109                                 // get data from the server
110                                 ColumnSortList colSortList = table.getColumnSortList();
111                                 if (colSortList.size() > 0) {
112                                         // ColumnSortEvent has been requested by the user
113                                         ColumnSortInfo info = colSortList.get(0);
114                                         OnlineGlomServiceAsync.Util.getInstance().getSortedTableData(OnlineGlomView.getCurrentTableName(),
115                                                         start, range.getLength(), table.getColumnIndex((Column<GlomField[], ?>) info.getColumn()),
116                                                         info.isAscending(), callback);
117                                 } else {
118                                         OnlineGlomServiceAsync.Util.getInstance().getTableData(OnlineGlomView.getCurrentTableName(), start,
119                                                         range.getLength(), callback);
120                                 }
121                         }
122                 };
123
124                 dataProvider.addDataDisplay(table);
125
126                 SimplePager pager = new SimplePager(SimplePager.TextLocation.CENTER);
127                 pager.setDisplay(table);
128
129                 // a panel to hold our widgets
130                 VerticalPanel panel = new VerticalPanel();
131                 panel.add(table);
132                 panel.add(pager);
133
134                 // create instances of GlomFieldColumn to retrieve the GlomField objects
135                 for (int i = 0; i < columns.length; i++) {
136                         // create a new column
137                         Column<GlomField[], ?> column = null;
138                         final int j = new Integer(i);
139
140                         switch (columns[i].getType()) {
141                         case TYPE_BOOLEAN:
142                                 // The onBrowserEvent method is overridden to ensure the user can't toggle the checkbox. We'll probably
143                                 // be able to use a CheckboxCell directly when we add support for editing.
144                                 column = new Column<GlomField[], Boolean>(new CheckboxCell(false, false) {
145                                         @Override
146                                         public void onBrowserEvent(Context context, Element parent, Boolean value, NativeEvent event,
147                                                         ValueUpdater<Boolean> valueUpdater) {
148                                                 String type = event.getType();
149
150                                                 boolean enterPressed = "keydown".equals(type) && event.getKeyCode() == KeyCodes.KEY_ENTER;
151                                                 if ("change".equals(type) || enterPressed) {
152                                                         InputElement input = parent.getFirstChild().cast();
153                                                         input.setChecked(!input.isChecked());
154                                                 }
155                                         }
156                                 }) {
157                                         @Override
158                                         public Boolean getValue(GlomField[] object) {
159                                                 return object[j].getBoolean();
160                                         }
161                                 };
162                                 // Make checkboxes centred in the column.
163                                 column.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
164                                 break;
165                         default:
166                                 // use a text rendering cell for types we don't know about but log an error
167                                 // TODO log error here
168                         case TYPE_DATE:
169                         case TYPE_IMAGE:
170                         case TYPE_INVALID:
171                         case TYPE_NUMERIC:
172                         case TYPE_TIME:
173                         case TYPE_TEXT:
174                                 // All of these types are formatted as text in the servlet.
175                                 column = new Column<GlomField[], GlomField>(new GlomTextCell()) {
176                                         @Override
177                                         public GlomField getValue(GlomField[] object) {
178                                                 return object[j];
179                                         }
180                                 };
181
182                                 // Set the alignment of the text.
183                                 switch (columns[i].getAlignment()) {
184                                 case HORIZONTAL_ALIGNMENT_LEFT:
185                                         column.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
186                                         break;
187                                 case HORIZONTAL_ALIGNMENT_RIGHT:
188                                         column.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
189                                         break;
190                                 case HORIZONTAL_ALIGNMENT_AUTO:
191                                 default:
192                                         // TODO: log warning, this shouldn't happen
193                                         column.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_DEFAULT);
194                                         break;
195                                 }
196                                 break;
197                         }
198
199                         // set column properties and add to cell table
200                         column.setSortable(true);
201                         table.addColumn(column, new SafeHtmlHeader(SafeHtmlUtils.fromString(columns[i].getHeader())));
202                 }
203
204                 Column<GlomField[], String> detailsColumn = new Column<GlomField[], String>(new ButtonCell() {
205                         @Override
206                         public void render(Context context, SafeHtml data, SafeHtmlBuilder sb) {
207                                 // TODO Enable the Details button: remove disabled=\"disabled\", add
208                                 // onclick=\"window.location.href='#targetHistoryToken'\", figure out how to create a history token to
209                                 // uniquely identify the row.
210                                 // TODO change to custom css class
211                                 // We're using a button tag for the link. If this causes a problem we can use an anchor tag with css
212                                 // styling
213                                 // to make it look like a button.
214                                 sb.appendHtmlConstant("<button class=\"gwt-Button\" title=\"Go to the details view for this row\" disabled=\"disabled\" type=\"button\" tabindex=\"-1\" >");
215                                 if (data != null) {
216                                         sb.append(data);
217                                 }
218                                 sb.appendHtmlConstant("</button>");
219                         }
220                 }) {
221                         @Override
222                         public String getValue(GlomField[] object) {
223                                 return "Details";
224                         }
225                 };
226
227                 table.addColumn(detailsColumn, "");
228
229                 // set row count which is needed for paging
230                 table.setRowCount(numRows);
231
232                 // add an AsyncHandler to activate sorting for the AsyncDataProvider that was created above
233                 table.addColumnSortHandler(new AsyncHandler(table));
234
235                 // take care of the stuff required for composite widgets
236                 initWidget(panel);
237                 setStyleName("glom-LayoutListView");
238         }
239
240 }