001/*
002 * #%L
003 * GwtMaterial
004 * %%
005 * Copyright (C) 2015 - 2017 GwtMaterialDesign
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 * 
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 * 
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package gwt.material.design.client.ui;
021
022import com.google.gwt.core.client.GWT;
023import com.google.gwt.dom.client.Document;
024import com.google.gwt.dom.client.OptionElement;
025import com.google.gwt.dom.client.SelectElement;
026import com.google.gwt.event.logical.shared.ValueChangeEvent;
027import com.google.gwt.i18n.client.HasDirection.Direction;
028import com.google.gwt.user.client.ui.FormPanel;
029import com.google.gwt.user.client.ui.HasConstrainedValue;
030import com.google.gwt.user.client.ui.ListBox;
031import gwt.material.design.client.base.*;
032import gwt.material.design.client.base.helper.UiHelper;
033import gwt.material.design.client.base.mixin.ErrorMixin;
034import gwt.material.design.client.base.mixin.ReadOnlyMixin;
035import gwt.material.design.client.base.mixin.ToggleStyleMixin;
036import gwt.material.design.client.constants.CssName;
037import gwt.material.design.client.js.JsMaterialElement;
038import gwt.material.design.client.ui.html.Label;
039import gwt.material.design.jquery.client.api.JQuery;
040import gwt.material.design.jquery.client.api.JQueryElement;
041
042import java.util.ArrayList;
043import java.util.Collection;
044import java.util.LinkedList;
045import java.util.List;
046
047import static gwt.material.design.client.js.JsMaterialElement.$;
048//@formatter:off
049
050/**
051 * <p>Material ListBox is another dropdown component that will set / get the value depends on the selected index
052 * <h3>UiBinder Usage:</h3>
053 * <p>
054 * <pre>
055 * {@code
056 *    <m:MaterialListBox ui:field="lstBox" />
057 * }
058 * </pre>
059 * <h3>Java Usage:</h3>
060 * <p>
061 * <pre>
062 * {@code
063 *     // functions
064 *    lstBox.setSelectedIndex(2);
065 *    lstBox.getSelectedIndex();
066 *    lstBox.addValueChangeHandler(handler);
067 * }
068 * </pre>
069 * </p>
070 *
071 * @author kevzlou7979
072 * @author Ben Dol
073 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#forms">Material ListBox</a>
074 * @see <a href="https://material.io/guidelines/components/menus.html">Material Design Specification</a>
075 */
076//@formatter:on
077public class MaterialListValueBox<T> extends AbstractValueWidget<T> implements JsLoader, HasPlaceholder,
078        HasConstrainedValue<T>, HasReadOnly {
079
080    private final ListBox listBox = new ListBox();
081    private final Label label = new Label();
082    protected final List<T> values = new ArrayList<>();
083    private KeyFactory<T, String> keyFactory = Object::toString;
084    private MaterialLabel errorLabel = new MaterialLabel();
085
086    private ToggleStyleMixin<ListBox> toggleOldMixin;
087    private ReadOnlyMixin<MaterialListValueBox<T>, ListBox> readOnlyMixin;
088    private ErrorMixin<AbstractValueWidget, MaterialLabel> errorMixin;
089
090    public MaterialListValueBox() {
091        super(Document.get().createDivElement(), CssName.INPUT_FIELD);
092    }
093
094    @Override
095    protected void onLoad() {
096        super.onLoad();
097
098        add(listBox);
099        add(label);
100        add(errorLabel);
101
102        registerHandler(addValueChangeHandler(valueChangeEvent -> {
103            if (isToggleReadOnly()) {
104                setReadOnly(true);
105            }
106        }));
107
108        load();
109    }
110
111    @Override
112    public void load() {
113        JQueryElement listBoxElement = $(listBox.getElement());
114        JsMaterialElement.$(listBox.getElement()).material_select(
115                () -> $("input.select-dropdown").trigger("close", true));
116        listBoxElement.change((e, param) -> {
117            try {
118                ValueChangeEvent.fire(this, getValue());
119            } catch (IndexOutOfBoundsException ex) {
120                GWT.log("ListBox value change handler threw an exception.", ex);
121            }
122            return true;
123        });
124
125        // Fixed IE browser for select dropdown scrolling
126        // Related materialize issue https://github.com/Dogfalo/materialize/issues/901
127        listBoxElement.siblings("input.select-dropdown").mousedown((event, o) -> {
128            $("input[data-activates!='" + listBoxElement.attr("data-activates") + "'].select-dropdown").trigger("close", true);
129            if (!UiHelper.isTouchScreenDevice()) {
130                event.preventDefault();
131            }
132            return true;
133        });
134    }
135
136    @Override
137    protected void onUnload() {
138        super.onUnload();
139
140        unload();
141    }
142
143    @Override
144    public void unload() {
145        if (listBox != null && listBox.isAttached()) {
146            $(listBox.getElement()).siblings("input.select-dropdown").off("mousedown");
147            $(listBox.getElement()).off("change");
148            $(listBox.getElement()).material_select("destroy");
149        }
150    }
151
152    @Override
153    public void reload() {
154        if (isAttached()) {
155            unload();
156            load();
157        }
158    }
159
160    public void add(T value) {
161        addItem(value);
162    }
163
164    /**
165     * Adds an item to the list box, specifying its direction. This method has
166     * the same effect as
167     * <pre>addItem(value, dir, item)</pre>
168     *
169     * @param value the item's value, to be submitted if it is part of a
170     *              {@link FormPanel}; cannot be <code>null</code>
171     * @param dir   the item's direction
172     */
173    public void addItem(T value, Direction dir) {
174        addItem(value, dir, true);
175    }
176
177    /**
178     * Adds an item to the list box, specifying its direction. This method has
179     * the same effect as
180     * <pre>addItem(value, dir, item)</pre>
181     *
182     * @param value  the item's value, to be submitted if it is part of a
183     *              {@link FormPanel}; cannot be <code>null</code>
184     * @param dir    the item's direction
185     * @param reload perform a 'material select' reload to update the DOM.
186     */
187    public void addItem(T value, Direction dir, boolean reload) {
188        values.add(value);
189        listBox.addItem(keyFactory.generateKey(value), dir);
190
191        if (reload) {
192            reload();
193        }
194    }
195
196    /**
197     * Adds an item to the list box. This method has the same effect as
198     * <pre>addItem(value, item)</pre>
199     *
200     * @param value the item's value, to be submitted if it is part of a
201     *              {@link FormPanel}; cannot be <code>null</code>
202     */
203    public void addItem(T value) {
204        addItem(value, true);
205    }
206
207    /**
208     * Adds an item to the list box. This method has the same effect as
209     * <pre>addItem(value, item)</pre>
210     *
211     * @param value  the item's value, to be submitted if it is part of a
212     *              {@link FormPanel}; cannot be <code>null</code>
213     * @param reload perform a 'material select' reload to update the DOM.
214     */
215    public void addItem(T value, boolean reload) {
216        values.add(value);
217        listBox.addItem(keyFactory.generateKey(value));
218
219        if (reload) {
220            reload();
221        }
222    }
223
224    /**
225     * Adds an item to the list box, specifying an initial value for the item.
226     *
227     * @param value the item's value, to be submitted if it is part of a
228     *              {@link FormPanel}; cannot be <code>null</code>
229     * @param text  the text of the item to be added
230     */
231    public void addItem(T value, String text) {
232        addItem(value, text, true);
233    }
234
235    /**
236     * Adds an item to the list box, specifying an initial value for the item.
237     *
238     * @param value  the item's value, to be submitted if it is part of a
239     *               {@link FormPanel}; cannot be <code>null</code>
240     * @param text   the text of the item to be added
241     * @param reload perform a 'material select' reload to update the DOM.
242     */
243    public void addItem(T value, String text, boolean reload) {
244        values.add(value);
245        listBox.addItem(text, keyFactory.generateKey(value));
246
247        if (reload) {
248            reload();
249        }
250    }
251
252    /**
253     * Adds an item to the list box, specifying its direction and an initial
254     * value for the item.
255     *
256     * @param value the item's value, to be submitted if it is part of a
257     *              {@link FormPanel}; cannot be <code>null</code>
258     * @param dir   the item's direction
259     * @param text  the text of the item to be added
260     */
261    public void addItem(T value, Direction dir, String text) {
262        addItem(value, dir, text, true);
263    }
264
265    /**
266     * Adds an item to the list box, specifying its direction and an initial
267     * value for the item.
268     *
269     * @param value  the item's value, to be submitted if it is part of a
270     *               {@link FormPanel}; cannot be <code>null</code>
271     * @param dir    the item's direction
272     * @param text   the text of the item to be added
273     * @param reload perform a 'material select' reload to update the DOM.
274     */
275    public void addItem(T value, Direction dir, String text, boolean reload) {
276        values.add(value);
277        listBox.addItem(text, dir, keyFactory.generateKey(value));
278
279        if (reload) {
280            reload();
281        }
282    }
283
284    /**
285     * Inserts an item into the list box. Has the same effect as
286     * <pre>insertItem(value, item, index)</pre>
287     *
288     * @param value the item's value, to be submitted if it is part of a
289     *              {@link FormPanel}.
290     * @param index the index at which to insert it
291     */
292    public void insertItem(T value, int index) {
293        insertItem(value, index, true);
294    }
295
296    /**
297     * Inserts an item into the list box. Has the same effect as
298     * <pre>insertItem(value, item, index)</pre>
299     *
300     * @param value  the item's value, to be submitted if it is part of a
301     *              {@link FormPanel}.
302     * @param index  the index at which to insert it
303     * @param reload perform a 'material select' reload to update the DOM.
304     */
305    public void insertItem(T value, int index, boolean reload) {
306        values.add(index, value);
307        listBox.insertItem(keyFactory.generateKey(value), index);
308
309        if (reload) {
310            reload();
311        }
312    }
313
314    /**
315     * Inserts an item into the list box, specifying its direction. Has the same
316     * effect as
317     * <pre>insertItem(value, dir, item, index)</pre>
318     *
319     * @param value the item's value, to be submitted if it is part of a
320     *              {@link FormPanel}.
321     * @param dir   the item's direction
322     * @param index the index at which to insert it
323     */
324    public void insertItem(T value, Direction dir, int index) {
325        insertItem(value, dir, index, true);
326    }
327
328    /**
329     * Inserts an item into the list box, specifying its direction. Has the same
330     * effect as
331     * <pre>insertItem(value, dir, item, index)</pre>
332     *
333     * @param value  the item's value, to be submitted if it is part of a
334     *              {@link FormPanel}.
335     * @param dir    the item's direction
336     * @param index  the index at which to insert it
337     * @param reload perform a 'material select' reload to update the DOM.
338     */
339    public void insertItem(T value, Direction dir, int index, boolean reload) {
340        values.add(index, value);
341        listBox.insertItem(keyFactory.generateKey(value), dir, index);
342
343        if (reload) {
344            reload();
345        }
346    }
347
348    /**
349     * Inserts an item into the list box, specifying an initial value for the
350     * item. Has the same effect as
351     * <pre>insertItem(value, null, item, index)</pre>
352     *
353     * @param value the item's value, to be submitted if it is part of a
354     *              {@link FormPanel}.
355     * @param text  the text of the item to be inserted
356     * @param index the index at which to insert it
357     */
358    public void insertItem(T value, String text, int index) {
359        insertItem(value, text, index, true);
360    }
361
362    /**
363     * Inserts an item into the list box, specifying an initial value for the
364     * item. Has the same effect as
365     * <pre>insertItem(value, null, item, index)</pre>
366     *
367     * @param value  the item's value, to be submitted if it is part of a
368     *               {@link FormPanel}.
369     * @param text   the text of the item to be inserted
370     * @param index  the index at which to insert it
371     * @param reload perform a 'material select' reload to update the DOM.
372     */
373    public void insertItem(T value, String text, int index, boolean reload) {
374        values.add(index, value);
375        listBox.insertItem(text, keyFactory.generateKey(value), index);
376
377        if (reload) {
378            reload();
379        }
380    }
381
382    /**
383     * Inserts an item into the list box, specifying its direction and an
384     * initial value for the item. If the index is less than zero, or greater
385     * than or equal to the length of the list, then the item will be appended
386     * to the end of the list.
387     *
388     * @param value the item's value, to be submitted if it is part of a
389     *              {@link FormPanel}.
390     * @param dir   the item's direction. If {@code null}, the item is displayed
391     *              in the widget's overall direction, or, if a direction
392     *              estimator has been set, in the item's estimated direction.
393     * @param text  the text of the item to be inserted
394     * @param index the index at which to insert it
395     */
396    public void insertItem(T value, Direction dir, String text, int index) {
397        insertItem(value, dir, text, index, true);
398    }
399
400    /**
401     * Inserts an item into the list box, specifying its direction and an
402     * initial value for the item. If the index is less than zero, or greater
403     * than or equal to the length of the list, then the item will be appended
404     * to the end of the list.
405     *
406     * @param value  the item's value, to be submitted if it is part of a
407     *               {@link FormPanel}.
408     * @param dir    the item's direction. If {@code null}, the item is displayed
409     *               in the widget's overall direction, or, if a direction
410     *               estimator has been set, in the item's estimated direction.
411     * @param text   the text of the item to be inserted
412     * @param index  the index at which to insert it
413     * @param reload perform a 'material select' reload to update the DOM.
414     */
415    public void insertItem(T value, Direction dir, String text, int index, boolean reload) {
416        values.add(index, value);
417        listBox.insertItem(keyFactory.generateKey(value), dir, text, index);
418
419        if (reload) {
420            reload();
421        }
422    }
423
424    /**
425     * Removes the item at the specified index.
426     *
427     * @param index the index of the item to be removed
428     * @throws IndexOutOfBoundsException if the index is out of range
429     */
430    public void removeItem(int index) {
431        removeItem(index, true);
432    }
433
434    /**
435     * Removes the item at the specified index.
436     *
437     * @param index  the index of the item to be removed
438     * @param reload perform a 'material select' reload to update the DOM.
439     * @throws IndexOutOfBoundsException if the index is out of range
440     */
441    public void removeItem(int index, boolean reload) {
442        values.remove(index);
443        listBox.removeItem(index);
444
445        if (reload) {
446            reload();
447        }
448    }
449
450    /**
451     * Removes a value from the list box. Nothing is done if the value isn't on
452     * the list box.
453     *
454     * @param value the value to be removed from the list
455     */
456    public void removeValue(String value) {
457        removeValue(value, true);
458    }
459
460    /**
461     * Removes a value from the list box. Nothing is done if the value isn't on
462     * the list box.
463     *
464     * @param value  the value to be removed from the list
465     * @param reload perform a 'material select' reload to update the DOM.
466     */
467    public void removeValue(String value, boolean reload) {
468        int idx = getIndex(value);
469        if (idx >= 0) {
470            removeItem(idx, reload);
471        }
472    }
473
474    @Override
475    public void reset() {
476        super.reset();
477        clear();
478    }
479
480    /**
481     * Removes all items from the list box.
482     */
483    @Override
484    public void clear() {
485        values.clear();
486        listBox.clear();
487        reload();
488    }
489
490    @Override
491    public void setPlaceholder(String placeholder) {
492        label.setText(placeholder);
493    }
494
495    @Override
496    public String getPlaceholder() {
497        return label.getText();
498    }
499
500    public OptionElement getOptionElement(int index) {
501        return getSelectElement().getOptions().getItem(index);
502    }
503
504    protected SelectElement getSelectElement() {
505        return listBox.getElement().cast();
506    }
507
508    /**
509     * Sets whether this list allows multiple selections.
510     *
511     * @param multipleSelect <code>true</code> to allow multiple selections
512     */
513    public void setMultipleSelect(boolean multipleSelect) {
514        listBox.setMultipleSelect(multipleSelect);
515    }
516
517    /**
518     * Gets whether this list allows multiple selection.
519     *
520     * @return <code>true</code> if multiple selection is allowed
521     */
522    public boolean isMultipleSelect() {
523        return listBox.isMultipleSelect();
524    }
525
526    public void setEmptyPlaceHolder(String value) {
527        listBox.insertItem(value, 0);
528
529        getOptionElement(0).setDisabled(true);
530    }
531
532    @Override
533    public void setAcceptableValues(Collection<T> values) {
534        this.values.clear();
535        clear();
536        values.forEach(this::addItem);
537    }
538
539
540    @Override
541    public T getValue() {
542        if (getSelectedIndex() != -1) {
543            return values.get(getSelectedIndex());
544        }
545        return null;
546    }
547
548    @Override
549    public void setValue(T value) {
550        setValue(value, false);
551    }
552
553    @Override
554    public void setValue(T value, boolean fireEvents) {
555        int index = values.indexOf(value);
556        if (index >= 0) {
557            T before = getValue();
558            setSelectedIndex(index);
559
560            if (fireEvents) {
561                ValueChangeEvent.fireIfNotEqual(this, before, value);
562            }
563        }
564    }
565
566    public boolean isOld() {
567        return getToggleOldMixin().isOn();
568    }
569
570    public void setOld(boolean old) {
571        getToggleOldMixin().setOn(old);
572    }
573
574    /**
575     * Sets the value associated with the item at a given index. This value can
576     * be used for any purpose, but is also what is passed to the server when
577     * the list box is submitted as part of a {@link FormPanel}.
578     *
579     * @param index the index of the item to be set
580     * @param value the item's new value; cannot be <code>null</code>
581     * @throws IndexOutOfBoundsException if the index is out of range
582     */
583    public void setValue(int index, String value) {
584        listBox.setValue(index, value);
585        reload();
586    }
587
588    @Override
589    public void setTitle(String title) {
590        listBox.setTitle(title);
591    }
592
593    /**
594     * Sets whether an individual list item is selected.
595     *
596     * @param index    the index of the item to be selected or unselected
597     * @param selected <code>true</code> to select the item
598     * @throws IndexOutOfBoundsException if the index is out of range
599     */
600    public void setItemSelected(int index, boolean selected) {
601        listBox.setItemSelected(index, selected);
602        reload();
603    }
604
605    /**
606     * Sets the text associated with the item at a given index.
607     *
608     * @param index the index of the item to be set
609     * @param text  the item's new text
610     * @throws IndexOutOfBoundsException if the index is out of range
611     */
612    public void setItemText(int index, String text) {
613        listBox.setItemText(index, text);
614        reload();
615    }
616
617    /**
618     * Sets the text associated with the item at a given index.
619     *
620     * @param index the index of the item to be set
621     * @param text  the item's new text
622     * @param dir   the item's direction.
623     * @throws IndexOutOfBoundsException if the index is out of range
624     */
625    public void setItemText(int index, String text, Direction dir) {
626        listBox.setItemText(index, text, dir);
627        reload();
628    }
629
630    public void setName(String name) {
631        listBox.setName(name);
632    }
633
634    /**
635     * Sets the currently selected index.
636     * <p>
637     * After calling this method, only the specified item in the list will
638     * remain selected. For a ListBox with multiple selection enabled, see
639     * {@link #setItemSelected(int, boolean)} to select multiple items at a
640     * time.
641     *
642     * @param index the index of the item to be selected
643     */
644    public void setSelectedIndex(int index) {
645        listBox.setSelectedIndex(index);
646        reload();
647    }
648
649    /**
650     * Sets the number of items that are visible. If only one item is visible,
651     * then the box will be displayed as a drop-down list.
652     *
653     * @param visibleItems the visible item count
654     */
655    public void setVisibleItemCount(int visibleItems) {
656        listBox.setVisibleItemCount(visibleItems);
657    }
658
659    /**
660     * Gets the number of items present in the list box.
661     *
662     * @return the number of items
663     */
664    public int getItemCount() {
665        return listBox.getItemCount();
666    }
667
668    /**
669     * Gets the text associated with the item at the specified index.
670     *
671     * @param index the index of the item whose text is to be retrieved
672     * @return the text associated with the item
673     * @throws IndexOutOfBoundsException if the index is out of range
674     */
675    public String getItemText(int index) {
676        return listBox.getItemText(index);
677    }
678
679    /**
680     * Gets the text for currently selected item. If multiple items are
681     * selected, this method will return the text of the first selected item.
682     *
683     * @return the text for selected item, or {@code null} if none is selected
684     */
685    public String getSelectedItemText() {
686        return listBox.getSelectedItemText();
687    }
688
689    public String getName() {
690        return listBox.getName();
691    }
692
693    /**
694     * Gets the currently-selected item. If multiple items are selected, this
695     * method will return the first selected item ({@link #isItemSelected(int)}
696     * can be used to query individual items).
697     *
698     * @return the selected index, or <code>-1</code> if none is selected
699     */
700    public int getSelectedIndex() {
701        return listBox.getSelectedIndex();
702    }
703
704    /**
705     * Gets the value associated with the item at a given index.
706     *
707     * @param index the index of the item to be retrieved
708     * @return the item's associated value
709     * @throws IndexOutOfBoundsException if the index is out of range
710     */
711    public T getValue(int index) {
712        return values.get(index);
713    }
714
715    /**
716     * Gets the value for currently selected item. If multiple items are
717     * selected, this method will return the value of the first selected item.
718     *
719     * @return the value for selected item, or {@code null} if none is selected
720     */
721    public T getSelectedValue() {
722        try {
723            return values.get(getSelectedIndex());
724        } catch (IndexOutOfBoundsException ex) {
725            return null;
726        }
727    }
728
729    /**
730     * Gets the number of items that are visible. If only one item is visible,
731     * then the box will be displayed as a drop-down list.
732     *
733     * @return the visible item count
734     */
735    public int getVisibleItemCount() {
736        return listBox.getVisibleItemCount();
737    }
738
739    /**
740     * Determines whether an individual list item is selected.
741     *
742     * @param index the index of the item to be tested
743     * @return <code>true</code> if the item is selected
744     * @throws IndexOutOfBoundsException if the index is out of range
745     */
746    public boolean isItemSelected(int index) {
747        return listBox.isItemSelected(index);
748    }
749
750
751    @Override
752    public void setEnabled(boolean enabled) {
753        listBox.setEnabled(enabled);
754        reload();
755    }
756
757    @Override
758    public boolean isEnabled() {
759        return listBox.isEnabled();
760    }
761
762    /**
763     * Use your own key factory for value keys.
764     */
765    public void setKeyFactory(KeyFactory<T, String> keyFactory) {
766        this.keyFactory = keyFactory;
767    }
768
769    @Override
770    public void setReadOnly(boolean value) {
771        getReadOnlyMixin().setReadOnly(value);
772        if (!value) {
773            $(listBox.getElement()).material_select("destroy");
774            $(listBox.getElement()).material_select();
775        }
776    }
777
778    @Override
779    public boolean isReadOnly() {
780        return getReadOnlyMixin().isReadOnly();
781    }
782
783    @Override
784    public void setToggleReadOnly(boolean toggle) {
785        getReadOnlyMixin().setToggleReadOnly(toggle);
786    }
787
788    @Override
789    public boolean isToggleReadOnly() {
790        return getReadOnlyMixin().isToggleReadOnly();
791    }
792
793    public ListBox getListBox() {
794        return listBox;
795    }
796
797    @Override
798    public ErrorMixin<AbstractValueWidget, MaterialLabel> getErrorMixin() {
799        if (errorMixin == null) {
800            errorMixin = new ErrorMixin<>(this, errorLabel, listBox, label);
801        }
802        return errorMixin;
803    }
804
805    public Label getLabel() {
806        return label;
807    }
808
809    public MaterialLabel getErrorLabel() {
810        return errorLabel;
811    }
812
813    /**
814     * Returns all selected values of the list box, or empty array if none.
815     *
816     * @return the selected values of the list box
817     */
818    public String[] getItemsSelected() {
819        List<String> selected = new LinkedList<>();
820        for (int i = 0; i < listBox.getItemCount(); i++) {
821            if (listBox.isItemSelected(i)) {
822                selected.add(listBox.getValue(i));
823            }
824        }
825        return selected.toArray(new String[selected.size()]);
826    }
827
828    /**
829     * Sets the currently selected value.
830     * <p>
831     * After calling this method, only the specified item in the list will
832     * remain selected. For a ListBox with multiple selection enabled, see
833     * {@link #setValueSelected(String, boolean)} to select multiple items at a
834     * time.
835     *
836     * @param value the value of the item to be selected
837     */
838    public void setSelectedValue(String value) {
839        int idx = getIndex(value);
840        if (idx >= 0) {
841            setSelectedIndex(idx);
842        }
843    }
844
845    /**
846     * Sets whether an individual list value is selected.
847     *
848     * @param value    the value of the item to be selected or unselected
849     * @param selected <code>true</code> to select the item
850     */
851    public void setValueSelected(String value, boolean selected) {
852        int idx = getIndex(value);
853        if (idx >= 0) {
854            setItemSelected(idx, selected);
855        }
856    }
857
858    /**
859     * Gets the index of the specified value.
860     *
861     * @param value the value of the item to be found
862     * @return the index of the value
863     */
864    public int getIndex(String value) {
865        int count = getItemCount();
866        for (int i = 0; i < count; i++) {
867            if (getValue(i).equals(value)) {
868                return i;
869            }
870        }
871        return -1;
872    }
873
874    public ReadOnlyMixin<MaterialListValueBox<T>, ListBox> getReadOnlyMixin() {
875        if (readOnlyMixin == null) {
876            readOnlyMixin = new ReadOnlyMixin<>(this, listBox);
877        }
878        return readOnlyMixin;
879    }
880
881    protected ToggleStyleMixin<ListBox> getToggleOldMixin() {
882        if (toggleOldMixin == null) {
883            toggleOldMixin = new ToggleStyleMixin<>(listBox, "browser-default");
884        }
885        return toggleOldMixin;
886    }
887}