001    package org.gwtbootstrap3.client.ui;
002    
003    /*
004     * #%L
005     * GwtBootstrap3
006     * %%
007     * Copyright (C) 2015 GwtBootstrap3
008     * %%
009     * Licensed under the Apache License, Version 2.0 (the "License");
010     * you may not use this file except in compliance with the License.
011     * You may obtain a copy of the License at
012     * 
013     *      http://www.apache.org/licenses/LICENSE-2.0
014     * 
015     * Unless required by applicable law or agreed to in writing, software
016     * distributed under the License is distributed on an "AS IS" BASIS,
017     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018     * See the License for the specific language governing permissions and
019     * limitations under the License.
020     * #L%
021     */
022    
023    import java.util.Collection;
024    import java.util.List;
025    
026    import org.gwtbootstrap3.client.ui.base.HasAutoComplete;
027    import org.gwtbootstrap3.client.ui.base.HasId;
028    import org.gwtbootstrap3.client.ui.base.HasPlaceholder;
029    import org.gwtbootstrap3.client.ui.base.HasResponsiveness;
030    import org.gwtbootstrap3.client.ui.base.HasSize;
031    import org.gwtbootstrap3.client.ui.base.ValueBoxBase;
032    import org.gwtbootstrap3.client.ui.base.helper.StyleHelper;
033    import org.gwtbootstrap3.client.ui.base.mixin.BlankValidatorMixin;
034    import org.gwtbootstrap3.client.ui.base.mixin.EnabledMixin;
035    import org.gwtbootstrap3.client.ui.base.mixin.ErrorHandlerMixin;
036    import org.gwtbootstrap3.client.ui.base.mixin.IdMixin;
037    import org.gwtbootstrap3.client.ui.constants.DeviceSize;
038    import org.gwtbootstrap3.client.ui.constants.InputSize;
039    import org.gwtbootstrap3.client.ui.constants.Styles;
040    import org.gwtbootstrap3.client.ui.form.error.ErrorHandler;
041    import org.gwtbootstrap3.client.ui.form.error.ErrorHandlerType;
042    import org.gwtbootstrap3.client.ui.form.error.HasErrorHandler;
043    import org.gwtbootstrap3.client.ui.form.validator.HasBlankValidator;
044    import org.gwtbootstrap3.client.ui.form.validator.HasValidators;
045    import org.gwtbootstrap3.client.ui.form.validator.Validator;
046    
047    import com.google.gwt.core.client.Scheduler;
048    import com.google.gwt.core.client.Scheduler.ScheduledCommand;
049    import com.google.gwt.dom.client.Element;
050    import com.google.gwt.dom.client.Style.Display;
051    import com.google.gwt.dom.client.Style.Unit;
052    import com.google.gwt.editor.client.EditorError;
053    import com.google.gwt.editor.client.HasEditorErrors;
054    import com.google.gwt.event.logical.shared.ResizeEvent;
055    import com.google.gwt.event.logical.shared.ResizeHandler;
056    import com.google.gwt.user.client.Timer;
057    import com.google.gwt.user.client.Window;
058    import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
059    import com.google.gwt.user.client.ui.PopupPanel;
060    import com.google.gwt.user.client.ui.SuggestOracle;
061    import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
062    
063    /**
064     * Wrapper for a {@link com.google.gwt.user.client.ui.SuggestBox}.<br/>
065     * <br/>
066     * The default style is inherited from the {@link Styles#DROPDOWN_MENU}. Styling of the suggestions items need
067     * a bit of css in order to be pleasing to the eye.
068     * 
069     * <pre>
070     *  .dropdown-menu .item {
071     *      padding: 5px;
072     *  }
073     *  
074     *  .dropdown-menu .item-selected {
075     *      background-color: #eee;
076     *  }
077     * </pre>
078     * 
079     * @author Steven Jardine
080     */
081    public class SuggestBox extends com.google.gwt.user.client.ui.SuggestBox implements HasId, HasResponsiveness, HasPlaceholder,
082            HasAutoComplete, HasSize<InputSize>, HasEditorErrors<String>, HasErrorHandler, HasValidators<String>,
083            HasBlankValidator<String> {
084    
085        static class CustomSuggestionDisplay extends DefaultSuggestionDisplay {
086    
087            private ResizeHandler popupResizeHandler = null;
088    
089            public CustomSuggestionDisplay() {
090                super();
091                final PopupPanel popup = getPopupPanel();
092                popup.setStyleName(Styles.DROPDOWN_MENU);
093                popup.getElement().getStyle().setDisplay(Display.BLOCK);
094            }
095    
096            /**
097             * Resize the popup panel to the size of the suggestBox and place it below the SuggestBox. This is not
098             * ideal but works better in a mobile environment.
099             *
100             * @param box the box the SuggestBox.
101             */
102            private void resizePopup(final com.google.gwt.user.client.ui.SuggestBox box) {
103                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
104                    @Override
105                    public void execute() {
106                        Element e = box.getElement();
107                        PopupPanel panel = getPopupPanel();
108                        panel.setWidth((e.getAbsoluteRight() - e.getAbsoluteLeft() - 2) + Unit.PX.getType());
109                        panel.setPopupPosition(e.getAbsoluteLeft(), e.getAbsoluteBottom());
110                    }
111                });
112            }
113    
114            /** {@inheritDoc} */
115            @Override
116            protected void showSuggestions(final com.google.gwt.user.client.ui.SuggestBox suggestBox,
117                    final Collection<? extends Suggestion> suggestions, final boolean isDisplayStringHTML,
118                    final boolean isAutoSelectEnabled, final SuggestionCallback callback) {
119                super.showSuggestions(suggestBox, suggestions, isDisplayStringHTML, isAutoSelectEnabled, callback);
120                resizePopup(suggestBox);
121                if (popupResizeHandler == null) {
122                    popupResizeHandler = new ResizeHandler() {
123                        private Timer timer = new Timer() {
124                            public void run() {
125                                resizePopup(suggestBox);
126                            }
127                        };
128    
129                        @Override
130                        public void onResize(ResizeEvent event) {
131                            timer.schedule(250);
132                        }
133                    };
134                    Window.addResizeHandler(popupResizeHandler);
135                }
136            }
137    
138        }
139    
140        private final EnabledMixin<SuggestBox> enabledMixin = new EnabledMixin<SuggestBox>(this);
141    
142        private final ErrorHandlerMixin<String> errorHandlerMixin = new ErrorHandlerMixin<String>(this);
143    
144        private final IdMixin<SuggestBox> idMixin = new IdMixin<SuggestBox>(this);
145    
146        private final BlankValidatorMixin<SuggestBox, String> validatorMixin = new BlankValidatorMixin<SuggestBox, String>(this,
147            errorHandlerMixin.getErrorHandler());
148    
149        /**
150         * Constructor for {@link SuggestBox}. Creates a {@link MultiWordSuggestOracle} and {@link TextBox} to use
151         * with this {@link SuggestBox}.
152         */
153        public SuggestBox() {
154            this(new MultiWordSuggestOracle());
155        }
156    
157        /**
158         * Constructor for {@link SuggestBox}. Creates a {@link TextBox} to use with this {@link SuggestBox}.
159         *
160         * @param oracle the oracle for this <code>SuggestBox</code>
161         */
162        public SuggestBox(SuggestOracle oracle) {
163            this(oracle, new TextBox());
164        }
165    
166        /**
167         * Constructor for {@link SuggestBox}. The text box will be removed from it's current location and wrapped
168         * by the {@link SuggestBox}.
169         *
170         * @param oracle supplies suggestions based upon the current contents of the text widget
171         * @param box the text widget
172         */
173        public SuggestBox(SuggestOracle oracle, ValueBoxBase<String> box) {
174            this(oracle, box, new CustomSuggestionDisplay());
175        }
176    
177        /**
178         * Constructor for {@link SuggestBox}. The text box will be removed from it's current location and wrapped
179         * by the {@link SuggestBox}.
180         *
181         * @param oracle supplies suggestions based upon the current contents of the text widget
182         * @param box the text widget
183         * @param suggestDisplay the class used to display suggestions
184         */
185        public SuggestBox(SuggestOracle oracle, ValueBoxBase<String> box, SuggestionDisplay suggestDisplay) {
186            super(oracle, box, suggestDisplay);
187            setStyleName(Styles.FORM_CONTROL);
188        }
189    
190        @Override
191        public void addValidator(Validator<String> validator) {
192            validatorMixin.addValidator(validator);
193        }
194    
195        @Override
196        public boolean getAllowBlank() {
197            return validatorMixin.getAllowBlank();
198        }
199    
200        /** {@inheritDoc} */
201        @Override
202        public String getAutoComplete() {
203            return getElement().getAttribute(AUTO_COMPLETE);
204        }
205    
206        /** {@inheritDoc} */
207        @Override
208        public ErrorHandler getErrorHandler() {
209            return errorHandlerMixin.getErrorHandler();
210        }
211    
212        /** {@inheritDoc} */
213        @Override
214        public ErrorHandlerType getErrorHandlerType() {
215            return errorHandlerMixin.getErrorHandlerType();
216        }
217    
218        /**
219         * {@inheritDoc}
220         */
221        @Override
222        public String getId() {
223            return idMixin.getId();
224        }
225    
226        /** {@inheritDoc} */
227        @Override
228        public String getPlaceholder() {
229            return getElement().getAttribute(PLACEHOLDER);
230        }
231    
232        /** {@inheritDoc} */
233        @Override
234        public InputSize getSize() {
235            return InputSize.fromStyleName(getStyleName());
236        }
237    
238        @Override
239        public boolean getValidateOnBlur() {
240            return validatorMixin.getValidateOnBlur();
241        }
242    
243        /** {@inheritDoc} */
244        @Override
245        public boolean isEnabled() {
246            return enabledMixin.isEnabled();
247        }
248    
249        @Override
250        public void reset() {
251            validatorMixin.reset();
252        }
253    
254        @Override
255        public void setAllowBlank(boolean allowBlank) {
256            validatorMixin.setAllowBlank(allowBlank);
257        }
258    
259        /** {@inheritDoc} */
260        @Override
261        public void setAutoComplete(final boolean autoComplete) {
262            getElement().setAttribute(AUTO_COMPLETE, autoComplete ? ON : OFF);
263        }
264    
265        /** {@inheritDoc} */
266        @Override
267        public void setEnabled(final boolean enabled) {
268            enabledMixin.setEnabled(enabled);
269        }
270    
271        /** {@inheritDoc} */
272        @Override
273        public void setErrorHandler(ErrorHandler handler) {
274            errorHandlerMixin.setErrorHandler(handler);
275        }
276    
277        /** {@inheritDoc} */
278        @Override
279        public void setErrorHandlerType(ErrorHandlerType type) {
280            errorHandlerMixin.setErrorHandlerType(type);
281        }
282    
283        /** {@inheritDoc} */
284        @Override
285        public void setHiddenOn(final DeviceSize deviceSize) {
286            StyleHelper.setHiddenOn(this, deviceSize);
287        }
288    
289        /** {@inheritDoc} */
290        @Override
291        public void setId(final String id) {
292            idMixin.setId(id);
293        }
294    
295        /** {@inheritDoc} */
296        @Override
297        public void setPlaceholder(final String placeHolder) {
298            getElement().setAttribute(PLACEHOLDER, placeHolder != null ? placeHolder : "");
299        }
300    
301        /** {@inheritDoc} */
302        @Override
303        public void setSize(final InputSize size) {
304            StyleHelper.addUniqueEnumStyleName(this, InputSize.class, size);
305        }
306    
307        @Override
308        public void setValidateOnBlur(boolean validateOnBlur) {
309            validatorMixin.setValidateOnBlur(validateOnBlur);
310        }
311    
312        @Override
313        public void setValidators(Validator<String>... validators) {
314            validatorMixin.setValidators(validators);
315        }
316    
317        /** {@inheritDoc} */
318        @Override
319        public void setVisibleOn(final DeviceSize deviceSize) {
320            StyleHelper.setVisibleOn(this, deviceSize);
321        }
322    
323        /** {@inheritDoc} */
324        @Override
325        public void showErrors(List<EditorError> errors) {
326            errorHandlerMixin.showErrors(errors);
327        }
328    
329        @Override
330        public boolean validate() {
331            return validatorMixin.validate();
332        }
333    
334        @Override
335        public boolean validate(boolean show) {
336            return validatorMixin.validate(show);
337        }
338    
339    }