001    package org.gwtbootstrap3.extras.select.client.ui;
002    
003    /*
004     * #%L
005     * GwtBootstrap3
006     * %%
007     * Copyright (C) 2013 - 2014 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 com.google.gwt.core.client.ScriptInjector;
024    import com.google.gwt.core.client.JavaScriptObject;
025    import com.google.gwt.core.client.JsArrayString;
026    import com.google.gwt.core.client.Scheduler;
027    import com.google.gwt.dom.client.Document;
028    import com.google.gwt.dom.client.Element;
029    import com.google.gwt.dom.client.SelectElement;
030    import com.google.gwt.event.dom.client.ChangeEvent;
031    import com.google.gwt.event.dom.client.ChangeHandler;
032    import com.google.gwt.event.shared.HandlerRegistration;
033    import com.google.gwt.user.client.Command;
034    import com.google.gwt.user.client.ui.Focusable;
035    import com.google.gwt.user.client.ui.HasEnabled;
036    import com.google.gwt.user.client.ui.HasName;
037    
038    import org.gwtbootstrap3.client.ui.base.ComplexWidget;
039    import org.gwtbootstrap3.client.ui.base.helper.StyleHelper;
040    import org.gwtbootstrap3.client.ui.base.mixin.AttributeMixin;
041    import org.gwtbootstrap3.client.ui.base.mixin.EnabledMixin;
042    import org.gwtbootstrap3.client.ui.base.mixin.FocusableMixin;
043    import org.gwtbootstrap3.client.ui.constants.ButtonType;
044    import org.gwtbootstrap3.extras.select.client.constants.Styles;
045    import org.gwtbootstrap3.extras.select.client.constants.SelectLanguage;
046    import org.gwtbootstrap3.extras.select.client.ui.interfaces.HasLanguage;
047    
048    import java.util.ArrayList;
049    import java.util.List;
050    
051    import static org.gwtbootstrap3.extras.select.client.constants.DataAttributes.*;
052    
053    /**
054     * @author godi
055     */
056    public class Select extends ComplexWidget implements Focusable, HasEnabled, HasLanguage, HasName {
057        private static final String REFRESH = "refresh";
058        private static final String RENDER = "render";
059        private static final String SHOW = "show";
060        private static final String HIDE = "hide";
061        private static final String SELECT_ALL = "selectAll";
062        private static final String DESELECT_ALL = "deselectAll";
063        private static final String TRUE = "true";
064        private SelectLanguage language = SelectLanguage.EN;
065    
066        private final SelectElement selectElement;
067        private final AttributeMixin<Select> attributeMixin = new AttributeMixin<Select>(this);
068        private final FocusableMixin<Select> focusableMixin = new FocusableMixin<Select>(this);
069        private final EnabledMixin<Select> enabledMixin = new EnabledMixin<Select>(this);
070    
071        public Select() {
072            selectElement = Document.get().createSelectElement();
073            setElement(selectElement);
074            setStyleName(Styles.BOOTSTRAP_SELECT);
075            addStyleName(org.gwtbootstrap3.client.ui.constants.Styles.FORM_CONTROL);
076        }
077    
078        @Override
079        protected void onLoad() {
080            super.onLoad();
081    
082            initialize();
083        }
084    
085        public HandlerRegistration addChangeHandler(final ChangeHandler handler) {
086            return addDomHandler(handler, ChangeEvent.getType());
087        }
088    
089        public void setHeader(final String header) {
090            attributeMixin.setAttribute(DATA_HEADER, header);
091        }
092    
093        public String getHeader() {
094            return attributeMixin.getAttribute(DATA_HEADER);
095        }
096    
097        public void clearHeader() {
098            attributeMixin.removeAttribute(DATA_HEADER);
099        }
100    
101        public void setShowSubtext(final boolean showSubtext) {
102            if (showSubtext) {
103                attributeMixin.setAttribute(DATA_SHOW_SUBTEXT, Boolean.toString(true));
104            } else {
105                attributeMixin.removeAttribute(DATA_SHOW_SUBTEXT);
106            }
107        }
108    
109        public boolean getShowSubtext() {
110            return attributeMixin.getAttribute(DATA_SHOW_SUBTEXT) != null;
111        }
112    
113        /**
114         * {@inheritDoc}
115         */
116        @Override
117        public void setEnabled(final boolean enabled) {
118            enabledMixin.setEnabled(enabled);
119        }
120    
121        /**
122         * {@inheritDoc}
123         */
124        @Override
125        public boolean isEnabled() {
126            return enabledMixin.isEnabled();
127        }
128    
129        @Override
130        public void setLanguage(final SelectLanguage language) {
131            this.language = language;
132    
133            // Inject the JS for the language
134            if (language.getJs() != null) {
135                ScriptInjector.fromString(language.getJs().getText())
136                        .setWindow(ScriptInjector.TOP_WINDOW).inject();
137            }
138        }
139    
140        @Override
141        public SelectLanguage getLanguage() {
142            return language;
143        }
144    
145        /**
146         * Sets the number of lines to show before scrolling
147         * <p/>
148         * Values:
149         * <p/>
150         * (1) auto - (default) shows them all
151         * (2) x - shows x number of entries before scrolling
152         *
153         * @param size String visible size
154         */
155        public void setVisibleSize(final String size) {
156            attributeMixin.setAttribute(DATA_SIZE, size);
157        }
158    
159        public String getVisibleSize() {
160            return attributeMixin.getAttribute(DATA_SIZE);
161        }
162    
163        public void clearVisibleSize() {
164            attributeMixin.removeAttribute(DATA_SIZE);
165        }
166    
167        /**
168         * Sets the width of the select
169         * <p/>
170         * !! use 'auto' to automatically adjust the width of the select to its widest option, or just use
171         * specific values (50px, 50%, etc...)
172         */
173        @Override
174        public void setWidth(final String width) {
175            attributeMixin.setAttribute(DATA_WIDTH, width);
176        }
177    
178        public String getWidth() {
179            return attributeMixin.getAttribute(DATA_WIDTH);
180        }
181    
182            /**
183         * Sets the Max Number of selectable option of the select
184         * <p/>
185         */
186        public void setMaxOption(final String maxOption) {
187            attributeMixin.setAttribute(DATA_MAX_OPTION, maxOption);
188        }
189    
190        public String getMaxOption() {
191            return attributeMixin.getAttribute(DATA_MAX_OPTION);
192        }
193    
194        public void setShowMenuArrow(final boolean showMenuArrow) {
195            if (showMenuArrow) {
196                addStyleName(Styles.SHOW_MENU_ARROW);
197            } else {
198                removeStyleName(Styles.SHOW_MENU_ARROW);
199            }
200        }
201    
202        public boolean getShowMenuArrow() {
203            return StyleHelper.containsStyle(getStyleName(), Styles.SHOW_MENU_ARROW);
204        }
205    
206        public void setShowTick(final boolean showTick) {
207            if (showTick) {
208                addStyleName(Styles.SHOW_TICK);
209            } else {
210                removeStyleName(Styles.SHOW_TICK);
211            }
212        }
213    
214        public boolean getShowTick() {
215            return StyleHelper.containsStyle(getStyleName(), Styles.SHOW_TICK);
216        }
217    
218        /**
219         * Supported Values:
220         * <p/>
221         * (1) values - default, comma delimited list
222         * (2) count - If one item is selected, then the value is shown, if more than one is selected then the number of selected items is displayed, eg 2 of 6 selected
223         * (3) count greater than x - Where X is the number of items selected when the display format changes from values to count
224         *
225         * @param format selected text format
226         */
227        public void setSelectedTextFormat(final String format) {
228            attributeMixin.setAttribute(DATA_SELECTED_TEXT_FORMAT, format);
229        }
230    
231        public String getSelectedTextFormat() {
232            return attributeMixin.getAttribute(DATA_SELECTED_TEXT_FORMAT);
233        }
234    
235        public void clearSelectedTextFormat() {
236            attributeMixin.removeAttribute(DATA_SELECTED_TEXT_FORMAT);
237        }
238    
239        public void setMultiple(final boolean multiple) {
240            if (multiple) {
241                attributeMixin.setAttribute(MULTIPLE, "");
242            } else {
243                attributeMixin.removeAttribute(MULTIPLE);
244            }
245        }
246    
247        public boolean isMultiple() {
248            return attributeMixin.hasAttribute(MULTIPLE);
249        }
250    
251      public void setMobile(final boolean mobile) {
252            if (mobile) {
253                attributeMixin.setAttribute(DATA_MOBILE, Boolean.toString(true));
254            } else {
255                attributeMixin.removeAttribute(DATA_MOBILE);
256            }
257        }
258    
259        public boolean isMobile() {
260            return attributeMixin.hasAttribute(DATA_MOBILE);
261        }
262    
263        public void setLiveSearch(final boolean liveSearch) {
264            if (liveSearch) {
265                attributeMixin.setAttribute(DATA_LIVE_SEARCH, Boolean.toString(true));
266            } else {
267                attributeMixin.removeAttribute(DATA_LIVE_SEARCH);
268            }
269        }
270    
271        public boolean isLiveSearch() {
272            return !attributeMixin.getAttribute(DATA_LIVE_SEARCH).isEmpty() || attributeMixin.getAttribute(DATA_LIVE_SEARCH).equals(TRUE);
273        }
274    
275        public void setStyle(final ButtonType style) {
276            attributeMixin.setAttribute(DATA_STYLE, style.getCssName());
277        }
278    
279        public void clearStyle() {
280            attributeMixin.removeAttribute(DATA_STYLE);
281        }
282    
283        public String getStyle() {
284            return attributeMixin.getAttribute(DATA_STYLE);
285        }
286    
287        public void setValue(final String value) {
288            // Need to defer the setValue to make sure the element is actually in the DOM to manipulate
289            Scheduler.get().scheduleDeferred(new Command() {
290                @Override
291                public void execute() {
292                    setValue(getElement(), value);
293                }
294            });
295        }
296    
297        public void setValues(final String... values) {
298            final JsArrayString array = JavaScriptObject.createArray().cast();
299    
300            for (final String value : values) {
301                array.push(value);
302            }
303    
304            // Need to defer the setValue to make sure the element is actually in the DOM to manipulate
305            Scheduler.get().scheduleDeferred(new Command() {
306                @Override
307                public void execute() {
308                    setValue(getElement(), array);
309                }
310            });
311        }
312    
313        public void setValue(final Option opt) {
314            setValue(opt.getText());
315        }
316    
317        public void setValues(final Option... opts) {
318            final String[] values = new String[opts.length];
319            for (int i = 0; i < opts.length; i++) {
320                values[i] = opts[i].getText();
321            }
322            setValues(values);
323        }
324    
325        /**
326         * @return the selected value, if multiple it will return the first selected item see {@link #isItemSelected(int)}
327         * and {@link #getValue(int)} for getting all the values selected or {@link #getAllSelectedValues()}
328         */
329        public String getValue() {
330            int selectedIndex = getSelectElement().getSelectedIndex();
331            return selectedIndex == -1 ? null : getSelectElement().getOptions().getItem(selectedIndex).getValue();
332        }
333    
334        public List<String> getAllSelectedValues() {
335            final List<String> allSelected = new ArrayList<String>();
336    
337            for (int i = 0; i < getItemCount(); i++) {
338                if (isItemSelected(i)) {
339                    allSelected.add(getValue(i));
340                }
341            }
342            return allSelected;
343        }
344    
345        public boolean isItemSelected(final int index) {
346            checkIndex(index);
347            return getSelectElement().getOptions().getItem(index).isSelected();
348        }
349    
350        public String getValue(final int index) {
351            checkIndex(index);
352            return getSelectElement().getOptions().getItem(index).getValue();
353        }
354    
355        public void selectAll() {
356            command(getElement(), SELECT_ALL);
357        }
358    
359        public void deselectAll() {
360            command(getElement(), DESELECT_ALL);
361        }
362    
363        public void render() {
364            command(getElement(), RENDER);
365        }
366    
367        /**
368         * WHEN CHANGING ANY SETTINGS CALL REFRESH AFTER!!
369         */
370        public void refresh() {
371            command(getElement(), REFRESH);
372        }
373    
374        public void hide() {
375            command(getElement(), HIDE);
376        }
377    
378        public void show() {
379            command(getElement(), SHOW);
380        }
381    
382        private void initialize() {
383            initialize(getElement());
384        }
385    
386        private void checkIndex(final int index) {
387            if (index < 0 || index >= getItemCount()) {
388                throw new IndexOutOfBoundsException();
389            }
390        }
391    
392        protected SelectElement getSelectElement() {
393            return getElement().cast();
394        }
395    
396        /**
397         * {@inheritDoc}
398         */
399        @Override
400        public int getTabIndex() {
401            return focusableMixin.getTabIndex();
402        }
403    
404        /**
405         * {@inheritDoc}
406         */
407        @Override
408        public void setAccessKey(final char c) {
409            focusableMixin.setAccessKey(c);
410        }
411    
412        /**
413         * {@inheritDoc}
414         */
415        @Override
416        public void setFocus(final boolean b) {
417            focusableMixin.setFocus(b);
418        }
419    
420        /**
421         * {@inheritDoc}
422         */
423        @Override
424        public void setTabIndex(final int i) {
425            focusableMixin.setTabIndex(i);
426        }
427    
428        @Override
429        public void setName(String name) {
430            selectElement.setName(name);
431        }
432    
433        @Override
434        public String getName() {
435            return selectElement.getName();
436        }
437    
438        /**
439         * Gets the number of items present in the list box.
440         *
441         * @return the number of items
442         */
443        public int getItemCount() {
444            return getSelectElement().getOptions().getLength();
445        }
446    
447        private native void initialize(Element e) /*-{
448            $wnd.jQuery(e).selectpicker({
449                iconBase: 'fa',
450                tickIcon: 'fa-check'
451            });
452        }-*/;
453    
454        private native void setValue(Element e, JsArrayString value) /*-{
455            $wnd.jQuery(e).selectpicker('val', value);
456        }-*/;
457    
458        private native void setValue(Element e, String value) /*-{
459            $wnd.jQuery(e).selectpicker('val', value);
460        }-*/;
461    
462        private native void command(Element e, String command) /*-{
463            $wnd.jQuery(e).selectpicker(command);
464        }-*/;
465    }