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