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