001package gwt.material.design.addins.client.timepicker;
002
003import com.google.gwt.dom.client.Document;
004import com.google.gwt.dom.client.Element;
005import com.google.gwt.dom.client.Style;
006import com.google.gwt.event.logical.shared.*;
007import com.google.gwt.event.shared.HandlerRegistration;
008import com.google.gwt.i18n.shared.DateTimeFormat;
009import com.google.gwt.user.client.DOM;
010import com.google.gwt.user.client.ui.HasValue;
011import gwt.material.design.addins.client.MaterialAddins;
012import gwt.material.design.client.MaterialDesignBase;
013import gwt.material.design.client.base.*;
014import gwt.material.design.client.base.mixin.ErrorMixin;
015import gwt.material.design.client.base.mixin.ToggleStyleMixin;
016import gwt.material.design.client.constants.*;
017import gwt.material.design.client.ui.MaterialIcon;
018import gwt.material.design.client.ui.MaterialInput;
019import gwt.material.design.client.ui.MaterialLabel;
020import gwt.material.design.client.ui.MaterialPanel;
021import gwt.material.design.client.ui.html.Label;
022
023import java.util.Date;
024
025/*
026 * #%L
027 * GwtMaterial
028 * %%
029 * Copyright (C) 2015 GwtMaterialDesign
030 * %%
031 * Licensed under the Apache License, Version 2.0 (the "License");
032 * you may not use this file except in compliance with the License.
033 * You may obtain a copy of the License at
034 * 
035 *      http://www.apache.org/licenses/LICENSE-2.0
036 * 
037 * Unless required by applicable law or agreed to in writing, software
038 * distributed under the License is distributed on an "AS IS" BASIS,
039 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
040 * See the License for the specific language governing permissions and
041 * limitations under the License.
042 * #L%
043 */
044
045//@formatter:off
046
047/**
048 * Material Time Picker - provide a simple way to select a single value from a pre-determined set.
049 *
050 * <h3>XML Namespace Declaration</h3>
051 * <pre>
052 * {@code
053 * xmlns:ma='urn:import:gwt.material.design.addins.client'
054 * }
055 * </pre>
056 *
057 * <h3>UiBinder Usage:</h3>
058 * <pre>
059 * {@code <ma:timepicker.MaterialTimePicker placeholder="Time Arrival" />}
060 * </pre>
061 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#timepickers">Material Pickers</a>
062 * @author kevzlou7979
063 * @author Ben Dol
064 */
065//@formatter:on
066public class MaterialTimePicker extends MaterialWidget implements HasError, HasPlaceholder, HasOrientation,
067        HasCloseHandlers<Date>, HasOpenHandlers<Date>, HasValue<Date>, HasIcon {
068
069    static {
070        if(MaterialAddins.isDebug()) {
071            MaterialDesignBase.injectDebugJs(MaterialTimePickerDebugClientBundle.INSTANCE.timepickerJsDebug());
072            MaterialDesignBase.injectCss(MaterialTimePickerDebugClientBundle.INSTANCE.timepickerCssDebug());
073        } else {
074            MaterialDesignBase.injectJs(MaterialTimePickerClientBundle.INSTANCE.timepickerJs());
075            MaterialDesignBase.injectCss(MaterialTimePickerClientBundle.INSTANCE.timepickerCss());
076        }
077    }
078
079    /** Wraps the actual input. */
080    private MaterialPanel panel = new MaterialPanel();
081
082    /** The input element for the time picker. */
083    private MaterialInput input = new MaterialInput();
084
085    /** Label to display errors messages. */
086    private MaterialLabel lblError = new MaterialLabel();
087
088    /** The current value held by the time picker. */
089    private Date time;
090
091    private Label label = new Label();
092
093    private MaterialIcon icon = new MaterialIcon();
094
095    /** */
096    private ToggleStyleMixin<MaterialInput> validMixin = new ToggleStyleMixin<>(this.input, "valid");
097
098    private final ErrorMixin<MaterialTimePicker, MaterialLabel> errorMixin = new ErrorMixin<>(this, this.lblError, this.input);
099
100    private String placeholder;
101    private boolean autoClose;
102    private boolean hour24;
103    private Orientation orientation = Orientation.PORTRAIT;
104
105
106    public MaterialTimePicker() {
107        super(Document.get().createElement("div"), "timepicker", "input-field");
108        this.input.setType(InputType.TEXT);
109        this.panel.add(this.input);
110        this.panel.add(label);
111        this.panel.add(this.lblError);
112        this.add(this.panel);
113    }
114
115    @Override
116    protected void onLoad() {
117        super.onLoad();
118        this.input.getElement().setAttribute("type", "text");
119        this.initTimePicker();
120    }
121
122    /**
123     * Side effects:
124     * <ul>
125     *   <li>Resets the time to <i>now<i></li>
126     *   <li>Clears erros/success message</li>
127     * </ul>
128     */
129    public void reset() {
130        this.setValue(new Date());
131        this.clearErrorOrSuccess();
132    }
133
134    /**
135     * @return the time
136     */
137    public String getTime() {
138        return this.getTime(this.input.getElement());
139    }
140
141    public boolean isAutoClose() {
142        return this.autoClose;
143    }
144
145    public void setAutoClose(boolean autoClose) {
146        this.autoClose = autoClose;
147    }
148
149    /**
150     * False (default) change to 24 hours system.
151     *
152     * @return <code>false</code> in case 12 hours mode is set;
153     *         <code>true</code> otherwise.
154     */
155    public boolean isHour24() {
156        return this.hour24;
157    }
158
159    /**
160     *
161     * @param hour24
162     */
163    public void setHour24(boolean hour24) {
164        this.hour24 = hour24;
165    }
166
167    /**
168     * @return The placeholder text.
169     */
170    @Override
171    public String getPlaceholder() {
172        return this.placeholder;
173    }
174
175    /**
176     * @param placeholder
177     *            The placeholder text to set.
178     */
179    @Override
180    public void setPlaceholder(String placeholder) {
181        this.placeholder = placeholder;
182        this.label.setText(placeholder);
183    }
184
185    /**
186     * @return The orientation.
187     */
188    @Override
189    public Orientation getOrientation() {
190        return this.orientation;
191    }
192
193    /**
194     * @param orientation
195     *            The orientation to set: Can be Horizontal or Vertical.
196     */
197    @Override
198    public void setOrientation(Orientation orientation) {
199        this.orientation = orientation;
200    }
201
202    @Override
203    public void setError(String error) {
204        this.errorMixin.setError(error);
205    }
206
207    @Override
208    public void setSuccess(String success) {
209        this.errorMixin.setSuccess(success);
210    }
211
212    @Override
213    public void setHelperText(String helperText) {
214        this.errorMixin.setHelperText(helperText);
215    }
216
217    @Override
218    public void clearErrorOrSuccess() {
219        this.errorMixin.clearErrorOrSuccess();
220    }
221
222    public void initTimePicker() {
223        this.initTimePicker(DOM.createUniqueId(), this.input.getElement(), this.getOrientation().getCssName(), this.isAutoClose(), this.isHour24());
224    }
225
226    /**
227     *
228     * @param clockId
229     *            The clock id for the lolliclock.
230     * @param e
231     *            The HTML element serving as container for textual content.
232     * @param orientation
233     *            The initial orientation.
234     * @param autoClose
235     *            Autoclose <code>true</code> or <code>false</code>
236     * @param hour24
237     *            Set this <true>true</code> for a 24 hours clock;
238     *            <code>false</code> otherwise.
239     */
240    protected native void initTimePicker(String clockId, Element e, String orientation, boolean autoClose, boolean hour24) /*-{
241        var that = this;
242        $wnd.jQuery(document).ready(function() {
243            $wnd.jQuery(e).lolliclock({
244                autoclose: autoClose,
245                orientation: orientation,
246                hour24: hour24,
247                uniqueId: clockId,
248                beforeShow: function() {
249                    that.@gwt.material.design.addins.client.timepicker.MaterialTimePicker::beforeShow()();
250                },
251                afterShow: function() {
252                    that.@gwt.material.design.addins.client.timepicker.MaterialTimePicker::afterShow()();
253                },
254                afterHide: function() {
255                    var hour = $wnd.jQuery('#' + clockId).find('.lolliclock-hours').find('.lolliclock-time-new').html();
256                    var minutes = $wnd.jQuery('#' + clockId).find('.lolliclock-minutes').find('.lolliclock-time-new').html();
257                    var suffix = $wnd.jQuery('#' + clockId).find('.lolliclock-am-pm').html();
258                    var time =  hour + ':' + minutes + " " + suffix;
259                    that.@gwt.material.design.addins.client.timepicker.MaterialTimePicker::afterHide(*)();
260                }
261            });
262            $wnd.jQuery(e).blur();
263        });
264    }-*/;
265
266
267    /**
268     * Called after the lolliclock event <code>afterShow</code>.
269     */
270    protected void beforeShow() {
271
272        this.input.getElement().blur();
273
274        // Add class 'valid' for visual feedback.
275        this.validMixin.setOn(true);
276    }
277
278    /**
279     * Called after the lolliclock event <code>afterShow</code>.
280     */
281    protected void afterShow() {
282        this.fireOpenEvent();
283    }
284
285    /**
286     * Called after the lolliclock event <code>afterHide</code>.
287     *
288     * @param time
289     *            The time given by lolliclock in 12-hours <code>hh:mm aa</code>
290     *            format.
291     */
292    protected void afterHide() {
293
294        String timeString = this.getTime(this.input.getElement());
295
296        Date parsedDate = null;
297
298        if(timeString.equals("") == false && timeString != null) {
299            try {
300                if(this.hour24 == true) {
301                    DateTimeFormat hour24DateTimeFormat = DateTimeFormat.getFormat("HH:mm");
302                    parsedDate = hour24DateTimeFormat.parse(timeString);
303                } else {
304                    DateTimeFormat hour12DateTimeFormat = DateTimeFormat.getFormat("hh:mm aa");
305                    parsedDate = hour12DateTimeFormat.parse(timeString);
306                }
307            } catch(Exception e) {
308                // Silently catch parse errors
309            }
310        }
311
312        this.setValue(parsedDate);
313
314        // Remove class 'valid' after hide.
315        this.validMixin.setOn(false);
316
317        this.fireCloseEvent();
318    }
319
320    protected native String getTime(Element e)/*-{
321        return $wnd.jQuery(e).val();
322    }-*/;
323
324    @Override
325    public void setEnabled(boolean enabled) {
326        this.input.setEnabled(enabled);
327    }
328
329    @Override
330    public HandlerRegistration addCloseHandler(final CloseHandler<Date> handler) {
331        return this.addHandler(new CloseHandler<Date>() {
332            @Override
333            public void onClose(CloseEvent<Date> event) {
334                if(isEnabled()){
335                    handler.onClose(event);
336                }
337            }
338        }, CloseEvent.getType());
339    }
340
341    @Override
342    public HandlerRegistration addOpenHandler(final OpenHandler<Date> handler) {
343        return this.addHandler(new OpenHandler<Date>() {
344            @Override
345            public void onOpen(OpenEvent<Date> event) {
346                if(isEnabled()){
347                    handler.onOpen(event);
348                }
349            }
350        }, OpenEvent.getType());
351    }
352
353    protected void fireCloseEvent() {
354        CloseEvent.fire(this, this.time);
355    }
356
357    protected void fireOpenEvent() {
358        OpenEvent.fire(this, this.time);
359    }
360
361    protected void fireValueChangeEvent() {
362        ValueChangeEvent.fire(this, this.time);
363    }
364
365    @Override
366    public void clear() {
367        this.clearTimePickerValue(this.input.getElement());
368    }
369
370    protected native void clearTimePickerValue(Element e) /*-{
371        $wnd.jQuery(e).val('');
372    }-*/;
373
374    @Override
375    public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<Date> handler) {
376        return this.addHandler(new ValueChangeHandler<Date>() {
377            @Override
378            public void onValueChange(ValueChangeEvent<Date> event) {
379                if(isEnabled()){
380                    handler.onValueChange(event);
381                }
382            }
383        }, ValueChangeEvent.getType());
384    }
385
386    @Override
387    public Date getValue() {
388        return this.time;
389    }
390
391    @Override
392    public void setValue(Date time) {
393        this.setValue(time, true);
394    }
395
396    @Override
397    public void setValue(Date time, boolean fireEvents) {
398
399        if(this.time != null) {
400            if(this.time.equals(time)) {
401                return;
402            }
403        }
404
405        if(this.time == time) {
406            return;
407        }
408
409        this.time = time;
410
411        String timeString = null;
412
413        if(this.hour24 == true) {
414            DateTimeFormat hour24DateTimeFormat = DateTimeFormat.getFormat("HH:mm");
415            timeString = hour24DateTimeFormat.format(time);
416        } else {
417            DateTimeFormat hour12DateTimeFormat = DateTimeFormat.getFormat("hh:mm aa");
418            timeString = hour12DateTimeFormat.format(time);
419        }
420
421        this.setValue(this.input.getElement(), timeString);
422
423        if(fireEvents == true) {
424            this.fireValueChangeEvent();
425        }
426    }
427
428    protected native void setValue(Element e, String time) /*-{
429        $wnd.jQuery(e).val(time);
430    }-*/;
431
432    @Override
433    public MaterialIcon getIcon() {
434        return icon;
435    }
436
437    @Override
438    public void setIconType(IconType iconType) {
439        icon.setIconType(iconType);
440        icon.setIconPrefix(true);
441        lblError.setPaddingLeft(44);
442        panel.insert(icon, 0);
443    }
444
445    @Override
446    public void setIconPosition(IconPosition position) {
447        icon.setIconPosition(position);
448    }
449
450    @Override
451    public void setIconSize(IconSize size) {
452        icon.setIconSize(size);
453    }
454
455    @Override
456    public void setIconFontSize(double size, Style.Unit unit) {
457        icon.setIconFontSize(size, unit);
458    }
459
460    @Override
461    public void setIconColor(String iconColor) {
462        icon.setIconColor(iconColor);
463    }
464
465    @Override
466    public void setIconPrefix(boolean prefix) {
467        icon.setIconPrefix(prefix);
468    }
469
470    @Override
471    public boolean isIconPrefix() {
472        return icon.isIconPrefix();
473    }
474}