001/*
002 *  Copyright 2012 GWT-Bootstrap
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.github.gwtbootstrap.client.ui;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import com.github.gwtbootstrap.client.ui.base.HasAlternateSize;
025import com.github.gwtbootstrap.client.ui.base.HasId;
026import com.github.gwtbootstrap.client.ui.base.HasSize;
027import com.github.gwtbootstrap.client.ui.base.HasStyle;
028import com.github.gwtbootstrap.client.ui.base.IsResponsive;
029import com.github.gwtbootstrap.client.ui.base.IsSearchQuery;
030import com.github.gwtbootstrap.client.ui.base.ResponsiveHelper;
031import com.github.gwtbootstrap.client.ui.base.SearchQueryStyleHelper;
032import com.github.gwtbootstrap.client.ui.base.SizeHelper;
033import com.github.gwtbootstrap.client.ui.base.Style;
034import com.github.gwtbootstrap.client.ui.base.StyleHelper;
035import com.github.gwtbootstrap.client.ui.constants.AlternateSize;
036import com.github.gwtbootstrap.client.ui.constants.Constants;
037import com.github.gwtbootstrap.client.ui.constants.Device;
038import com.google.gwt.editor.client.IsEditor;
039import com.google.gwt.editor.client.adapters.TakesValueEditor;
040import com.google.gwt.event.dom.client.ChangeEvent;
041import com.google.gwt.event.dom.client.ChangeHandler;
042import com.google.gwt.event.logical.shared.ValueChangeEvent;
043import com.google.gwt.event.logical.shared.ValueChangeHandler;
044import com.google.gwt.event.shared.HandlerRegistration;
045import com.google.gwt.text.shared.Renderer;
046import com.google.gwt.user.client.ui.Composite;
047import com.google.gwt.user.client.ui.HasConstrainedValue;
048import com.google.gwt.user.client.ui.HasEnabled;
049import com.google.gwt.user.client.ui.HasName;
050import com.google.gwt.view.client.ProvidesKey;
051import com.google.gwt.view.client.SimpleKeyProvider;
052
053/**
054 * The TW Bootstrap style ValueListBox
055 * 
056 * @param <T> the value type
057 * @author ohashi keisuke
058 * @since 2.0.4.0
059 */
060public class ValueListBox<T> extends Composite implements HasConstrainedValue<T>, IsEditor<TakesValueEditor<T>>, HasName, HasId, HasEnabled, HasSize, HasAlternateSize, IsSearchQuery, IsResponsive, HasStyle {
061
062        private final Map<Object, Integer> valueKeyToIndex = new HashMap<Object, Integer>();
063
064        private final List<T> valueList = new ArrayList<T>();
065
066        private final Renderer<T> renderer;
067
068        private final ProvidesKey<T> keyProvider;
069
070        private TakesValueEditor<T> editor;
071
072        private T value;
073
074        public ValueListBox(Renderer<T> renderer) {
075                this(renderer, new SimpleKeyProvider<T>());
076        }
077
078        public ValueListBox(Renderer<T> renderer,
079                ProvidesKey<T> keyProvider) {
080                this.keyProvider = keyProvider;
081                this.renderer = renderer;
082                initWidget(new ListBox());
083
084                getListBox().addChangeHandler(new ChangeHandler() {
085
086                        public void onChange(ChangeEvent event) {
087                                int selectedIndex = getListBox().getSelectedIndex();
088
089                                if (selectedIndex < 0) {
090                                        return;
091                                }
092                                T newValue = valueList.get(selectedIndex);
093                                setValue(newValue, true);
094                        }
095                });
096        }
097
098        public void setName(String name) {
099                getListBox().setName(name);
100        }
101
102        public String getName() {
103                return getListBox().getName();
104        }
105
106        public HandlerRegistration addValueChangeHandler(ValueChangeHandler<T> handler) {
107                return addHandler(handler, ValueChangeEvent.getType());
108        }
109
110        /**
111         * Returns a {@link TakesValueEditor} backed by the ValueListBox.
112         */
113        public TakesValueEditor<T> asEditor() {
114                if (editor == null) {
115                        editor = TakesValueEditor.of(this);
116                }
117                return editor;
118        }
119
120        public T getValue() {
121                return value;
122        }
123
124        public void setAcceptableValues(Collection<T> newValues) {
125
126                valueList.clear();
127                
128                valueKeyToIndex.clear();
129                
130                ListBox listBox = getListBox();
131                
132                listBox.clear();
133
134                for (T nextNewValue : newValues) {
135                        addValue(nextNewValue);
136                }
137
138                updateListBox();
139        }
140
141        /**
142         * Set the value and display it in the select element. Add the value to the
143         * acceptable set if it is not already there.
144         */
145        public void setValue(T value) {
146                setValue(value, false);
147        }
148
149        public void setValue(T value, boolean fireEvents) {
150                if (value == this.value || (this.value != null && this.value.equals(value))) {
151                        return;
152                }
153
154                T before = this.value;
155                this.value = value;
156                updateListBox();
157
158                if (fireEvents) {
159                        ValueChangeEvent.fireIfNotEqual(this, before, value);
160                }
161        }
162
163        private void addValue(T value) {
164                Object key = keyProvider.getKey(value);
165                if (valueKeyToIndex.containsKey(key)) {
166                        throw new IllegalArgumentException("Duplicate value: " + value);
167                }
168
169                valueKeyToIndex.put(key, valueList.size());
170                valueList.add(value);
171                getListBox().addItem(renderer.render(value));
172                assert valueList.size() == getListBox().getItemCount();
173        }
174
175        private ListBox getListBox() {
176                return (ListBox) getWidget();
177        }
178
179        private void updateListBox() {
180                Object key = keyProvider.getKey(value);
181                Integer index = valueKeyToIndex.get(key);
182                if (index == null) {
183                        addValue(value);
184                }
185
186                index = valueKeyToIndex.get(key);
187                getListBox().setSelectedIndex(index);
188        }
189
190        /**
191         * {@inheritDoc}
192         */
193        @Override
194        public String getId() {
195                return getListBox().getId();
196        }
197
198        /**
199         * {@inheritDoc}
200         */
201        @Override
202        public void setId(String id) {
203                getListBox().setId(id);
204        }
205
206        /**
207         * {@inheritDoc}
208         */
209        @Override
210        public void setSearchQuery(boolean searchQuery) {
211                SearchQueryStyleHelper.setSearchQuery(this, searchQuery);
212        }
213
214        /**
215         * {@inheritDoc}
216         */
217        @Override
218        public boolean isSearchQuery() {
219                return SearchQueryStyleHelper.isSearchQuery(this);
220        }
221
222
223        /**
224         * {@inheritDoc}
225         */
226        @Override
227        public void setAlternateSize(AlternateSize size) {
228                StyleHelper.changeStyle(this, size, AlternateSize.class);
229        }
230
231        /**
232         * {@inheritDoc}
233         */
234        @Override
235        public void setSize(int size) {
236                SizeHelper.setSize(this, size);
237        }
238        /**
239         * {@inheritDoc}
240         */
241        @Override
242        public void setEnabled(boolean enabled) {
243                getListBox().setEnabled(enabled);
244                if(enabled) {
245                        removeStyleName(Constants.DISABLED);
246                } else {
247                        addStyleName(Constants.DISABLED);
248                }
249        }
250
251        @Override
252        public boolean isEnabled() {
253                return getListBox().isEnabled();
254        }
255        
256
257        /**
258         * {@inheritDoc}
259         */
260        @Override
261        public void setShowOn(Device device) {
262                ResponsiveHelper.setShowOn(this, device);
263        }
264
265        /**
266         * {@inheritDoc}
267         */
268        @Override
269        public void setHideOn(Device device) {
270                ResponsiveHelper.setHideOn(this, device);
271                
272        }
273
274        /**
275         * {@inheritDoc}
276         */
277        @Override
278        public void setStyle(Style style) {
279                StyleHelper.setStyle(this, style);
280        }
281
282        /**
283         * {@inheritDoc}
284         */
285        @Override
286        public void addStyle(Style style) {
287                StyleHelper.addStyle(this, style);
288        }
289
290        /**
291         * {@inheritDoc}
292         */
293        @Override
294        public void removeStyle(Style style) {
295                StyleHelper.removeStyle(this, style);
296                
297        }
298
299}