001 /**
002 * Copyright 2010 The Kuali Foundation Licensed under the
003 * Educational Community License, Version 2.0 (the "License"); you may
004 * not use this file except in compliance with the License. You may
005 * obtain a copy of the License at
006 *
007 * http://www.osedu.org/licenses/ECL-2.0
008 *
009 * Unless required by applicable law or agreed to in writing,
010 * software distributed under the License is distributed on an "AS IS"
011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
012 * or implied. See the License for the specific language governing
013 * permissions and limitations under the License.
014 */
015
016 package org.kuali.student.common.ui.client.widgets.search;
017
018 import java.util.ArrayList;
019 import java.util.List;
020
021 import org.kuali.student.common.assembly.data.Data.DataType;
022 import org.kuali.student.common.assembly.data.LookupResultMetadata;
023 import org.kuali.student.common.search.dto.SearchRequest;
024 import org.kuali.student.common.search.dto.SearchResult;
025 import org.kuali.student.common.search.dto.SearchResultCell;
026 import org.kuali.student.common.search.dto.SearchResultRow;
027 import org.kuali.student.common.ui.client.application.Application;
028 import org.kuali.student.common.ui.client.application.KSAsyncCallback;
029 import org.kuali.student.common.ui.client.mvc.Callback;
030 import org.kuali.student.common.ui.client.service.SearchRpcServiceAsync;
031 import org.kuali.student.common.ui.client.service.SearchServiceFactory;
032 import org.kuali.student.common.ui.client.widgets.KSButton;
033 import org.kuali.student.common.ui.client.widgets.KSButtonAbstract.ButtonStyle;
034 import org.kuali.student.common.ui.client.widgets.KSLabel;
035 import org.kuali.student.common.ui.client.widgets.field.layout.layouts.FieldLayoutComponent;
036 import org.kuali.student.common.ui.client.widgets.layout.VerticalFlowPanel;
037 import org.kuali.student.common.ui.client.widgets.searchtable.ResultRow;
038 import org.kuali.student.common.ui.client.widgets.table.scroll.Column;
039 import org.kuali.student.common.ui.client.widgets.table.scroll.DefaultTableModel;
040 import org.kuali.student.common.ui.client.widgets.table.scroll.RetrieveAdditionalDataHandler;
041 import org.kuali.student.common.ui.client.widgets.table.scroll.Row;
042 import org.kuali.student.common.ui.client.widgets.table.scroll.RowComparator;
043 import org.kuali.student.common.ui.client.widgets.table.scroll.Table;
044
045 import com.google.gwt.core.client.GWT;
046 import com.google.gwt.user.client.Window;
047 import com.google.gwt.user.client.ui.Composite;
048 import com.google.gwt.user.client.ui.VerticalPanel;
049
050 public class SearchResultsTable extends Composite{
051
052 protected final int PAGE_SIZE = 10;
053
054 protected SearchRpcServiceAsync searchRpcServiceAsync = SearchServiceFactory.getSearchService();
055
056 protected VerticalPanel layout = new VerticalPanel();
057
058 private DefaultTableModel tableModel;
059 protected String resultIdColumnKey;
060 protected String resultDisplayKey;
061 protected SearchRequest searchRequest;
062 private Table table = new Table();
063 protected boolean isMultiSelect = true;
064 protected boolean withMslable = true;
065 protected KSButton mslabel = new KSButton("Modify your search?", ButtonStyle.DEFAULT_ANCHOR);
066
067 protected List<Callback<List<SelectedResults>>> selectedCompleteCallbacks = new ArrayList<Callback<List<SelectedResults>>>();
068
069 public KSButton getMslabel() {
070 return mslabel;
071 }
072
073 public void setMslabel(KSButton mslabel) {
074 this.mslabel = mslabel;
075 }
076
077 public void removeContent() {
078 table.removeContent();
079 }
080 public SearchResultsTable(){
081 super();
082 redraw();
083 layout.setWidth("100%");
084 initWidget(layout);
085 }
086
087 public void redraw(){
088 layout.clear();
089 }
090
091 public void setMutipleSelect(boolean isMultiSelect){
092 this.isMultiSelect = isMultiSelect;
093 }
094
095 public void setWithMslable(boolean withMslable) {
096 this.withMslable = withMslable;
097 }
098
099 //FIXME do we really need to recreate the table for every refresh?
100 public void initializeTable(List<LookupResultMetadata> listResultMetadata, String resultIdKey, String resultDisplayKey){
101
102 //creating a new table because stale data was corrupting new searches
103 table = new Table();
104 table.removeAllRows();
105 this.resultIdColumnKey = resultIdKey;
106 this.resultDisplayKey = resultDisplayKey;
107
108 tableModel = new DefaultTableModel();
109 tableModel.setMultipleSelectable(isMultiSelect);
110
111 //create table heading
112 for (LookupResultMetadata r: listResultMetadata){
113 if(!r.isHidden()){
114 Column col1 = new Column();
115 col1.setId(r.getKey());
116 String header = "";
117 if (Application.getApplicationContext().getMessage(r.getKey() + FieldLayoutComponent.NAME_MESSAGE_KEY) != null) {
118 header = Application.getApplicationContext().getMessage(r.getKey() + FieldLayoutComponent.NAME_MESSAGE_KEY);
119 } else {
120 header = Application.getApplicationContext().getUILabel("", null, null, r.getName());
121 }
122 col1.setName(header);
123 col1.setId(r.getKey());
124 col1.setWidth("100px");
125 col1.setAscendingRowComparator(new FieldAscendingRowComparator(r.getKey(), r.getDataType()));
126 col1.setDescendingRowComparator(new FieldDescendingRowComparator(r.getKey(), r.getDataType()));
127
128 tableModel.addColumn(col1);
129 }
130 }
131
132 // TODO - there's a better way to do this
133 if (this.searchRequest.getSearchKey().toLowerCase().contains("cross")) {
134 tableModel.setMoreData(false);
135 }
136 if(isMultiSelect){
137 tableModel.installCheckBoxRowHeaderColumn();
138 }
139
140 table.getScrollPanel().setHeight("300px");
141 table.setTableModel(tableModel);
142
143 table.addRetrieveAdditionalDataHandler(new RetrieveAdditionalDataHandler(){
144 @Override
145 public void onAdditionalDataRequest() {
146 performOnDemandSearch(tableModel.getRowCount(), PAGE_SIZE);
147 //tableModel.fireTableDataChanged();
148 }
149 });
150
151 redraw();
152 layout.add(table);
153 }
154
155 public void performSearch(SearchRequest searchRequest, List<LookupResultMetadata> listResultMetadata, String resultIdKey, String resultDisplayKey, boolean pagedResults) {
156 this.searchRequest = searchRequest;
157 initializeTable(listResultMetadata, resultIdKey, resultDisplayKey);
158 if (this.searchRequest.getSearchKey().toLowerCase().contains("cross")) {
159 //FIXME Do we still need this if condition?
160 // Added an else to the if(pagedResults) line to prevent searches being executed
161 // twice if the search name includes cross
162 performOnDemandSearch(0, 0);
163 }
164 else if(pagedResults){
165 performOnDemandSearch(0, PAGE_SIZE);
166 }
167 else{
168 performOnDemandSearch(0, 0);
169 }
170 }
171
172 public void performSearch(SearchRequest searchRequest, List<LookupResultMetadata> listResultMetadata, String resultIdKey, boolean pagedResults){
173 this.performSearch(searchRequest, listResultMetadata, resultIdKey, null, true);
174 }
175
176 public void performSearch(SearchRequest searchRequest, List<LookupResultMetadata> listResultMetadata, String resultIdKey){
177 this.performSearch(searchRequest, listResultMetadata, resultIdKey, true);
178 }
179
180 protected void performOnDemandSearch(int startAt, int size) {
181
182 table.displayLoading(true);
183 searchRequest.setStartAt(startAt);
184 if (size != 0) {
185 searchRequest.setNeededTotalResults(false);
186 searchRequest.setMaxResults(size);
187 } else {
188 searchRequest.setNeededTotalResults(true);
189 }
190
191 searchRpcServiceAsync.search(searchRequest, new KSAsyncCallback<SearchResult>(){
192
193 @Override
194 public void handleFailure(Throwable cause) {
195 GWT.log("Failed to perform search", cause); //FIXME more detail info here
196 Window.alert("Failed to perform search");
197 table.displayLoading(false);
198 }
199
200 @Override
201 public void onSuccess(SearchResult results) {
202 table.addContent();
203
204 if(results != null && results.getRows() != null && results.getRows().size() != 0){
205 for (SearchResultRow r: results.getRows()){
206 ResultRow theRow = new ResultRow();
207 for(SearchResultCell c: r.getCells()){
208 if(c.getKey().equals(resultIdColumnKey)){
209 theRow.setId(c.getValue());
210 }
211 theRow.setValue(c.getKey(), c.getValue());
212 }
213 tableModel.addRow(new SearchResultsRow(theRow));
214 }
215 } else {
216 tableModel.setMoreData(false);
217
218 //add no matches found if no search results
219 if(searchRequest.getStartAt() == 0){
220 table.removeContent();
221 VerticalFlowPanel noResultsPanel = new VerticalFlowPanel();
222 noResultsPanel.add(new KSLabel("No matches found"));
223 if(withMslable) noResultsPanel.add(mslabel);
224 noResultsPanel.addStyleName("ks-no-results-message");
225 table.getScrollPanel().add(noResultsPanel);
226 }
227 }
228 // tableModel.selectFirstRow();
229 tableModel.fireTableDataChanged();
230 table.displayLoading(false);
231 }
232 });
233 }
234
235 public List<ResultRow> getSelectedRows(){
236 List<ResultRow> rows = new ArrayList<ResultRow>();
237 for(Row row : tableModel.getSelectedRows()){
238 rows.add(((SearchResultsRow)row).getResultRow());
239 }
240 return rows;
241 }
242
243 public List<String> getSelectedIds(){
244 List<String> ids = new ArrayList<String>();
245 for(Row row : tableModel.getSelectedRows()){
246 ids.add(((SearchResultsRow)row).getResultRow().getId());
247 }
248 return ids;
249 }
250
251 public void addSelectionCompleteCallback(Callback<List<SelectedResults>> callback){
252 selectedCompleteCallbacks.add(callback);
253 }
254 }
255
256 class SearchResultsRow extends Row {
257
258 ResultRow row;
259
260 public SearchResultsRow(ResultRow row) {
261 this.row = row;
262 }
263
264 @Override
265 public Object getCellData(String columnId) {
266 return row.getValue(columnId);
267 }
268
269 @Override
270 public void setCellData(String columnId, Object newValue) {
271 row.setValue(columnId, newValue.toString());
272 }
273
274 @Override
275 public String toString() {
276 return row.toString();
277 }
278
279 public ResultRow getResultRow() {
280 return row;
281 }
282 }
283
284 class FieldAscendingRowComparator extends RowComparator{
285
286 String columnId;
287 DataType type;
288
289 FieldAscendingRowComparator(String columnId, DataType type) {
290 this.columnId = columnId;
291 this.type = type;
292 }
293
294 @Override
295 public int compare(Row row0, Row row1) {
296 String id0, id1;
297
298 if (type.equals(DataType.STRING)) {
299 id0 = (String)row0.getCellData(columnId);
300 id1 = (String)row1.getCellData(columnId);
301 } else {
302 id0 = (String)row0.getCellData(columnId);
303 id1 = (String)row1.getCellData(columnId);
304 }
305 return id0.compareTo(id1);
306 }
307 }
308
309 class FieldDescendingRowComparator extends RowComparator{
310
311 String columnId;
312 DataType type;
313
314 FieldDescendingRowComparator(String columnId, DataType type) {
315 this.columnId = columnId;
316 this.type = type;
317 }
318
319 @Override
320 public int compare(Row row0, Row row1) {
321 String id0, id1;
322
323 if (type.equals(DataType.STRING)) {
324 id0 = (String)row0.getCellData(columnId);
325 id1 = (String)row1.getCellData(columnId);
326 } else {
327 id0 = (String)row0.getCellData(columnId);
328 id1 = (String)row1.getCellData(columnId);
329 }
330 return id1.compareTo(id0);
331 }
332 }