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 }