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    }