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.HashMap;
020    import java.util.LinkedHashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.kuali.student.common.assembly.data.LookupMetadata;
025    import org.kuali.student.common.assembly.data.LookupMetadata.Usage;
026    import org.kuali.student.common.assembly.data.LookupParamMetadata;
027    import org.kuali.student.common.assembly.data.Metadata;
028    import org.kuali.student.common.assembly.data.Metadata.WriteAccess;
029    import org.kuali.student.common.search.dto.SearchParam;
030    import org.kuali.student.common.search.dto.SearchRequest;
031    import org.kuali.student.common.ui.client.application.Application;
032    import org.kuali.student.common.ui.client.configurable.mvc.DefaultWidgetFactory;
033    import org.kuali.student.common.ui.client.mvc.Callback;
034    import org.kuali.student.common.ui.client.util.UtilConstants;
035    import org.kuali.student.common.ui.client.widgets.KSButton;
036    import org.kuali.student.common.ui.client.widgets.KSButtonAbstract.ButtonStyle;
037    import org.kuali.student.common.ui.client.widgets.KSDropDown;
038    import org.kuali.student.common.ui.client.widgets.KSLabel;
039    import org.kuali.student.common.ui.client.widgets.buttongroups.ButtonEnumerations;
040    import org.kuali.student.common.ui.client.widgets.buttongroups.ButtonEnumerations.ButtonEnum;
041    import org.kuali.student.common.ui.client.widgets.field.layout.button.ActionCancelGroup;
042    import org.kuali.student.common.ui.client.widgets.field.layout.button.ButtonGroup;
043    import org.kuali.student.common.ui.client.widgets.field.layout.element.FieldElement;
044    import org.kuali.student.common.ui.client.widgets.field.layout.layouts.FieldLayoutComponent;
045    import org.kuali.student.common.ui.client.widgets.layout.HorizontalBlockFlowPanel;
046    import org.kuali.student.common.ui.client.widgets.layout.VerticalFlowPanel;
047    import org.kuali.student.common.ui.client.widgets.list.KSSelectItemWidgetAbstract;
048    import org.kuali.student.common.ui.client.widgets.list.ListItems;
049    import org.kuali.student.common.ui.client.widgets.list.SelectionChangeEvent;
050    import org.kuali.student.common.ui.client.widgets.list.SelectionChangeHandler;
051    import org.kuali.student.common.ui.client.widgets.searchtable.ResultRow;
052    
053    import com.google.gwt.core.client.GWT;
054    import com.google.gwt.event.dom.client.ClickEvent;
055    import com.google.gwt.event.dom.client.ClickHandler;
056    import com.google.gwt.event.dom.client.KeyCodes;
057    import com.google.gwt.event.dom.client.KeyDownEvent;
058    import com.google.gwt.event.dom.client.KeyDownHandler;
059    import com.google.gwt.event.shared.HandlerRegistration;
060    import com.google.gwt.user.client.Window;
061    import com.google.gwt.user.client.ui.Composite;
062    import com.google.gwt.user.client.ui.HTMLPanel;
063    import com.google.gwt.user.client.ui.HasText;
064    import com.google.gwt.user.client.ui.HasValue;
065    import com.google.gwt.user.client.ui.SimplePanel;
066    import com.google.gwt.user.client.ui.SuggestBox;
067    import com.google.gwt.user.client.ui.VerticalPanel;
068    import com.google.gwt.user.client.ui.Widget;
069    
070    public class SearchPanel extends Composite{
071    
072        //Layout configuration
073        private VerticalFlowPanel layout = new VerticalFlowPanel();
074        private SimplePanel searchSelectorPanel = new SimplePanel();
075        private VerticalFlowPanel resultsTablePanel = new VerticalFlowPanel();
076        private HorizontalBlockFlowPanel enteredCriteriaString = new HorizontalBlockFlowPanel();
077        private CollapsablePanel modifySearchPanel;
078        private String criteriaInstructions = getMessage("searchPanelEnterFields");
079        private KSLabel enteredCriteriaHeading = new KSLabel(getMessage("searchPanelCriteria"));
080        protected SearchResultsTable table;
081        private boolean isMultiSelect = true;
082    
083            public static enum SearchStyle{ADVANCED, CUSTOM}; 
084        private ActionCancelGroup actionCancelButtons;
085    
086        private String actionLabel = getMessage("search");  //set default action label
087        private boolean resultsSelected = false;
088        private boolean hasSearchParams = false;                    //indicates if there are any user supplied search parameters
089                                                                                                            //if false will auto default search and not display modify search link.
090        
091            //Search data
092        private List<LookupMetadata> lookups = new ArrayList<LookupMetadata>();
093        private boolean multiSelect = false;
094        private boolean resultsShown = false;    
095        private SearchParametersWidget activeSearchParametersWidget = null;
096        // uses "name" of the lookup metadata to lookup the widget that layouts the search UI
097        private Map<String, SearchParametersWidget> searchParameterWidgetMap = new HashMap<String, SearchParametersWidget>();
098        private List<SearchField> searchFields = new ArrayList<SearchField>();
099        private List<Callback<LookupMetadata>> lookupChangedCallbacks = new ArrayList<Callback<LookupMetadata>>();    
100        private String selectedLookupName;
101        private List<Callback<List<SelectedResults>>> selectedCompleteCallbacks = new ArrayList<Callback<List<SelectedResults>>>();  
102        private List<Callback<Boolean>> actionCompletedCallbacks = new ArrayList<Callback<Boolean>>();    
103    
104            private Callback<ButtonEnumerations.ButtonEnum> actionCancelCallback = new Callback<ButtonEnumerations.ButtonEnum>(){
105    
106                    @Override
107            public void exec(ButtonEnum result) {
108                if (result == ButtonEnumerations.SearchCancelEnum.SEARCH) {
109                    table.removeContent();
110                    getActionCompleteCallback().exec(true);                                 
111                }
112           }
113            };
114       
115        interface SearchParametersWidget {
116            public SearchRequest getSearchRequest();
117            public LookupMetadata getLookupMetadata();
118            public List<HasSearchParam> getSearchParams();
119        }
120    
121        public SearchPanel(LookupMetadata meta){
122            lookups.add(meta);
123            this.initWidget(layout);
124        }
125    
126        public SearchPanel(List<LookupMetadata> metas){
127            lookups = metas;       
128            this.initWidget(layout);
129        }
130    
131        @SuppressWarnings("unchecked")
132            public ButtonGroup getButtons(){
133            return actionCancelButtons;
134        }
135        
136        public void setMutipleSelect(boolean isMultiSelect){
137            this.isMultiSelect = isMultiSelect;
138        }
139        
140        public void setupButtons() {
141            if (actionCancelButtons != null) {
142                actionCancelButtons.setButtonText(ButtonEnumerations.SearchCancelEnum.SEARCH, getActionLabel());
143                actionCancelButtons.addCallback(actionCancelCallback);            
144            }       
145        }
146        
147        public void setupSearch() {
148            resultsTablePanel.clear();
149            layout.clear();
150            resultsShown = false;
151            hasSearchParams = false;
152    
153            //create search panel
154            Widget searchParamPanel;        
155            if (lookups.size() == 1) {
156                searchParamPanel = createSearchParamPanel(lookups.get(0));
157                selectedLookupName = lookups.get(0).getName();
158                activeSearchParametersWidget = searchParameterWidgetMap.get(selectedLookupName);
159            } else {
160                LinkedHashMap<String, Widget> searches = new LinkedHashMap<String, Widget>();
161                LinkedHashMap<String, LookupMetadata> searchLookups = new LinkedHashMap<String, LookupMetadata>();
162                for(LookupMetadata lookup: lookups){
163                    searches.put(lookup.getName(), createSearchParamPanel(lookup));
164                    searchLookups.put(lookup.getName(), lookup);
165                }
166                selectedLookupName = lookups.get(0).getName();
167                // Sets the activeSearchParametersWidget to be the first search
168                activeSearchParametersWidget = searchParameterWidgetMap.get(selectedLookupName);
169                String actionLabel = (lookups.get(0) == null)? null : lookups.get(0)
170                        .getWidgetOptionValue(LookupMetadata.WidgetOption.ADVANCED_LIGHTBOX_ACTION_LABEL);
171                setActionLabel(actionLabel);
172                searchParamPanel = new SwappablePanel(searches);
173                ((SwappablePanel)searchParamPanel).setSearchLookups(searchLookups);
174                ((SwappablePanel)searchParamPanel).addLookupChangedCallback(new Callback<LookupMetadata>() {
175                    @Override
176                    public void exec(LookupMetadata selectedLookup) {
177                        activeSearchParametersWidget = searchParameterWidgetMap.get(selectedLookup.getName());
178                        selectedLookupName = selectedLookup.getName();
179                        if (lookupChangedCallbacks != null) {
180                            for (Callback<LookupMetadata> callback : lookupChangedCallbacks) {
181                                callback.exec(selectedLookup);
182                            }
183                        }
184                    }
185                });
186            }
187            searchSelectorPanel.setWidget(searchParamPanel);
188            layout.add(searchSelectorPanel);
189            
190            //Create layout for results screen
191            
192            //Entered criteria section
193            if (hasSearchParams){
194                    enteredCriteriaHeading.addStyleName("ks-form-module-single-line-margin");
195                    enteredCriteriaHeading.addStyleName("KS-Advanced-Search-Search-Criteria-Label");
196                    resultsTablePanel.add(enteredCriteriaHeading);
197                    resultsTablePanel.add(enteredCriteriaString);
198                    resultsTablePanel.setVisible(false);
199            }
200            
201            //Search Results table
202            table = GWT.create(SearchResultsTable.class);
203            table.setMutipleSelect(isMultiSelect);
204            table.addStyleName("KS-Advanced-Search-Results-Table");
205            for (Callback<List<SelectedResults>> selectionCompleteCallback : selectedCompleteCallbacks) {
206                table.addSelectionCompleteCallback(selectionCompleteCallback);
207            }
208            resultsTablePanel.add(table);
209            layout.add(resultsTablePanel); 
210            
211            table.getMslabel().addClickHandler(new ClickHandler(){
212    
213                            @Override
214                            public void onClick(ClickEvent event) {
215                                    if(modifySearchPanel.isOpen()){
216                                            modifySearchPanel.close();
217                                    }
218                                    else{
219                                            modifySearchPanel.open();
220                                    }
221                                    
222                                    resultsTablePanel.setVisible(false);
223                                    resultsSelected = false;
224                                    actionCancelButtons.setButtonText(ButtonEnumerations.SearchCancelEnum.SEARCH, getActionLabel());
225                            }
226                    });
227            
228            resultsSelected = false;
229            actionCancelButtons.setButtonText(ButtonEnumerations.SearchCancelEnum.SEARCH, getActionLabel());
230            
231            //Execute the search and display the results of there are no user supplied search parameters defined
232            if (!hasSearchParams){
233                    getActionCompleteCallback().exec(true);
234            }
235        }
236    
237        private Widget createSearchParamPanel(LookupMetadata meta){
238            ParamListItems listItems = new ParamListItems(meta);
239            final AdvancedSearch advancedSearch = new AdvancedSearch(meta);
240            LinkPanel panel = new LinkPanel(SearchStyle.ADVANCED, advancedSearch);
241            searchParameterWidgetMap.put(meta.getName(), advancedSearch);
242    
243            //check whether we need custom tab i.e. whether we have at least one parameter that should appear on custom tab
244            for(LookupParamMetadata metaParam: meta.getParams()){
245                    if (metaParam.getUsage() != null && metaParam.getUsage() != Usage.DEFAULT){
246                            hasSearchParams = true;   //Only set to true if this only has user supplied params (i.e. params without default values)
247                    }
248                    if ((metaParam.getUsage() == Usage.CUSTOM) || (metaParam.getUsage() == Usage.ADVANCED_CUSTOM)) {
249                    
250                    final CustomizedSearch customizedSearch = new CustomizedSearch(meta, listItems);
251                    KSButton button = panel.addLinkToPanel(SearchStyle.ADVANCED, getMessage("searchPanelCustomizeSearch"), SearchStyle.CUSTOM);
252                    button.addClickHandler(new ClickHandler(){
253    
254                        @Override
255                        public void onClick(ClickEvent event) {
256                            resultsTablePanel.setVisible(false);
257                            activeSearchParametersWidget = customizedSearch;
258                        }});
259                    button.addStyleName("KS-Advanced-Search-Link");
260                    button.getParent().addStyleName("clearfix");
261                    panel.addPanel(SearchStyle.CUSTOM, customizedSearch);
262                    button = panel.addLinkToPanel(SearchStyle.CUSTOM, getMessage("searchPanelReturnToAdvancedSearch"), SearchStyle.ADVANCED);
263                    button.addClickHandler(new ClickHandler(){
264    
265                        @Override
266                        public void onClick(ClickEvent event) {
267                            resultsTablePanel.setVisible(false);
268                            activeSearchParametersWidget = advancedSearch;
269                        }});
270                    button.addStyleName("KS-Advanced-Search-Link");
271                    button.getParent().addStyleName("clearfix");
272                    break;
273                }
274            }
275    
276            return panel;
277        }
278    
279        private class CustomizedSearch extends Composite implements SearchParametersWidget {
280    
281            private List<CustomLine> lines = new ArrayList<CustomLine>();
282            private List<HasSearchParam> searchParams = new ArrayList<HasSearchParam>();
283            private VerticalPanel layout = new VerticalPanel();
284            private VerticalPanel linePanel = new VerticalPanel();
285            private LookupMetadata meta;
286    
287            public CustomizedSearch(final LookupMetadata meta, final ParamListItems listItems){
288    
289                KSLabel instrLabel = new KSLabel(criteriaInstructions);
290                layout.add(instrLabel);
291    
292                layout.add(linePanel);
293                CustomLine line = new CustomLine(meta, listItems);
294                line.addStyleName("ks-form-module-single-line-margin");
295                linePanel.add(line);
296                lines.add(line);
297                searchParams.add(line);
298                this.meta = meta;
299    
300                KSButton addCriteria = new KSButton(getMessage("searchPanelAddCriteria"), ButtonStyle.SECONDARY);
301                addCriteria.addClickHandler(new ClickHandler(){
302    
303                    @Override
304                    public void onClick(ClickEvent event) {
305                        CustomLine line = new CustomLine(meta, listItems);
306                        line.addStyleName("ks-form-module-single-line-margin");
307                        linePanel.add(line);
308                        lines.add(line);
309                        searchParams.add(line);
310                    }
311                });
312    
313                addCriteria.addStyleName("ks-form-module-single-line-margin");
314                layout.add(addCriteria);    
315                
316                this.initWidget(layout);
317            }
318            
319            public LookupMetadata getLookupMetadata() {
320                return meta;
321            }
322    
323            @Override
324            public SearchRequest getSearchRequest() {
325                //Create search request and then pass it to the table
326                //TODO pass search to the table
327                SearchRequest sr = new SearchRequest();
328                List<SearchParam> params = new ArrayList<SearchParam>();
329                for(CustomLine field: lines){
330                    SearchParam param = field.getSearchParam();
331                    //TODO is this check needed here? probably. assuming string here
332                    if((param.getValue() != null)){
333                        params.add(param);
334                    }
335                }
336    
337                //add search criteria widgets to the custom tab
338                for(LookupParamMetadata metaParam: meta.getParams()){
339    
340                    //select only parameters shown on custom search tab
341                    if ((metaParam.getUsage() != Usage.CUSTOM) && (metaParam.getUsage() != Usage.ADVANCED_CUSTOM)) {
342                        continue;
343                    }
344    
345                    if(metaParam.getWriteAccess() == WriteAccess.NEVER){
346                        SearchParam param = new SearchParam();
347                        param.setKey(metaParam.getKey());
348                        if(metaParam.getDefaultValueList()==null){
349                            param.setValue(metaParam.getDefaultValueString());
350                        }else{
351                            param.setValue(metaParam.getDefaultValueList());
352                        }
353                        params.add(param);
354                    }
355                    else if(metaParam.getWriteAccess() == WriteAccess.WHEN_NULL){
356                        if((metaParam.getDefaultValueString() != null && !metaParam.getDefaultValueString().isEmpty())||
357                           (metaParam.getDefaultValueList() != null && !metaParam.getDefaultValueList().isEmpty())){
358                            SearchParam param = new SearchParam();
359                            param.setKey(metaParam.getKey());
360                            if(metaParam.getDefaultValueList()==null){
361                                param.setValue(metaParam.getDefaultValueString());
362                            }else{
363                                param.setValue(metaParam.getDefaultValueList());
364                            }
365                            params.add(param);
366                        }
367                    }
368                }
369    
370                sr.setParams(params);
371                sr.setSearchKey(meta.getSearchTypeId());
372                return sr;
373            }
374    
375            @Override
376            public List<HasSearchParam> getSearchParams() {
377                return searchParams;
378            }
379    
380        }
381    
382        private interface HasSearchParam{
383            public SearchParam getSearchParam();
384            public String getFieldName();
385            public String getSearchText();
386        }
387    
388        private static class CustomLine extends Composite implements HasSearchParam{
389            private KSDropDown paramSelector = new KSDropDown();
390            private SimplePanel widgetPanel = new SimplePanel();
391            private Widget widget = null;
392            private String key;
393            private HorizontalBlockFlowPanel layout = new HorizontalBlockFlowPanel();
394            private ParamListItems listItems;
395    
396            public CustomLine(LookupMetadata meta, final ParamListItems listItems){
397                
398                List<LookupParamMetadata> customParams = new ArrayList<LookupParamMetadata>();
399    
400                for (LookupParamMetadata lookupParamMetadata : listItems.getParams()) {
401                    if (lookupParamMetadata.getWriteAccess() != WriteAccess.NEVER){
402                        if (lookupParamMetadata.getUsage() == Usage.CUSTOM || lookupParamMetadata.getUsage() == Usage.ADVANCED_CUSTOM ) {
403                            customParams.add(lookupParamMetadata);
404                        }                   
405                    } 
406                }
407                
408                for(LookupParamMetadata param:customParams){
409                   String id = param.getKey()+"-name";
410                   if(Application.getApplicationContext().getMessage(id)!=null)
411                   {
412                    param.setName(Application.getApplicationContext().getMessage(id));  
413                   }
414                  }
415                
416                ParamListItems customParamList = new ParamListItems(customParams);
417                
418                this.listItems = customParamList;
419                paramSelector.setBlankFirstItem(false);
420                paramSelector.setListItems(customParamList);
421    
422                String id = meta.getParams().get(0).getKey();
423                paramSelector.selectItem(id);
424                widget = listItems.getWidget(id);
425                key = id;
426                widgetPanel.setWidget(widget);
427                paramSelector.addSelectionChangeHandler(new SelectionChangeHandler(){
428    
429                    @Override
430                    public void onSelectionChange(SelectionChangeEvent event) {
431                        String id = ((KSSelectItemWidgetAbstract)event.getWidget()).getSelectedItem();
432                        widget = listItems.getWidget(id);
433                        widgetPanel.setWidget(widget);
434                        key = id;
435    
436                    }
437                });
438                layout.add(paramSelector);
439                layout.add(widgetPanel);
440                this.initWidget(layout);
441            }
442    
443            public SearchParam getSearchParam(){
444                return SearchPanel.getSearchParam(widget, key);
445            }
446    
447            public String getKey(){
448                return key;
449            }
450    
451            public String getFieldName(){
452                String id = paramSelector.getSelectedItem();
453                return listItems.getItemText(id);
454            }
455    
456            @Override
457            public String getSearchText() {
458                return SearchPanel.getSearchText(widget);
459            }
460        }
461    
462        private class AdvancedSearch extends Composite implements SearchParametersWidget {
463            private LookupMetadata meta;
464            private List<HasSearchParam> searchParams = new ArrayList<HasSearchParam>();
465    
466            public AdvancedSearch(final LookupMetadata meta){
467                VerticalPanel panel = new VerticalPanel();
468    
469                KSLabel instrLabel = new KSLabel();
470                panel.add(instrLabel);
471                this.meta = meta;            
472    
473                //add widget for each search criteria to the advanced tab
474                boolean allFieldsRequired = true;
475                for(LookupParamMetadata param: meta.getParams()){
476    
477                    //select only parameters shown on advanced search tab
478                    if ((param.getUsage() != Usage.ADVANCED) && (param.getUsage() != Usage.ADVANCED_CUSTOM)) {
479                        continue;
480                    }
481    
482                    if ((param.getWriteAccess() == WriteAccess.ALWAYS) || (param.getWriteAccess() == WriteAccess.REQUIRED)){
483                        SearchField paramField = new SearchField(param);
484                        searchFields.add(paramField);
485                        panel.add(paramField);
486                        searchParams.add(paramField);
487                    }
488                    else if (param.getWriteAccess() == WriteAccess.WHEN_NULL){
489                        if(param.getDefaultValueString() == null && param.getDefaultValueList() == null){
490                            SearchField paramField = new SearchField(param);
491                            searchFields.add(paramField);
492                            panel.add(paramField);
493                            searchParams.add(paramField); 
494                        }
495                    }
496    
497                    if (param.getWriteAccess() != Metadata.WriteAccess.REQUIRED) {
498                        allFieldsRequired = false;
499                    }
500                }
501    
502                //do not show criteria instructions if we have only one criteria field or in case all fields are required
503                if ((searchFields.size() > 1) || (allFieldsRequired == false)) {
504                    instrLabel.setText(criteriaInstructions);
505                }    
506                this.addKeyDownHandler(downHandler);
507                this.initWidget(panel);
508            }
509            
510            public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
511                return addDomHandler(handler, KeyDownEvent.getType());
512            }
513            
514            public LookupMetadata getLookupMetadata() {
515                return meta;
516            }        
517            
518            private KeyDownHandler downHandler = new KeyDownHandler(){
519    
520                    @Override
521                    public void onKeyDown(KeyDownEvent event) {
522                            if(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER)   // Enter button
523                                    actionCancelCallback.exec(ButtonEnumerations.SearchCancelEnum.SEARCH);
524                    }
525                    
526            };
527    
528            @Override
529            public SearchRequest getSearchRequest() {
530                SearchRequest sr = new SearchRequest();
531                List<SearchParam> params = new ArrayList<SearchParam>();
532                List<HasSearchParam> searchParams = getSearchParams();
533    
534                //initialize search parameters if user entered values into search criteria fields in UI
535                
536                for(HasSearchParam field: searchParams){
537                    SearchParam param = field.getSearchParam();
538                    //TODO is this null check needed here? probably. assuming string here
539                    //TODO make check more robust here/inserting params more robust
540                    //do not pass to the search parameters that are empty
541                    //FIXME hack - comparison to 'optional' - replace with check against 'optional' field and update related lookup metadata
542                    if ((param.getValue() != null) && ((param.getValue().toString().trim().isEmpty() == false) || (param.getKey().toLowerCase().indexOf("optional") == -1))) {
543                        params.add(param);
544                    }
545                }
546    
547                //initialize search parameters that are hidden from the UI because they are set to default context specific values
548                for(LookupParamMetadata metaParam: meta.getParams()){
549                    if(metaParam.getWriteAccess() == WriteAccess.NEVER){
550                        if ((metaParam.getDefaultValueString() == null || metaParam.getDefaultValueString().isEmpty())&&
551                            (metaParam.getDefaultValueList() == null || metaParam.getDefaultValueList().isEmpty())) {
552                            //FIXME throw an exception?
553                            GWT.log("Key = " + metaParam.getKey() + " has write access NEVER but has no default value!", null);
554                            continue;
555                        }
556                        SearchParam param = new SearchParam();
557                        param.setKey(metaParam.getKey());
558                        if(metaParam.getDefaultValueList()==null){
559                            param.setValue(metaParam.getDefaultValueString());
560                        }else{
561                            param.setValue(metaParam.getDefaultValueList());
562                        }
563                        params.add(param);
564                    }
565                    else if(metaParam.getWriteAccess() == WriteAccess.WHEN_NULL){
566                        if((metaParam.getDefaultValueString() != null && !metaParam.getDefaultValueString().isEmpty())||
567                           (metaParam.getDefaultValueList() != null && !metaParam.getDefaultValueList().isEmpty())){
568                            SearchParam param = new SearchParam();
569                            param.setKey(metaParam.getKey());
570                            if(metaParam.getDefaultValueList()==null){
571                                param.setValue(metaParam.getDefaultValueString());
572                            }else{
573                                param.setValue(metaParam.getDefaultValueList());
574                            }
575                            params.add(param);
576                        }
577                    }
578                }
579                sr.setParams(params);
580                if (meta.getResultSortKey() != null) {
581                    sr.setSortColumn(meta.getResultSortKey());
582                }
583                sr.setSearchKey(meta.getSearchTypeId());
584                return sr;
585            }
586    
587            @Override
588            public List<HasSearchParam> getSearchParams() {
589                return searchParams;
590            }
591        }
592    
593        private class SearchField extends Composite implements HasSearchParam{
594    
595            private Widget widget = null;
596            private LookupParamMetadata meta = null;
597            private VerticalFlowPanel panel = new VerticalFlowPanel();
598            private String fieldName;       
599    
600            public SearchParam getSearchParam(){
601                return SearchPanel.getSearchParam(widget, meta.getKey());
602            }
603    
604            public SearchField(LookupParamMetadata param){
605                meta = param;
606                //TODO use message call here
607                if(getMessage(param.getKey()+FieldLayoutComponent.NAME_MESSAGE_KEY)!=null)
608                    fieldName = getMessage(param.getKey()+FieldLayoutComponent.NAME_MESSAGE_KEY);
609                else
610                    fieldName = param.getName();
611                
612                widget = DefaultWidgetFactory.getInstance().getWidget(param);
613                if(param.getDefaultValueString() != null){
614                    //TODO Add handling of default value lists here
615                    if(widget instanceof HasText){
616                        ((HasText) widget).setText(param.getDefaultValueString().toString());
617                    }
618                    else if(widget instanceof HasValue){
619                        ((HasValue) widget).setValue(param.getDefaultValueString());
620                    }
621                }
622    
623                //FIXME: remove because required field '*' indication will be part of FieldElement class
624                if (param.getWriteAccess() == Metadata.WriteAccess.REQUIRED) {
625                    fieldName += " *";
626                }            
627    
628                FieldElement fieldElement = new FieldElement(fieldName, widget);
629                fieldElement.getTitleWidget().addStyleName("KS-Picker-Criteria-Text");
630                panel.add(fieldElement);
631                panel.addStyleName("clearfix");
632                                        
633                this.initWidget(panel);
634            }
635    
636            public Widget getFieldPanel(){
637                return panel;
638            }
639    
640            public String getFieldName() {
641                return fieldName;
642            }
643    
644            @Override
645            public String getSearchText() {
646                return SearchPanel.getSearchText(widget);
647            }
648        }
649    
650        private static SearchParam getSearchParam(final Widget widget, String key){
651            SearchParam param = new SearchParam();
652            param.setKey(key);
653            if(widget instanceof HasText){
654                param.setValue(((HasText) widget).getText());
655            }
656            else if(widget instanceof HasValue){
657                Object value = ((HasValue) widget).getValue();
658                if(value != null){
659                //TODO need to handle date and other types here, how they are converted for search, etc
660                    if(value instanceof String){
661                        param.setValue((String)value);
662                    }
663                    else{
664                        param.setValue(value.toString());
665                        GWT.log("Fields in search probably(?) shouldnt have values other than string", null);
666                    }
667                }
668            }
669            else if (widget instanceof KSPicker){
670                    KSPicker pickerWidget = (KSPicker)widget; 
671                    String pickerValue = pickerWidget.getValue().toString();
672                    if (UtilConstants.IMPOSSIBLE_CHARACTERS.equals(pickerValue)){
673                            SuggestBox suggestBox = (SuggestBox)pickerWidget.getInputWidget();
674                            pickerValue = suggestBox.getText();
675                    }
676                    
677                param.setValue(pickerValue);
678            }
679            else {
680                param.setValue("");
681            }
682    
683            return param;
684        }
685        
686        private static String getSearchText(final Widget widget){
687            if(widget instanceof HasText){
688                return ((HasText) widget).getText();
689            }
690            else if(widget instanceof HasValue){
691                Object value = ((HasValue) widget).getValue();
692                if(value != null){
693                //TODO need to handle date and other types here, how they are converted for search, etc
694                    if(value instanceof String){
695                        return (String)value;
696                    }
697                    else{
698                        GWT.log("Fields in search probably(?) shouldnt have values other than string", null);
699                        return value.toString();
700                    }
701                }
702            }
703            else if (widget instanceof KSPicker){
704                return ((KSPicker)widget).getDisplayValue();
705            }
706            return "";
707        }
708    
709        private void showCriteriaChosen(List<HasSearchParam> fields){
710            enteredCriteriaString.clear();
711            boolean first = true;;
712            for(HasSearchParam field: fields){
713                String name = field.getFieldName();
714                String value = field.getSearchText();
715              if(!value.isEmpty()&&value.equals("$$##@@"))
716                    value = field.getSearchParam().getValue().toString().toUpperCase();
717                if(!value.isEmpty()){
718                    HTMLPanel label = new HTMLPanel(name + ": <b>" + value + "</b>&nbsp;");
719                    if (!first) {
720                        label.addStyleName("KS-Advanced-Search-Search-Criteria-Text");
721                    }
722                    enteredCriteriaString.add(label);
723                    first = false;
724                }
725            }
726        }
727    
728        public List<String> getSelectedIds(){
729            List<String> ids = new ArrayList<String>();
730            if(table != null){
731                ids = table.getSelectedIds();
732            }
733            return ids;
734        }
735    
736        public List<SelectedResults> getSelectedValues() {
737    
738            List<SelectedResults> selectedValues = new ArrayList<SelectedResults>();
739            if (table != null) {
740                List<ResultRow> selectedRows = table.getSelectedRows();
741                for (ResultRow row : selectedRows) {
742                    String displayKey = row.getValue(activeSearchParametersWidget.getLookupMetadata().getResultDisplayKey());
743                    String returnKey = row.getValue(activeSearchParametersWidget.getLookupMetadata().getResultReturnKey());
744                    selectedValues.add(new SelectedResults(displayKey, returnKey, row));
745                    if (multiSelect == false) {
746                        break;
747                    }
748                }
749            }
750    
751            return selectedValues;
752        }
753    
754        public boolean isMultiSelect() {
755            return multiSelect;
756        }
757    
758        public void setMultiSelect(boolean multiSelect) {
759            this.multiSelect = multiSelect;
760        }
761    
762        private static class ParamListItems implements ListItems{
763    
764            private List<LookupParamMetadata> params = new ArrayList<LookupParamMetadata>();
765    
766            public ParamListItems(LookupMetadata meta){
767                params = meta.getParams();
768            }
769    
770            public ParamListItems(List<LookupParamMetadata> params){
771                this.params = params;
772            }
773            
774            @Override
775            public List<String> getAttrKeys() {
776                return new ArrayList<String>();
777            }
778    
779            @Override
780            public String getItemAttribute(String id, String attrkey) {
781                return "";
782            }
783    
784            @Override
785            public int getItemCount() {
786                return params.size();
787            }
788    
789            @Override
790            public List<String> getItemIds() {
791                List<String> ids = new ArrayList<String>();
792                for(LookupParamMetadata param: params){
793                    ids.add(param.getKey());
794                }
795                return ids;
796            }
797    
798            @Override
799            public String getItemText(String id) {
800                String itemText = id;
801                for(LookupParamMetadata param: params){
802                    if(param.getKey().equals(id)){
803                        //TODO this should be a message key
804                        itemText = param.getName();
805                        break;
806                    }
807                }
808                return itemText;
809            }
810    
811            public Widget getWidget(String id){
812                Widget w = null;
813                for(LookupParamMetadata param: params){
814                    if(param.getKey().equals(id)){
815                        w = DefaultWidgetFactory.getInstance().getWidget(param);
816                        break;
817                    }
818                }
819                return w;
820            }
821    
822            public List<LookupParamMetadata> getParams() {
823                return params;
824            }
825        }
826    
827        private String getMessage(final String msgKey) {
828            return Application.getApplicationContext().getMessage(msgKey);
829        }
830    
831        public void addLookupChangedCallback(Callback<LookupMetadata> callback) {
832            lookupChangedCallbacks.add(callback);
833        }
834        
835        //FIXME: Is an action complete callback really necessary here, couldn't this simply be a method to perform the seach action.
836        public Callback<Boolean> getActionCompleteCallback() {
837            return new Callback<Boolean>() {
838                
839                @Override
840                public void exec(Boolean result) {                               
841                    
842                    if (resultsSelected == true) {
843                            List<SelectedResults> selectedItems = getSelectedValues();
844                            if (selectedItems.isEmpty())
845                                    Window.alert("Please, select a value");
846                            else
847                            {       
848                                    for(Callback<List<SelectedResults>> callback: selectedCompleteCallbacks){
849                                            callback.exec(selectedItems);
850                                    }  
851                                    return;
852                            }       
853                    }
854                    
855                    actionCancelButtons.setButtonText(ButtonEnumerations.SearchCancelEnum.SEARCH, getMessage("select"));
856                    resultsSelected = true;
857                    
858                    SearchRequest sr = getSearchRequest();
859                    table.performSearch(sr, activeSearchParametersWidget.getLookupMetadata().getResults(), activeSearchParametersWidget.getLookupMetadata().getResultReturnKey(), activeSearchParametersWidget.getLookupMetadata().getResultDisplayKey(), true);
860                    resultsTablePanel.setVisible(true);
861                    List<HasSearchParam> userCriteria = new ArrayList<HasSearchParam>();
862                    List<HasSearchParam> searchParams = activeSearchParametersWidget.getSearchParams();
863    
864                    //initialize search parameters if user entered values into search criteria fields in UI
865                    for(HasSearchParam field: searchParams){
866                        SearchParam param = field.getSearchParam();
867                        //TODO is this null check needed here? probably. assuming string here
868                        //TODO make check more robust here/inserting params more robust
869                        //do not pass to the search parameters that are empty
870                        //FIXME hack - comparison to 'optional' - replace with check against 'optional' field and update related lookup metadata
871                        if ((param.getValue() != null) && ((param.getValue().toString().trim().isEmpty() == false) || (param.getKey().toLowerCase().indexOf("optional") == -1))) {
872                            userCriteria.add(field);
873                        }
874                    }
875                    showCriteriaChosen(userCriteria);
876    
877                    if (hasSearchParams){
878                            //Only display modify link if there are search parametes available.
879                            if(!resultsShown){
880                                searchSelectorPanel.removeFromParent();
881                                modifySearchPanel = GWT.create(CollapsablePanel.class);
882                                modifySearchPanel.initialise(getMessage("searchPanelModifySearch"), searchSelectorPanel, false);
883                                modifySearchPanel.getLabel().addClickHandler(new ClickHandler(){
884                                    @Override
885                                    public void onClick(ClickEvent event) {
886                                        resultsTablePanel.setVisible(false);
887                                        actionCancelButtons.setButtonText(ButtonEnumerations.SearchCancelEnum.SEARCH, getActionLabel());
888                                        resultsSelected = false;
889                                    }});
890                                SearchPanel.this.layout.insert(modifySearchPanel, 0);
891                                
892                            }
893                            else{
894                                modifySearchPanel.close();
895                            }
896                    }
897                    resultsShown = true; 
898                    
899                    for(Callback<Boolean> callback: actionCompletedCallbacks){
900                        callback.exec( Boolean.valueOf(true));
901                    }                
902                }
903            };
904        }
905        
906        public SearchRequest getSearchRequest() {
907            if (activeSearchParametersWidget != null) {
908                return activeSearchParametersWidget.getSearchRequest();
909            }
910            return null;
911        }    
912        
913        public void setActionCancelButtonGroup(ActionCancelGroup actionCancelButtons) {
914            this.actionCancelButtons = actionCancelButtons;
915        }
916            
917        public String getSelectedLookupName() {
918            return selectedLookupName;
919        }
920    
921        public void setSelectedLookupName(String selectedLookupName) {
922            this.selectedLookupName = selectedLookupName;
923        }   
924        
925        public void addSelectionCompleteCallback(Callback<List<SelectedResults>> callback){
926            selectedCompleteCallbacks.add(callback);
927        }   
928        
929        public void addActionCompleteCallback(Callback<Boolean> callback){
930            actionCompletedCallbacks.add(callback);
931        }
932    
933        public String getActionLabel() {
934            return actionLabel;
935        }
936    
937        public void setActionLabel(String actionLabel) {
938            if ((actionLabel != null) && (actionLabel.trim().length() > 0)) {
939                this.actionLabel = actionLabel;
940            }
941        }
942    
943    }