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.ui.client.application.Application;
025    import org.kuali.student.common.ui.client.configurable.mvc.DefaultWidgetFactory;
026    import org.kuali.student.common.ui.client.mvc.Callback;
027    import org.kuali.student.common.ui.client.util.UtilConstants;
028    import org.kuali.student.common.ui.client.widgets.KSButton;
029    import org.kuali.student.common.ui.client.widgets.KSDropDown;
030    import org.kuali.student.common.ui.client.widgets.KSLabel;
031    import org.kuali.student.common.ui.client.widgets.KSButtonAbstract.ButtonStyle;
032    import org.kuali.student.common.ui.client.widgets.buttongroups.ButtonEnumerations;
033    import org.kuali.student.common.ui.client.widgets.buttongroups.ButtonEnumerations.ButtonEnum;
034    import org.kuali.student.common.ui.client.widgets.field.layout.button.ActionCancelGroup;
035    import org.kuali.student.common.ui.client.widgets.field.layout.button.ButtonGroup;
036    import org.kuali.student.common.ui.client.widgets.field.layout.element.FieldElement;
037    import org.kuali.student.common.ui.client.widgets.field.layout.layouts.FieldLayoutComponent;
038    import org.kuali.student.common.ui.client.widgets.layout.HorizontalBlockFlowPanel;
039    import org.kuali.student.common.ui.client.widgets.layout.VerticalFlowPanel;
040    import org.kuali.student.common.ui.client.widgets.list.KSSelectItemWidgetAbstract;
041    import org.kuali.student.common.ui.client.widgets.list.ListItems;
042    import org.kuali.student.common.ui.client.widgets.list.SelectionChangeEvent;
043    import org.kuali.student.common.ui.client.widgets.list.SelectionChangeHandler;
044    import org.kuali.student.common.ui.client.widgets.searchtable.ResultRow;
045    import org.kuali.student.r1.common.assembly.data.LookupMetadata;
046    import org.kuali.student.r1.common.assembly.data.LookupMetadata.Usage;
047    import org.kuali.student.r1.common.assembly.data.LookupParamMetadata;
048    import org.kuali.student.r1.common.assembly.data.Metadata;
049    import org.kuali.student.r1.common.assembly.data.Metadata.WriteAccess;
050    import org.kuali.student.r2.core.search.dto.SearchParamInfo;
051    import org.kuali.student.r2.core.search.dto.SearchRequestInfo;
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 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    
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 SearchRequestInfo getSearchRequest() {
325                //Create search request and then pass it to the table
326                //TODO pass search to the table
327                SearchRequestInfo sr = new SearchRequestInfo();
328                List<SearchParamInfo> params = new ArrayList<SearchParamInfo>();
329                for(CustomLine field: lines){
330                    SearchParamInfo param = field.getSearchParam();
331                    //TODO is this check needed here? probably. assuming string here
332                    if((param.getValues().get(0) != 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                        SearchParamInfo param = new SearchParamInfo();
347                        param.setKey(metaParam.getKey());
348                        if(metaParam.getDefaultValueList()==null){
349                            param.getValues().add(metaParam.getDefaultValueString());
350                        }else{
351                            param.setValues(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                            SearchParamInfo param = new SearchParamInfo();
359                            param.setKey(metaParam.getKey());
360                            if(metaParam.getDefaultValueList()==null){
361                                param.getValues().add(metaParam.getDefaultValueString());
362                            }else{
363                                param.setValues(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 SearchParamInfo 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 SearchParamInfo 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                String searchId = meta.getId();
473    
474                //add widget for each search criteria to the advanced tab
475                boolean allFieldsRequired = true;
476                for(LookupParamMetadata param: meta.getParams()){
477    
478                    //select only parameters shown on advanced search tab
479                    if ((param.getUsage() != Usage.ADVANCED) && (param.getUsage() != Usage.ADVANCED_CUSTOM)) {
480                        continue;
481                    }
482    
483                    if ((param.getWriteAccess() == WriteAccess.ALWAYS) || (param.getWriteAccess() == WriteAccess.REQUIRED)){
484                        SearchField paramField = new SearchField(param, searchId);
485                        searchFields.add(paramField);
486                        panel.add(paramField);
487                        searchParams.add(paramField);
488                    }
489                    else if (param.getWriteAccess() == WriteAccess.WHEN_NULL){
490                        if(param.getDefaultValueString() == null && param.getDefaultValueList() == null){
491                            SearchField paramField = new SearchField(param, searchId);
492                            searchFields.add(paramField);
493                            panel.add(paramField);
494                            searchParams.add(paramField); 
495                        }
496                    }
497    
498                    if (param.getWriteAccess() != Metadata.WriteAccess.REQUIRED) {
499                        allFieldsRequired = false;
500                    }
501                }
502    
503                //do not show criteria instructions if we have only one criteria field or in case all fields are required
504                if ((searchFields.size() > 1) || (allFieldsRequired == false)) {
505                    instrLabel.setText(criteriaInstructions);
506                }    
507                this.addKeyDownHandler(downHandler);
508                this.initWidget(panel);
509            }
510            
511            public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
512                return addDomHandler(handler, KeyDownEvent.getType());
513            }
514            
515            public LookupMetadata getLookupMetadata() {
516                return meta;
517            }        
518            
519            private KeyDownHandler downHandler = new KeyDownHandler(){
520    
521                    @Override
522                    public void onKeyDown(KeyDownEvent event) {
523                            if(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER)   // Enter button
524                                    actionCancelCallback.exec(ButtonEnumerations.SearchCancelEnum.SEARCH);
525                    }
526                    
527            };
528    
529            @Override
530            public SearchRequestInfo getSearchRequest() {
531                SearchRequestInfo sr = new SearchRequestInfo();
532                List<SearchParamInfo> params = new ArrayList<SearchParamInfo>();
533                List<HasSearchParam> searchParams = getSearchParams();
534    
535                //initialize search parameters if user entered values into search criteria fields in UI
536                
537                for(HasSearchParam field: searchParams){
538                    SearchParamInfo param = field.getSearchParam();
539                    //TODO is this null check needed here? probably. assuming string here
540                    //TODO make check more robust here/inserting params more robust
541                    //do not pass to the search parameters that are empty
542                    //FIXME hack - comparison to 'optional' - replace with check against 'optional' field and update related lookup metadata
543                    if ((param.getValues().get(0) != null) && ((param.getValues().get(0).toString().trim().isEmpty() == false) || (param.getKey().toLowerCase().indexOf("optional") == -1))) {
544                        params.add(param);
545                    }
546                }
547    
548                //initialize search parameters that are hidden from the UI because they are set to default context specific values
549                for(LookupParamMetadata metaParam: meta.getParams()){
550                    if(metaParam.getWriteAccess() == WriteAccess.NEVER){
551                        if ((metaParam.getDefaultValueString() == null || metaParam.getDefaultValueString().isEmpty())&&
552                            (metaParam.getDefaultValueList() == null || metaParam.getDefaultValueList().isEmpty())) {
553                            //FIXME throw an exception?
554                            GWT.log("Key = " + metaParam.getKey() + " has write access NEVER but has no default value!", null);
555                            continue;
556                        }
557                        SearchParamInfo param = new SearchParamInfo();
558                        param.setKey(metaParam.getKey());
559                        if(metaParam.getDefaultValueList()==null){
560                            param.getValues().add(metaParam.getDefaultValueString());
561                        }else{
562                            param.setValues(metaParam.getDefaultValueList());
563                        }
564                        params.add(param);
565                    }
566                    else if(metaParam.getWriteAccess() == WriteAccess.WHEN_NULL){
567                        if((metaParam.getDefaultValueString() != null && !metaParam.getDefaultValueString().isEmpty())||
568                           (metaParam.getDefaultValueList() != null && !metaParam.getDefaultValueList().isEmpty())){
569                            SearchParamInfo param = new SearchParamInfo();
570                            param.setKey(metaParam.getKey());
571                            if(metaParam.getDefaultValueList()==null){
572                                param.getValues().add(metaParam.getDefaultValueString());
573                            }else{
574                                param.setValues(metaParam.getDefaultValueList());
575                            }
576                            params.add(param);
577                        }
578                    }
579                }
580                sr.setParams(params);
581                if (meta.getResultSortKey() != null) {
582                    sr.setSortColumn(meta.getResultSortKey());
583                }
584                sr.setSearchKey(meta.getSearchTypeId());
585                return sr;
586            }
587    
588            @Override
589            public List<HasSearchParam> getSearchParams() {
590                return searchParams;
591            }
592        }
593    
594        private class SearchField extends Composite implements HasSearchParam{
595    
596            private Widget widget = null;
597            private LookupParamMetadata meta = null;
598            private VerticalFlowPanel panel = new VerticalFlowPanel();
599            private String fieldName;       
600    
601            public SearchParamInfo getSearchParam(){
602                return SearchPanel.getSearchParam(widget, meta.getKey());
603            }
604    
605            public SearchField(LookupParamMetadata param, String searchId){
606                meta = param;
607                //TODO use message call here
608                // KSCM-1326 This is where we implemented several options to override
609                // Search Field names. Loads custom overridden messages from KSMG_MESSAGE table.
610                if(getMessage(searchId + ":" + param.getKey()+ FieldLayoutComponent.NAME_MESSAGE_KEY)!=null)
611                    fieldName = getMessage(searchId + ":" + param.getKey()+ FieldLayoutComponent.NAME_MESSAGE_KEY);
612                else if(getMessage(param.getKey()+FieldLayoutComponent.NAME_MESSAGE_KEY)!=null)
613                    fieldName = getMessage(param.getKey()+FieldLayoutComponent.NAME_MESSAGE_KEY);
614                else
615                    fieldName = param.getName();
616                
617                widget = DefaultWidgetFactory.getInstance().getWidget(param);
618                if(param.getDefaultValueString() != null){
619                    //TODO Add handling of default value lists here
620                    if(widget instanceof HasText){
621                        ((HasText) widget).setText(param.getDefaultValueString().toString());
622                    }
623                    else if(widget instanceof HasValue){
624                        ((HasValue) widget).setValue(param.getDefaultValueString());
625                    }
626                }
627    
628                //FIXME: remove because required field '*' indication will be part of FieldElement class
629                if (param.getWriteAccess() == Metadata.WriteAccess.REQUIRED) {
630                    fieldName += " *";
631                }            
632    
633                FieldElement fieldElement = new FieldElement(fieldName, widget);
634                fieldElement.getTitleWidget().addStyleName("KS-Picker-Criteria-Text");
635                panel.add(fieldElement);
636                panel.addStyleName("clearfix");
637                                        
638                this.initWidget(panel);
639            }
640    
641            public Widget getFieldPanel(){
642                return panel;
643            }
644    
645            public String getFieldName() {
646                return fieldName;
647            }
648    
649            @Override
650            public String getSearchText() {
651                return SearchPanel.getSearchText(widget);
652            }
653        }
654    
655        private static SearchParamInfo getSearchParam(final Widget widget, String key){
656            SearchParamInfo param = new SearchParamInfo();
657            param.setKey(key);
658            if(widget instanceof HasText){
659                param.getValues().add(((HasText) widget).getText());
660            }
661            else if(widget instanceof HasValue){
662                Object value = ((HasValue) widget).getValue();
663                if(value != null){
664                //TODO need to handle date and other types here, how they are converted for search, etc
665                    if(value instanceof String){
666                        param.getValues().add((String)value);
667                    }
668                    else{
669                        param.getValues().add(value.toString());
670                        GWT.log("Fields in search probably(?) shouldnt have values other than string", null);
671                    }
672                }
673            }
674            else if (widget instanceof KSPicker){
675                    KSPicker pickerWidget = (KSPicker)widget; 
676                    String pickerValue = pickerWidget.getValue().toString();
677                    if (UtilConstants.IMPOSSIBLE_CHARACTERS.equals(pickerValue)){
678                            SuggestBox suggestBox = (SuggestBox)pickerWidget.getInputWidget();
679                            pickerValue = suggestBox.getText();
680                    }
681                    
682                param.getValues().add(pickerValue);
683            }
684            else {
685                param.getValues().add("");
686            }
687    
688            return param;
689        }
690        
691        private static String getSearchText(final Widget widget){
692            if(widget instanceof HasText){
693                return ((HasText) widget).getText();
694            }
695            else if(widget instanceof HasValue){
696                Object value = ((HasValue) widget).getValue();
697                if(value != null){
698                //TODO need to handle date and other types here, how they are converted for search, etc
699                    if(value instanceof String){
700                        return (String)value;
701                    }
702                    else{
703                        GWT.log("Fields in search probably(?) shouldnt have values other than string", null);
704                        return value.toString();
705                    }
706                }
707            }
708            else if (widget instanceof KSPicker){
709                return ((KSPicker)widget).getDisplayValue();
710            }
711            return "";
712        }
713    
714        private void showCriteriaChosen(List<HasSearchParam> fields){
715            enteredCriteriaString.clear();
716            boolean first = true;;
717            for(HasSearchParam field: fields){
718                String name = field.getFieldName();
719                String value = field.getSearchText();
720              if(!value.isEmpty()&&value.equals("$$##@@"))
721                    value = field.getSearchParam().getValues().get(0).toUpperCase();
722                if(!value.isEmpty()){
723                    HTMLPanel label = new HTMLPanel(name + ": <b>" + value + "</b>&nbsp;");
724                    if (!first) {
725                        label.addStyleName("KS-Advanced-Search-Search-Criteria-Text");
726                    }
727                    enteredCriteriaString.add(label);
728                    first = false;
729                }
730            }
731        }
732    
733        public List<String> getSelectedIds(){
734            List<String> ids = new ArrayList<String>();
735            if(table != null){
736                ids = table.getSelectedIds();
737            }
738            return ids;
739        }
740    
741        public List<SelectedResults> getSelectedValues() {
742    
743            List<SelectedResults> selectedValues = new ArrayList<SelectedResults>();
744            if (table != null) {
745                List<ResultRow> selectedRows = table.getSelectedRows();
746                for (ResultRow row : selectedRows) {
747                    String displayKey = row.getValue(activeSearchParametersWidget.getLookupMetadata().getResultDisplayKey());
748                    String returnKey = row.getValue(activeSearchParametersWidget.getLookupMetadata().getResultReturnKey());
749                    selectedValues.add(new SelectedResults(displayKey, returnKey, row));
750                    if (multiSelect == false) {
751                        break;
752                    }
753                }
754            }
755    
756            return selectedValues;
757        }
758    
759        public boolean isMultiSelect() {
760            return multiSelect;
761        }
762    
763        public void setMultiSelect(boolean multiSelect) {
764            this.multiSelect = multiSelect;
765        }
766    
767        private static class ParamListItems implements ListItems{
768    
769            private List<LookupParamMetadata> params = new ArrayList<LookupParamMetadata>();
770    
771            public ParamListItems(LookupMetadata meta){
772                params = meta.getParams();
773            }
774    
775            public ParamListItems(List<LookupParamMetadata> params){
776                this.params = params;
777            }
778            
779            @Override
780            public List<String> getAttrKeys() {
781                return new ArrayList<String>();
782            }
783    
784            @Override
785            public String getItemAttribute(String id, String attrkey) {
786                return "";
787            }
788    
789            @Override
790            public int getItemCount() {
791                return params.size();
792            }
793    
794            @Override
795            public List<String> getItemIds() {
796                List<String> ids = new ArrayList<String>();
797                for(LookupParamMetadata param: params){
798                    ids.add(param.getKey());
799                }
800                return ids;
801            }
802    
803            @Override
804            public String getItemText(String id) {
805                String itemText = id;
806                for(LookupParamMetadata param: params){
807                    if(param.getKey().equals(id)){
808                        //TODO this should be a message key
809                        itemText = param.getName();
810                        break;
811                    }
812                }
813                return itemText;
814            }
815    
816            public Widget getWidget(String id){
817                Widget w = null;
818                for(LookupParamMetadata param: params){
819                    if(param.getKey().equals(id)){
820                        w = DefaultWidgetFactory.getInstance().getWidget(param);
821                        break;
822                    }
823                }
824                return w;
825            }
826    
827            public List<LookupParamMetadata> getParams() {
828                return params;
829            }
830        }
831    
832        private String getMessage(final String msgKey) {
833            return Application.getApplicationContext().getMessage(msgKey);
834        }
835    
836        public void addLookupChangedCallback(Callback<LookupMetadata> callback) {
837            lookupChangedCallbacks.add(callback);
838        }
839        
840        //FIXME: Is an action complete callback really necessary here, couldn't this simply be a method to perform the seach action.
841        public Callback<Boolean> getActionCompleteCallback() {
842            return new Callback<Boolean>() {
843                
844                @Override
845                public void exec(Boolean result) {                               
846                    
847                    if (resultsSelected == true) {
848                            List<SelectedResults> selectedItems = getSelectedValues();
849                            if (selectedItems.isEmpty())
850                                    Window.alert("Please, select a value");
851                            else
852                            {       
853                                    for(Callback<List<SelectedResults>> callback: selectedCompleteCallbacks){
854                                            callback.exec(selectedItems);
855                                    }  
856                                    return;
857                            }       
858                    }
859                    
860                    actionCancelButtons.setButtonText(ButtonEnumerations.SearchCancelEnum.SEARCH, getMessage("select"));
861                    resultsSelected = true;
862                    
863                    SearchRequestInfo sr = getSearchRequest();
864                    // KSLAB2571 KSCM1326 - adding searchId for better message overriding.
865                    String searchId = activeSearchParametersWidget.getLookupMetadata().getId();
866                    table.performSearch(searchId, sr, activeSearchParametersWidget.getLookupMetadata().getResults(), activeSearchParametersWidget.getLookupMetadata().getResultReturnKey(), activeSearchParametersWidget.getLookupMetadata().getResultDisplayKey(), true);
867                    resultsTablePanel.setVisible(true);
868                    List<HasSearchParam> userCriteria = new ArrayList<HasSearchParam>();
869                    List<HasSearchParam> searchParams = activeSearchParametersWidget.getSearchParams();
870    
871                    //initialize search parameters if user entered values into search criteria fields in UI
872                    for(HasSearchParam field: searchParams){
873                        SearchParamInfo param = field.getSearchParam();
874                        //TODO is this null check needed here? probably. assuming string here
875                        //TODO make check more robust here/inserting params more robust
876                        //do not pass to the search parameters that are empty
877                        //FIXME hack - comparison to 'optional' - replace with check against 'optional' field and update related lookup metadata
878                        if ((param.getValues().get(0) != null) && ((param.getValues().get(0).trim().isEmpty() == false) || (param.getKey().toLowerCase().indexOf("optional") == -1))) {
879                            userCriteria.add(field);
880                        }
881                    }
882                    showCriteriaChosen(userCriteria);
883    
884                    if (hasSearchParams){
885                            //Only display modify link if there are search parametes available.
886                            if(!resultsShown){
887                                searchSelectorPanel.removeFromParent();
888                                modifySearchPanel = GWT.create(CollapsablePanel.class);
889                            modifySearchPanel.initialise(getMessage("searchPanelModifySearch"), searchSelectorPanel, false);
890                                modifySearchPanel.getLabel().addClickHandler(new ClickHandler(){
891                                    @Override
892                                    public void onClick(ClickEvent event) {
893                                        resultsTablePanel.setVisible(false);
894                                        actionCancelButtons.setButtonText(ButtonEnumerations.SearchCancelEnum.SEARCH, getActionLabel());
895                                        resultsSelected = false;
896                                    }});
897                                SearchPanel.this.layout.insert(modifySearchPanel, 0);
898                                
899                            }
900                            else{
901                                modifySearchPanel.close();
902                            }
903                    }
904                    resultsShown = true; 
905                    
906                    for(Callback<Boolean> callback: actionCompletedCallbacks){
907                        callback.exec( Boolean.valueOf(true));
908                    }                
909                }
910            };
911        }
912        
913        public SearchRequestInfo getSearchRequest() {
914            if (activeSearchParametersWidget != null) {
915                return activeSearchParametersWidget.getSearchRequest();
916            }
917            return null;
918        }    
919        
920        public void setActionCancelButtonGroup(ActionCancelGroup actionCancelButtons) {
921            this.actionCancelButtons = actionCancelButtons;
922        }
923            
924        public String getSelectedLookupName() {
925            return selectedLookupName;
926        }
927    
928        public void setSelectedLookupName(String selectedLookupName) {
929            this.selectedLookupName = selectedLookupName;
930        }   
931        
932        public void addSelectionCompleteCallback(Callback<List<SelectedResults>> callback){
933            selectedCompleteCallbacks.add(callback);
934        }   
935        
936        public void addActionCompleteCallback(Callback<Boolean> callback){
937            actionCompletedCallbacks.add(callback);
938        }
939    
940        public String getActionLabel() {
941            return actionLabel;
942        }
943    
944        public void setActionLabel(String actionLabel) {
945            if ((actionLabel != null) && (actionLabel.trim().length() > 0)) {
946                this.actionLabel = actionLabel;
947            }
948        }
949    
950    }