View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  
16  package org.kuali.student.common.ui.client.widgets.search;
17  
18  import java.util.ArrayList;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.kuali.student.common.ui.client.application.Application;
24  import org.kuali.student.common.ui.client.application.KSAsyncCallback;
25  import org.kuali.student.common.ui.client.configurable.mvc.WidgetConfigInfo;
26  import org.kuali.student.common.ui.client.configurable.mvc.binding.SelectItemWidgetBinding;
27  import org.kuali.student.common.ui.client.mvc.Callback;
28  import org.kuali.student.common.ui.client.mvc.HasCrossConstraints;
29  import org.kuali.student.common.ui.client.mvc.HasDataValue;
30  import org.kuali.student.common.ui.client.mvc.HasFocusLostCallbacks;
31  import org.kuali.student.common.ui.client.mvc.TranslatableValueWidget;
32  import org.kuali.student.common.ui.client.service.CachingSearchService;
33  import org.kuali.student.common.ui.client.util.SearchUtils;
34  import org.kuali.student.common.ui.client.util.SearchUtils.SearchRequestWrapper;
35  import org.kuali.student.common.ui.client.widgets.HasInputWidget;
36  import org.kuali.student.common.ui.client.widgets.KSDropDown;
37  import org.kuali.student.common.ui.client.widgets.KSErrorDialog;
38  import org.kuali.student.common.ui.client.widgets.KSLabel;
39  import org.kuali.student.common.ui.client.widgets.KSTextBox;
40  import org.kuali.student.common.ui.client.widgets.list.KSCheckBoxList;
41  import org.kuali.student.common.ui.client.widgets.list.KSLabelList;
42  import org.kuali.student.common.ui.client.widgets.list.KSRadioButtonList;
43  import org.kuali.student.common.ui.client.widgets.list.KSSelectItemWidgetAbstract;
44  import org.kuali.student.common.ui.client.widgets.list.ListItems;
45  import org.kuali.student.common.ui.client.widgets.list.SearchResultListItems;
46  import org.kuali.student.common.ui.client.widgets.list.SelectionChangeEvent;
47  import org.kuali.student.common.ui.client.widgets.list.SelectionChangeHandler;
48  import org.kuali.student.common.ui.client.widgets.suggestbox.IdableSuggestOracle.IdableSuggestion;
49  import org.kuali.student.common.ui.client.widgets.suggestbox.KSSuggestBox;
50  import org.kuali.student.common.ui.client.widgets.suggestbox.SearchSuggestOracle;
51  import org.kuali.student.r1.common.assembly.data.Data;
52  import org.kuali.student.r1.common.assembly.data.Data.DataValue;
53  import org.kuali.student.r1.common.assembly.data.Data.StringValue;
54  import org.kuali.student.r1.common.assembly.data.Data.Value;
55  import org.kuali.student.r1.common.assembly.data.LookupMetadata;
56  import org.kuali.student.r1.common.assembly.data.QueryPath;
57  import org.kuali.student.r2.core.search.dto.SearchRequestInfo;
58  import org.kuali.student.r2.core.search.dto.SearchResultInfo;
59  
60  import com.google.gwt.core.client.GWT;
61  import com.google.gwt.dom.client.Element;
62  import com.google.gwt.event.dom.client.BlurEvent;
63  import com.google.gwt.event.dom.client.BlurHandler;
64  import com.google.gwt.event.dom.client.ClickEvent;
65  import com.google.gwt.event.dom.client.ClickHandler;
66  import com.google.gwt.event.dom.client.KeyUpEvent;
67  import com.google.gwt.event.dom.client.KeyUpHandler;
68  import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
69  import com.google.gwt.event.logical.shared.ValueChangeEvent;
70  import com.google.gwt.event.logical.shared.ValueChangeHandler;
71  import com.google.gwt.event.shared.HandlerRegistration;
72  import com.google.gwt.user.client.DOM;
73  import com.google.gwt.user.client.ui.Anchor;
74  import com.google.gwt.user.client.ui.Composite;
75  import com.google.gwt.user.client.ui.FlowPanel;
76  import com.google.gwt.user.client.ui.HasText;
77  import com.google.gwt.user.client.ui.SuggestBox;
78  import com.google.gwt.user.client.ui.Widget;
79  
80  public class KSPicker extends Composite implements HasFocusLostCallbacks, HasValueChangeHandlers<String>, HasDataValue, TranslatableValueWidget, HasInputWidget, HasCrossConstraints {
81  
82      protected FlowPanel layout = new FlowPanel();
83      protected BasicWidget basicWidget;
84      private Anchor advSearchLink = new Anchor(getMessage("advSearch"));
85      private AdvancedSearchWindow advSearchWindow = null;
86      private SearchPanel searchPanel;
87      public SearchPanel getSearchPanel() {
88  		return searchPanel;
89  	}
90  
91      protected WidgetConfigInfo config;
92      private Callback<List<SelectedResults>> advancedSearchCallback;
93  	private List<Callback<SelectedResults>> basicSelectionCallbacks =
94          new ArrayList<Callback<SelectedResults>>();
95      private List<Callback<String>> basicSelectionTextChangeCallbacks =
96          new ArrayList<Callback<String>>();
97      private CachingSearchService cachingSearchService = CachingSearchService.getSearchService();
98          
99      protected SearchRequestWrapper searchRequestWrapper = new SearchRequestWrapper();
100         
101     public KSPicker() {}
102 
103     public void init(WidgetConfigInfo config) {
104         this.config = config;
105 		init(config.lookupMeta, config.additionalLookups);
106 	}
107 
108     @Override
109     public Widget getInputWidget(){
110     	if(basicWidget != null){
111     		return basicWidget.get();
112     	}
113     	return null;
114 
115     }
116 
117     public void init(LookupMetadata inLookupMetadata, List<LookupMetadata> additionalLookupMetadata) {
118     	this.initWidget(layout);
119         if (inLookupMetadata == null) {
120             KSErrorDialog.show(new Throwable(getMessage("invalidLookupConfig")));
121             return;
122         }
123 
124         if(config == null) {
125             config = new WidgetConfigInfo();
126         }
127 
128         setupBasicSearch(inLookupMetadata);
129         setupAdvancedSearch(additionalLookupMetadata);
130     }
131 
132     private static String getMessage(final String messageId) {
133         return Application.getApplicationContext().getMessage(messageId);
134     }
135 
136     private void setupBasicSearch(LookupMetadata inLookupMetadata) {
137 
138     	//setup initial search widget such as suggest box, drop down etc.
139     	if (inLookupMetadata.getWidget() != null){
140 	    	switch (inLookupMetadata.getWidget()){
141 	    		case SUGGEST_BOX:
142 	    			setupSuggestBox(inLookupMetadata);
143 	    			break;
144 	    		case DROP_DOWN:
145 	    		case CHECKBOX_LIST:
146 	    		case RADIO:
147 	    			setupListWidget(inLookupMetadata);
148 	    			break;
149 	    		case NO_WIDGET:
150 	    			if(getMessage(inLookupMetadata.getId()+"-name")!=null){
151 	    			   advSearchLink.setText(getMessage(inLookupMetadata.getId()+"-name"));
152 	    			}   
153 	    			else
154 	    			if ((inLookupMetadata.getName() != null) && (inLookupMetadata.getName().trim().length() > 0)) {
155 	                    advSearchLink.setText(inLookupMetadata.getName().trim());
156 	                }
157 	                basicWidget = new BasicWidget(new SelectionContainerWidget());
158 	                break;
159 	    		default:
160 	    			setupDefaultWidget(inLookupMetadata);
161 	    	}
162     	} else {
163 			setupDefaultWidget(inLookupMetadata);
164     	}
165     }
166 
167     private void setupDefaultWidget(LookupMetadata inLookupMetadata){
168         basicWidget = new BasicWidget(config != null && config.canEdit ? new KSTextBox() : new KSLabel());
169         GWT.log("KSTextBox for " + inLookupMetadata.getSearchTypeId(), null);
170         layout.add(basicWidget.get());
171     }
172 
173     private void setupSuggestBox(LookupMetadata inLookupMetadata){
174         if(config.canEdit) {
175                 final SearchSuggestOracle orgSearchOracle = new SearchSuggestOracle(inLookupMetadata);
176                 final KSSuggestBox suggestBox = new KSSuggestBox(orgSearchOracle, true);
177                 suggestBox.addKeyUpHandler(new KeyUpHandler() {
178                     @Override
179                     public void onKeyUp(KeyUpEvent event) {
180                         for(Callback<String> basicSelectionTextChangeCallback: basicSelectionTextChangeCallbacks){
181                             basicSelectionTextChangeCallback.exec("Text Changed");
182                         }
183                     }
184                 });
185                 suggestBox.addSelectionChangeHandler(new SelectionChangeHandler(){
186 
187                     @Override
188                     public void onSelectionChange(SelectionChangeEvent event) {
189                     	IdableSuggestion currentSuggestion = suggestBox.getSelectedSuggestion();
190                         SelectedResults selectedResults = new SelectedResults(
191                                 currentSuggestion.getReplacementString(), currentSuggestion.getId());
192                         for(Callback<SelectedResults> basicSelectionCallback: basicSelectionCallbacks){
193                             basicSelectionCallback.exec(selectedResults);
194                         }
195                     }
196                 });
197                 basicWidget = new BasicWidget(suggestBox);
198                 ((KSSuggestBox) basicWidget.get()).setAutoSelectEnabled(false);
199                 orgSearchOracle.setTextWidget(((SuggestBox) basicWidget.get()).getTextBox());
200         } else {
201             basicWidget = new BasicWidget(new KSLabel());
202         }
203         layout.add(basicWidget.get());
204     }
205 
206     protected void setupListWidget(LookupMetadata inLookupMetadata) {
207 
208         //FIXME should we search on values to populate drop down here or later when user will access the screen?
209         if(config.canEdit) {
210 
211             KSSelectItemWidgetAbstract listItemWidget = null;
212             switch (inLookupMetadata.getWidget()){
213             	case DROP_DOWN:
214             		listItemWidget = new KSDropDown();
215             		break;
216             	case CHECKBOX_LIST:
217             		listItemWidget = new KSCheckBoxList();
218             		((KSCheckBoxList)listItemWidget).setIgnoreMultipleAttributes(true);
219             		break;
220             	case RADIO:
221             		listItemWidget = new KSRadioButtonList();
222             		break;
223             }
224             basicWidget = new BasicWidget(listItemWidget);
225             SearchUtils.initializeSearchRequest(inLookupMetadata, searchRequestWrapper);
226             if(!searchRequestWrapper.isDeferSearch()) populateListWidget(searchRequestWrapper.getSearchRequest());
227         } else {
228         	if (inLookupMetadata.getWidget() == LookupMetadata.Widget.DROP_DOWN || inLookupMetadata.getWidget() == LookupMetadata.Widget.RADIO){
229                 basicWidget = new BasicWidget(new KSLabel());
230         	} else {
231         		//FIXME: This method of creating read is very inefficient, need better solution
232         		basicWidget = new BasicWidget(new KSLabelList());
233                 SearchUtils.initializeSearchRequest(inLookupMetadata, searchRequestWrapper);
234                 if(!searchRequestWrapper.isDeferSearch()) populateListWidget(searchRequestWrapper.getSearchRequest());
235         	}
236             layout.add(basicWidget.get());
237         }
238     }
239 
240     private void setupAdvancedSearch(List<LookupMetadata> additionalLookupMetadata) {
241 
242         int firstLookUpData = 0;
243         //setup advanced search widget such as advanced search box, browse hierarchy search box etc.
244         List<LookupMetadata> advancedLightboxLookupdata = getLookupMetadataBasedOnWidget(additionalLookupMetadata, LookupMetadata.Widget.ADVANCED_LIGHTBOX);
245         if ((advancedLightboxLookupdata != null && advancedLightboxLookupdata.size() > 0) && config.canEdit) {
246 
247         	//Title for advanced search window
248         	String advSearchTitle;
249         	advSearchTitle = getMessage(advancedLightboxLookupdata.get(firstLookUpData).getId()+"-title");
250         	if (basicWidget.get() instanceof SelectionContainerWidget){
251         		if(advSearchTitle==null)
252         		   advSearchTitle = advancedLightboxLookupdata.get(firstLookUpData).getTitle();
253         	} else {
254         		if(advSearchTitle==null)
255         		   advSearchTitle = "Advanced Search: " + advancedLightboxLookupdata.get(firstLookUpData).getTitle();
256         		else
257         		   advSearchTitle = "Advanced Search: " + advSearchTitle; 
258         	}
259         	
260             //for multiple searches, show a drop down for user to select from
261             if (advancedLightboxLookupdata.size() == 1) {
262                 String actionLabel = advancedLightboxLookupdata.get(firstLookUpData).getWidgetOptionValue(LookupMetadata.WidgetOption.ADVANCED_LIGHTBOX_ACTION_LABEL);
263                 searchPanel = new SearchPanel(advancedLightboxLookupdata.get(firstLookUpData));
264                 searchPanel.setActionLabel(actionLabel);
265                 advSearchWindow = new AdvancedSearchWindow(advSearchTitle, searchPanel);
266             } else {
267                 searchPanel = new SearchPanel(advancedLightboxLookupdata);
268                 advSearchWindow = new AdvancedSearchWindow(advSearchTitle, searchPanel);
269                 searchPanel.addLookupChangedCallback(new Callback<LookupMetadata>() {
270                     @Override
271                     public void exec(LookupMetadata selectedLookup) {
272                         String actionLabel = (selectedLookup == null)? null : selectedLookup
273                                 .getWidgetOptionValue(LookupMetadata.WidgetOption.ADVANCED_LIGHTBOX_ACTION_LABEL);
274                         searchPanel.setActionLabel(actionLabel);
275                     }
276                 });
277                 LookupMetadata initialLookupMetaData = advancedLightboxLookupdata.get(firstLookUpData);
278                 String actionLabel = (initialLookupMetaData == null)? null : initialLookupMetaData
279                         .getWidgetOptionValue(LookupMetadata.WidgetOption.ADVANCED_LIGHTBOX_ACTION_LABEL);
280                 searchPanel.setActionLabel(actionLabel);
281             }
282             searchPanel.setMultiSelect(true);
283 
284             String previewMode = additionalLookupMetadata.get(firstLookUpData).getWidgetOptionValue(LookupMetadata.WidgetOption.ADVANCED_LIGHTBOX_PREVIEW_MODE);
285             if (previewMode != null && previewMode.equals("true")) {
286                 searchPanel.setActionLabel("Preview");
287             }
288 
289             searchPanel.addSelectionCompleteCallback(new Callback<List<SelectedResults>>() {
290                 public void exec(List<SelectedResults> results) {
291                 	if (results != null && results.size() > 0) {
292 	                    if (advancedSearchCallback != null) {
293 	                        advancedSearchCallback.exec(results);
294 	                    }
295 	                    else {
296 	                        basicWidget.setResults(results);
297 	                    }
298                         advSearchWindow.hide();
299                 	}
300                 }
301             });
302 
303             advSearchLink.addClickHandler(new ClickHandler(){
304                 @Override
305                 public void onClick(ClickEvent event) {
306                    if(advSearchWindow != null){
307                        advSearchWindow.show();
308                    }
309                 }
310             });
311             layout.add(advSearchLink);
312         }
313     }
314 
315     protected void populateListWidget(SearchRequestInfo sr) {
316         
317         cachingSearchService.search(sr, new KSAsyncCallback<SearchResultInfo>(){
318 
319             @Override
320             public void onSuccess(SearchResultInfo results) {
321                 if(results != null){
322                     ((KSSelectItemWidgetAbstract)basicWidget.get()).setListItems(new SearchResultListItems(results.getRows(), config.lookupMeta));
323                 } else {
324                     ((KSSelectItemWidgetAbstract)basicWidget.get()).setListItems(new SearchResultListItems(null, config.lookupMeta));
325                     //FIXME is this configuration error?
326                 }
327                 //((KSDropDown)basicWidget.get()).processOutstandingCallback();  //due to possible race condition
328                 ((KSSelectItemWidgetAbstract)basicWidget.get()).setInitialized(true);
329                 layout.add(basicWidget.get());
330             }
331         });
332     }
333 
334 	private List<LookupMetadata> getLookupMetadataBasedOnWidget(List<LookupMetadata> additionalLookupMetadata, LookupMetadata.Widget widgetType) {
335 
336 	    //lookup does not need to have additional lookup e.g. if the lookup is for suggest box within advanced search lightbox
337 	    if (additionalLookupMetadata == null) {
338 	        return null;
339 	    }
340 
341     	List<LookupMetadata> lookups = new ArrayList<LookupMetadata>();
342     	for (LookupMetadata addLookupData : additionalLookupMetadata) {
343     		if (addLookupData.getWidget() == widgetType) {
344     			lookups.add(addLookupData);
345     		}
346     	}
347     	return (lookups.size() > 0 ? lookups : null);
348     }
349 
350     public class BasicWidget implements HasDataValue, HasFocusLostCallbacks, TranslatableValueWidget {
351 		private Widget basicWidget;
352 
353 		public BasicWidget(Widget basicWidget){
354 			this.basicWidget = basicWidget;
355             initAccessibility();
356 		}
357 
358         private void initAccessibility() {
359             Element element = basicWidget.getElement();
360             element.setAttribute("role", "combobox");
361             element.setAttribute("aria-autocomplete", "list");
362             element.setAttribute("aria-haspopup", "true");
363         }
364 
365         public void setResults(List<SelectedResults> results) {
366             int firstValue = 0;
367             if (results != null && results.size() > 0) {
368                 if (basicWidget instanceof KSTextBox) {
369                     ((KSTextBox)basicWidget).setText(results.get(firstValue).getDisplayKey());  //FIXME: what about the result id?
370                 } else if (basicWidget.getClass().getName().contains("ContainerWidget")) {
371                     List<String> selections = new ArrayList<String>();
372                     for (SelectedResults result: results) {
373                         //selections.add(result.getDisplayKey());						  //FIXME: what about the result ids?
374                         selections.add(result.getReturnKey());                            //we don't need display key, at least for now
375                     }
376                     ((SelectionContainerWidget) basicWidget).setSelections(selections);
377                 } else if (basicWidget instanceof KSSuggestBox) {
378                     IdableSuggestion theSuggestion = new IdableSuggestion();
379                     theSuggestion.setReplacementString(results.get(firstValue).getDisplayKey());
380                     theSuggestion.setId(results.get(firstValue).getReturnKey());
381                     theSuggestion.setAttrMap(results.get(firstValue).getResultRow().getColumnValues());
382                     ((KSSuggestBox) basicWidget).setValue(theSuggestion);
383                 }
384             }
385 		}
386 		public void clear() {
387 		    if(basicWidget instanceof KSTextBox) {
388                 ((KSTextBox)basicWidget).setText(null);
389             } else if(basicWidget instanceof KSSuggestBox) {
390                 ((KSSuggestBox) basicWidget).setValue((String)null);
391             } else if(basicWidget instanceof KSLabel) {
392                 ((KSLabel)basicWidget).setText(null);
393             } else if(basicWidget instanceof KSDropDown) {
394                 ((KSDropDown)basicWidget).clear();
395             } else {
396                 ((KSSuggestBox) basicWidget).setValue(null, false);
397             }
398 		}
399 		public void setValue(final String id, String translation) {
400 			if(basicWidget instanceof KSTextBox) {
401 				((KSTextBox)basicWidget).setText(translation);
402 			} else if(basicWidget instanceof KSSuggestBox) {
403 				((KSSuggestBox) basicWidget).setValue(id, translation);
404 			} else if(basicWidget instanceof KSLabel) {
405 			    ((KSLabel)basicWidget).setText(translation);
406 			} else if(basicWidget instanceof KSSelectItemWidgetAbstract) {
407 			    ListItems searchResults = ((KSSelectItemWidgetAbstract)basicWidget).getListItems();
408 			    if (searchResults != null) {
409 			        ((KSSelectItemWidgetAbstract)basicWidget).selectItem(id);
410 			    }
411 			    else {
412                     ((KSSelectItemWidgetAbstract)basicWidget).addWidgetReadyCallback(new Callback<Widget>() {
413                         @Override
414                         public void exec(Widget widget) {
415                             final ListItems searchResults = ((KSSelectItemWidgetAbstract)widget).getListItems();
416 
417                             if(id != null){
418                                 for (String itemId : searchResults.getItemIds()) {
419                                     if (itemId.equals(id.trim())) {
420                                         ((KSSelectItemWidgetAbstract)basicWidget).selectItem(itemId);
421                                         break;
422                                     }
423                                 }
424                             } else {
425                                 ((KSSelectItemWidgetAbstract)basicWidget).clear();
426                             }
427                         }
428                     });
429                 }
430 
431             } else {
432 				((KSSuggestBox) basicWidget).setValue("", true);
433 			}
434 		}
435 
436 		public void setValue(final Value value, boolean fireEvents) {
437 			if (basicWidget instanceof KSTextBox || basicWidget instanceof KSLabel) {
438 				if(value != null){
439 					((HasText)basicWidget).setText((String) value.get());
440 				}
441 				else{
442 					((HasText)basicWidget).setText("");
443 				}
444 			} else if (basicWidget instanceof KSSuggestBox) {
445 				//Do check here
446 				if(value != null){
447 					if(!config.isRepeating){
448 						((KSSuggestBox) basicWidget).setValue((String)value.get(), fireEvents);
449 					}
450 					else{
451 						DataValue dataValue = (DataValue)value;
452 						Data d = dataValue.get();
453 						QueryPath path = QueryPath.parse("0");
454 						String v = d.query(path);
455 						((KSSuggestBox) basicWidget).setValue((String)v, fireEvents);
456 					}
457 				}
458 				else{
459 					((KSSuggestBox) basicWidget).setValue("", fireEvents);
460 				}
461 			} else if(basicWidget instanceof KSSelectItemWidgetAbstract) {
462 				SelectItemWidgetBinding.INSTANCE.setWidgetValue((KSSelectItemWidgetAbstract)basicWidget, value);
463             }
464 		}
465 		public String getDisplayValue() {
466 		    String result = "";
467 		    if (basicWidget instanceof KSTextBox) {
468                 result = ((KSTextBox)basicWidget).getText();
469             } else if (basicWidget instanceof KSSuggestBox) {
470                 IdableSuggestion suggestion = ((KSSuggestBox)basicWidget).getCurrentSuggestion();
471                 if(suggestion != null) {
472                     result = suggestion.getReplacementString();
473                 }
474             }  else if (basicWidget instanceof KSDropDown) {
475                 KSDropDown dropDown = (KSDropDown)basicWidget;
476                 StringValue value = new StringValue(((KSDropDown) basicWidget).getSelectedItem());
477                 String itemId = dropDown.getSelectedItem();
478 
479                 if(itemId != null && !itemId.isEmpty()) {
480                     result = dropDown.getListItems().getItemText(itemId);
481                 }
482             }
483 		    return result;
484 		}
485 		public Value getValue() {
486 			if (basicWidget instanceof KSTextBox) {
487 				StringValue value = new StringValue(((KSTextBox)basicWidget).getText());
488 				return value;
489 			} else if (basicWidget instanceof KSSuggestBox) {
490 				//Do check here
491 				if(!config.isRepeating){
492 					StringValue value = new StringValue(((KSSuggestBox) basicWidget).getValue());
493 					return value;
494 				}
495 				else{
496 					Data data = new Data();
497 					data.set(new Data.IntegerKey(0),((KSSuggestBox) basicWidget).getValue());
498 					DataValue value = new DataValue(data);
499 					return value;
500 				}
501 			}  else if (basicWidget instanceof KSSelectItemWidgetAbstract) {
502 				return SelectItemWidgetBinding.INSTANCE.getWidgetValue((KSSelectItemWidgetAbstract)basicWidget);
503 			}
504 
505 			return null;
506 		}
507 
508 		public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
509 			if (basicWidget instanceof KSTextBox) {
510 				return ((KSTextBox)basicWidget).addValueChangeHandler(handler);
511 			} else if (basicWidget instanceof KSSuggestBox) {
512 				return ((KSSuggestBox) basicWidget).addValueChangeHandler(handler);
513 			}
514 			return null;
515 		}
516 
517         public void addValuesChangeHandler(ValueChangeHandler<List<String>> handler) {
518             if (basicWidget.getClass().getName().contains("ContainerWidget")) {
519                 ((SelectionContainerWidget) basicWidget).addValueChangeHandler(handler);
520             }
521         }
522 		public void addSelectionChangeHandler(SelectionChangeHandler handler) {
523 		    if (basicWidget instanceof KSSelectItemWidgetAbstract)  {
524 				((KSSelectItemWidgetAbstract) basicWidget).addSelectionChangeHandler(handler);
525 			}else if(config!=null&&!config.isRepeating&&basicWidget instanceof KSSuggestBox){
526 				((KSSuggestBox) basicWidget).addSelectionChangeHandler(handler);
527 			}
528 		}
529 
530 		@Override
531 		public void addValueChangeCallback(final Callback<Value> callback) {
532 			ValueChangeHandler<String> handler = new ValueChangeHandler<String>(){
533 				@Override
534 				public void onValueChange(ValueChangeEvent<String> event) {
535 					StringValue value = new StringValue(event.getValue());
536 					callback.exec(value);
537 				}
538 			};
539 			addValueChangeHandler(handler);
540 		}
541 
542 		@Override
543 		public void addFocusLostCallback(final Callback<Boolean> callback) {
544 			if (basicWidget instanceof KSTextBox) {
545 				((KSTextBox)basicWidget).addBlurHandler(new BlurHandler(){
546 					@Override
547 					public void onBlur(BlurEvent event) {
548 						callback.exec(true);
549 
550 					}
551 				});
552 			} else if (basicWidget instanceof KSSuggestBox) {
553 				((KSSuggestBox) basicWidget).getTextBox().addBlurHandler(new BlurHandler(){
554                     @Override
555                     public void onBlur(BlurEvent event) {
556                     	//Don't blur the widget if the suggestion list is showing
557                     	if(!((KSSuggestBox) basicWidget).isSuggestionListShowing()){
558                             callback.exec(true);
559                     	}
560                     }
561 				});
562 			} else if (basicWidget instanceof KSSelectItemWidgetAbstract) {
563                 ((KSSelectItemWidgetAbstract) basicWidget).addBlurHandler(new BlurHandler(){
564                     @Override
565                     public void onBlur(BlurEvent event) {
566                         callback.exec(true);
567                     }
568                 });
569             }
570 		}
571 
572         @Override
573         public void setValue(Value value) {
574             this.setValue(value, true);
575         }
576 
577         public Widget get() {
578             return basicWidget;
579         }
580 
581         @Override
582         public void setValue(final Map<String, String> translations) {
583         	//TODO: Reviewed in M6 cleanup, unknown:  Update to also work with a KSLabelList that doesn't require pre-population of all list items
584         	if (basicWidget instanceof KSSelectItemWidgetAbstract){
585         		Callback<Widget> widgetReadyCallback = new Callback<Widget>(){
586 					public void exec(Widget widget) {
587 						((KSSelectItemWidgetAbstract)widget).clear();
588 		        		for (String id:translations.keySet()){
589 		        			((KSSelectItemWidgetAbstract)widget).selectItem(id);
590 		        		}
591 					}
592         		};
593         		if (!((KSSelectItemWidgetAbstract)basicWidget).isInitialized()){
594         			((KSSelectItemWidgetAbstract)basicWidget).addWidgetReadyCallback(widgetReadyCallback);
595         		} else{
596         			widgetReadyCallback.exec(basicWidget);
597         		}
598         	} else {
599         		GWT.log("Basic picker widget not coded to handle multiple translations", null);
600         	}
601         }
602 
603     }
604 
605     /**
606      * An empty widget used to contain selections from an Advanced Search window, when NO_WIDGET has been
607      * specified for initial lookup definition. This is mostly used for creating a link to advanced search
608      * window.
609      * 
610      */
611     private class SelectionContainerWidget extends Widget implements HasValueChangeHandlers<List<String>> {
612     	private List<String> selections = new ArrayList<String>();
613 
614     	public SelectionContainerWidget(){
615     		this.setElement(DOM.createSpan());
616     	}
617 
618 		public List<String> getSelections() {
619 			return selections;
620 		}
621 
622 		public void setSelections(List<String> selections) {
623 			this.selections = selections;
624 			ValueChangeEvent.fire(this, this.selections);
625 		}
626 
627 		@Override
628 		public HandlerRegistration addValueChangeHandler(ValueChangeHandler<List<String>> handler) {
629 			return super.addHandler(handler, ValueChangeEvent.getType());
630 		}
631     }
632 
633 
634     public AdvancedSearchWindow getSearchWindow(){
635         return advSearchWindow;
636     }
637 
638     public void addBasicSelectionCompletedCallback(Callback<SelectedResults> callback) {
639         basicSelectionCallbacks.add(callback);
640     }
641 
642     public void addBasicSelectionTextChangeCallback(Callback<String> callback) {
643         basicSelectionTextChangeCallbacks.add(callback);
644     }
645 
646 	@Override
647 	public void addValueChangeCallback(Callback<Value> callback) {
648 		basicWidget.addValueChangeCallback(callback);
649 	}
650 
651 	@Override
652 	public void setValue(Value value) {
653 		setValue(value, true);
654 	}
655 
656     public void setValue(String value){
657         basicWidget.setValue(new StringValue(value));
658     }
659 
660 	public void setValue(Value value, boolean fireEvents) {
661 		//suggest.reset();
662 		basicWidget.setValue(value, fireEvents);
663 	}
664 	public void clear() {
665 	    basicWidget.clear();
666 	}
667 
668     @Override
669     public void setValue(String id, String translation) {
670         basicWidget.setValue(id, translation);
671     }
672 
673     @Override
674     public Value getValue() {
675         return basicWidget.getValue();
676     }
677 	public String getDisplayValue() {
678 	    return basicWidget.getDisplayValue();
679 	}
680 	@Override
681 	public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
682 		return basicWidget.addValueChangeHandler(handler);
683 	}
684 
685 	public void addValuesChangeHandler(ValueChangeHandler<List<String>> handler) {
686         if(basicWidget != null)
687 		basicWidget.addValuesChangeHandler(handler);
688 	}
689 
690 	public void addSelectionChangeHandler(SelectionChangeHandler handler) {
691         if(basicWidget != null)
692 		basicWidget.addSelectionChangeHandler(handler);
693 	}
694 
695 	
696 	@Override
697 	public void addFocusLostCallback(Callback<Boolean> callback) {
698 		basicWidget.addFocusLostCallback(callback);
699 	}
700 
701     public void setAdvancedSearchCallback(Callback<List<SelectedResults>> advancedSearchCallback) {
702         this.advancedSearchCallback = advancedSearchCallback;
703     }
704 
705     @Override
706     public void setValue(Map<String, String> translations) {
707         basicWidget.setValue(translations);
708     }
709 
710 	@Override
711     public HashSet<String> getCrossConstraints() {
712 		return searchRequestWrapper.getCrossConstraints();
713 	}
714 
715 	@Override
716 	public void reprocessWithUpdatedConstraints() {
717         SearchUtils.initializeSearchRequest(config.lookupMeta, searchRequestWrapper);
718         populateListWidget(searchRequestWrapper.getSearchRequest());
719 	}
720 
721 	public Callback<List<SelectedResults>> getAdvancedSearchCallback() {
722 		return advancedSearchCallback;
723 	}
724 
725     @Override
726     protected void onEnsureDebugId(String baseID) {
727         super.onEnsureDebugId(baseID);
728         Widget basicInputWidget = getInputWidget();
729         if (basicInputWidget != null) {
730             basicInputWidget.ensureDebugId(baseID + "-KSPicker-widget");
731         }
732         advSearchLink.ensureDebugId(baseID + "-Advanced-Search-anchor");
733     }
734 
735 }