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.dom.client.Document;
023import com.google.gwt.dom.client.Element;
024import com.google.gwt.event.dom.client.ChangeEvent;
025import com.google.gwt.event.dom.client.ChangeHandler;
026import com.google.gwt.event.dom.client.HasChangeHandlers;
027import com.google.gwt.event.shared.HandlerRegistration;
028import gwt.material.design.client.base.AbstractValueWidget;
029import gwt.material.design.client.base.HasError;
030import gwt.material.design.client.base.mixin.ErrorMixin;
031import gwt.material.design.client.constants.CssName;
032import gwt.material.design.client.constants.InputType;
033import gwt.material.design.client.ui.html.Paragraph;
034import gwt.material.design.client.ui.html.Span;
035
036import static gwt.material.design.jquery.client.api.JQuery.$;
037
038//@formatter:off
039
040/**
041 * Material Range - a slider that initialize the minimum and maximum values.
042 * <p>
043 * <h3>UiBinder Usage:</h3>
044 * <pre>
045 * {@code <m:MaterialRange value="2" min="20" max="50" value="25"/>}
046 * </pre>
047 *
048 * @author kevzlou7979
049 * @author Ben Dol
050 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#range">Material Range</a>
051 * @see <a href="https://material.io/guidelines/components/sliders.html">Material Design Specification</a>
052 */
053//@formatter:on
054public class MaterialRange extends AbstractValueWidget<Integer> implements HasChangeHandlers, HasError {
055
056    private Paragraph paragraph = new Paragraph();
057    private MaterialInput rangeInputElement = new MaterialInput();
058    private Span thumb = new Span();
059    private Span value = new Span();
060    private static String VALUE = "value";
061    private static String MAX = "max";
062    private static String MIN = "min";
063    private MaterialLabel errorLabel = new MaterialLabel();
064    private ErrorMixin<AbstractValueWidget, MaterialLabel> errorMixin;
065
066    /**
067     * Creates a range
068     */
069    public MaterialRange() {
070        super(Document.get().createFormElement());
071    }
072
073    /**
074     * Creates a range with specified values
075     *
076     * @param min   - start min value
077     * @param max   - end max value
078     * @param value - default range value
079     */
080    public MaterialRange(Integer min, Integer max, Integer value) {
081        this();
082        setMin(min);
083        setMax(max);
084        setValue(value);
085    }
086
087    public void reset() {
088        setValue(getMin());
089        clearErrorOrSuccess();
090    }
091
092    @Override
093    protected void onLoad() {
094        super.onLoad();
095
096        getElement().setAttribute("action", "#");
097        errorLabel.setVisible(false);
098        paragraph.setStyleName(CssName.RANGE_FIELD);
099        rangeInputElement.setType(InputType.RANGE);
100        paragraph.add(rangeInputElement);
101        thumb.getElement().setClassName(CssName.THUMB);
102        value.getElement().setClassName(CssName.VALUE);
103        thumb.add(value);
104        paragraph.add(thumb);
105        add(paragraph);
106        add(errorLabel);
107
108        registerHandler(addChangeHandler(changeEvent -> setValue(getValue(), true)));
109    }
110
111    /**
112     * Retrieve the Integer value from the given Attribute of the range element
113     *
114     * @param attribute The name of the attribute on the range element
115     * @return The Integer vaulue read from the given attribute or null
116     */
117    protected Integer getIntFromRangeElement(String attribute) {
118        Element ele = $(rangeInputElement).asElement();
119        if (ele != null) {
120            return ele.getPropertyInt(attribute);
121        }
122        return null;
123    }
124
125    /**
126     * Set the given Integer value to the attribute of the range element.
127     */
128    protected void setIntToRangeElement(String attribute, Integer val) {
129        Element ele = $(rangeInputElement).asElement();
130        if (ele != null) {
131            ele.setPropertyInt(attribute, val);
132        }
133    }
134
135    /**
136     * Read the current value
137     *
138     * @return The Integer value or null
139     */
140    @Override
141    public Integer getValue() {
142        return getIntFromRangeElement(VALUE);
143    }
144
145    @Override
146    public void setValue(Integer value, boolean fireEvents) {
147        if (value == null) {
148            throw new IllegalArgumentException("Value must not be null");
149        }
150        if (value < getMin()) {
151            throw new IllegalArgumentException("Value must not be less than the minimum range value.");
152        }
153        if (value > getMax()) {
154            throw new IllegalArgumentException("Value must not be greater than the maximum range value");
155        }
156        setIntToRangeElement(VALUE, value);
157
158        super.setValue(value, fireEvents);
159    }
160
161    /**
162     * Read the min value
163     *
164     * @return The Integer or null
165     */
166    public Integer getMin() {
167        return getIntFromRangeElement(MIN);
168    }
169
170    /**
171     * Write the current min value
172     *
173     * @param min value must be &lt; max
174     */
175    public void setMin(Integer min) {
176        setIntToRangeElement(MIN, min);
177    }
178
179    /**
180     * Read the max value
181     *
182     * @return The Integer or null
183     */
184    public Integer getMax() {
185        return getIntFromRangeElement(MAX);
186    }
187
188    /**
189     * Write the current max value
190     *
191     * @param max value must be &gt; min
192     */
193    public void setMax(Integer max) {
194        setIntToRangeElement(MAX, max);
195    }
196
197    @Override
198    public void setError(String error) {
199        getErrorMixin().setError(error);
200    }
201
202    @Override
203    public void setSuccess(String success) {
204        getErrorMixin().setSuccess(success);
205    }
206
207    @Override
208    public void setHelperText(String helperText) {
209        getErrorMixin().setHelperText(helperText);
210    }
211
212    @Override
213    public void clearErrorOrSuccess() {
214        getErrorMixin().clearErrorOrSuccess();
215    }
216
217    public MaterialLabel getErrorLabel() {
218        return errorLabel;
219    }
220
221    public MaterialInput getRangeInputElement() {
222        return rangeInputElement;
223    }
224
225    public Paragraph getParagraph() {
226        return paragraph;
227    }
228
229    public Span getThumb() {
230        return thumb;
231    }
232
233    /**
234     * Register the ChangeHandler to become notified if the user changes the slider.
235     * The Handler is called when the user releases the mouse only at the end of the slide
236     * operation.
237     */
238    @Override
239    public HandlerRegistration addChangeHandler(final ChangeHandler handler) {
240        return getRangeInputElement().addDomHandler(handler, ChangeEvent.getType());
241    }
242
243    @Override
244    public ErrorMixin<AbstractValueWidget, MaterialLabel> getErrorMixin() {
245        if (errorMixin == null) {
246            errorMixin = new ErrorMixin<>(this, errorLabel, null);
247        }
248        return errorMixin;
249    }
250}