001package org.gwtbootstrap3.extras.datetimepicker.client.ui.base;
002
003/*
004 * #%L
005 * GwtBootstrap3
006 * %%
007 * Copyright (C) 2016 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
023import java.util.Date;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.gwtbootstrap3.client.shared.event.HideEvent;
029import org.gwtbootstrap3.client.shared.event.HideHandler;
030import org.gwtbootstrap3.client.shared.event.ShowEvent;
031import org.gwtbootstrap3.client.shared.event.ShowHandler;
032import org.gwtbootstrap3.client.ui.TextBox;
033import org.gwtbootstrap3.client.ui.base.HasId;
034import org.gwtbootstrap3.client.ui.base.HasPlaceholder;
035import org.gwtbootstrap3.client.ui.base.HasReadOnly;
036import org.gwtbootstrap3.client.ui.base.HasResponsiveness;
037import org.gwtbootstrap3.client.ui.base.ValueBoxBase;
038import org.gwtbootstrap3.client.ui.base.helper.StyleHelper;
039import org.gwtbootstrap3.client.ui.base.mixin.BlankValidatorMixin;
040import org.gwtbootstrap3.client.ui.base.mixin.ErrorHandlerMixin;
041import org.gwtbootstrap3.client.ui.constants.DeviceSize;
042import org.gwtbootstrap3.client.ui.form.error.ErrorHandler;
043import org.gwtbootstrap3.client.ui.form.error.ErrorHandlerType;
044import org.gwtbootstrap3.client.ui.form.error.HasErrorHandler;
045import org.gwtbootstrap3.client.ui.form.validator.HasBlankValidator;
046import org.gwtbootstrap3.client.ui.form.validator.HasValidators;
047import org.gwtbootstrap3.client.ui.form.validator.ValidationChangedEvent.ValidationChangedHandler;
048import org.gwtbootstrap3.client.ui.form.validator.Validator;
049import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.DateTimePickerDayOfWeek;
050import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.DateTimePickerFormatViewType;
051import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.DateTimePickerLanguage;
052import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.DateTimePickerPosition;
053import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.DateTimePickerView;
054import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasAutoClose;
055import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasDateTimePickerHandlers;
056import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasDaysOfWeekDisabled;
057import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasEndDate;
058import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasForceParse;
059import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasFormat;
060import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasHighlightToday;
061import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasKeyboardNavigation;
062import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasLanguage;
063import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasMaxView;
064import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasMinView;
065import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasMinuteStep;
066import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasPosition;
067import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasShowMeridian;
068import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasShowTodayButton;
069import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasShowClearButton;
070import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasStartDate;
071import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasStartView;
072import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasViewSelect;
073import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.constants.HasWeekStart;
074import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.events.ChangeDateEvent;
075import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.events.ChangeDateHandler;
076import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.events.ChangeMonthEvent;
077import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.events.ChangeMonthHandler;
078import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.events.ChangeYearEvent;
079import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.events.ChangeYearHandler;
080import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.events.OutOfRangeEvent;
081import org.gwtbootstrap3.extras.datetimepicker.client.ui.base.events.OutOfRangeHandler;
082
083import com.google.gwt.core.client.ScriptInjector;
084import com.google.gwt.dom.client.Element;
085import com.google.gwt.editor.client.EditorError;
086import com.google.gwt.editor.client.HasEditorErrors;
087import com.google.gwt.editor.client.LeafValueEditor;
088import com.google.gwt.event.dom.client.BlurEvent;
089import com.google.gwt.event.dom.client.BlurHandler;
090import com.google.gwt.event.logical.shared.ValueChangeEvent;
091import com.google.gwt.event.logical.shared.ValueChangeHandler;
092import com.google.gwt.event.shared.HandlerRegistration;
093import com.google.gwt.i18n.client.DateTimeFormat;
094import com.google.gwt.user.client.Event;
095import com.google.gwt.user.client.ui.HasEnabled;
096import com.google.gwt.user.client.ui.HasName;
097import com.google.gwt.user.client.ui.HasValue;
098import com.google.gwt.user.client.ui.HasVisibility;
099import com.google.gwt.user.client.ui.Widget;
100
101/**
102 * @author Joshua Godi
103 * @author Steven Jardine
104 */
105public class DateTimePickerBase extends Widget implements HasEnabled, HasReadOnly, HasId, HasResponsiveness, HasVisibility,
106        HasPlaceholder, HasAutoClose, HasDaysOfWeekDisabled, HasEndDate, HasForceParse, HasFormat, HasHighlightToday,
107        HasKeyboardNavigation, HasMaxView, HasMinuteStep, HasMinView, HasShowMeridian, HasShowTodayButton, HasShowClearButton, HasStartDate,
108        HasStartView, HasViewSelect, HasWeekStart, HasDateTimePickerHandlers, HasLanguage, HasName, HasValue<Date>, HasPosition,
109        LeafValueEditor<Date>, HasEditorErrors<Date>, HasErrorHandler, HasValidators<Date>, HasBlankValidator<Date> {
110
111    static class DatePickerValidatorMixin extends BlankValidatorMixin<DateTimePickerBase, Date> {
112
113        private boolean showing = false;
114
115        public void setShowing(boolean showing) {
116            this.showing = showing;
117        }
118
119        public DatePickerValidatorMixin(DateTimePickerBase inputWidget, ErrorHandler errorHandler) {
120            super(inputWidget, errorHandler);
121        }
122
123        @Override
124        protected com.google.web.bindery.event.shared.HandlerRegistration setupBlurValidation() {
125            return getInputWidget().addDomHandler(new BlurHandler() {
126                @Override
127                public void onBlur(BlurEvent event) {
128                    getInputWidget().validate(!showing && getValidateOnBlur());
129                }
130            }, BlurEvent.getType());
131        }
132
133    }
134
135    // Check http://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/DateTimeFormat.html
136    // for more information on syntax
137    private static final Map<Character, Character> DATE_TIME_FORMAT_MAP = new HashMap<Character, Character>();
138    static {
139        DATE_TIME_FORMAT_MAP.put('h', 'H'); // 12/24 hours
140        DATE_TIME_FORMAT_MAP.put('H', 'h'); // 12/24 hours
141        DATE_TIME_FORMAT_MAP.put('m', 'M'); // months
142        DATE_TIME_FORMAT_MAP.put('i', 'm'); // minutes
143        DATE_TIME_FORMAT_MAP.put('p', 'a'); // meridian
144        DATE_TIME_FORMAT_MAP.put('P', 'a'); // meridian
145    }
146
147    private final TextBox textBox;
148    private DateTimeFormat dateTimeFormat;
149    private final DateTimeFormat startEndDateFormat = DateTimeFormat.getFormat("yyyy-MM-dd");
150    private LeafValueEditor<Date> editor;
151    private final ErrorHandlerMixin<Date> errorHandlerMixin = new ErrorHandlerMixin<Date>(this);
152    private final DatePickerValidatorMixin validatorMixin = new DatePickerValidatorMixin(this,
153                                                                                         errorHandlerMixin.getErrorHandler());
154
155    /**
156     * DEFAULT values
157     */
158    private String format = "mm/dd/yyyy hh:ii";
159    private DateTimePickerFormatViewType formatViewType = DateTimePickerFormatViewType.DATE_TIME;
160    private DateTimePickerDayOfWeek weekStart = DateTimePickerDayOfWeek.SUNDAY;
161    private DateTimePickerDayOfWeek[] daysOfWeekDisabled = {};
162    private boolean autoClose = false;
163    private DateTimePickerView startView = DateTimePickerView.MONTH;
164    private DateTimePickerView minView = DateTimePickerView.HOUR;
165    private DateTimePickerView maxView = DateTimePickerView.DECADE;
166    private boolean showTodayButton = false;
167    private boolean showClearButton = false;
168    private boolean highlightToday = false;
169    private boolean keyboardNavigation = true;
170    private boolean forceParse = true;
171    private int minuteStep = 5;
172    private DateTimePickerView viewSelect = DateTimePickerView.HOUR;
173    private boolean showMeridian = false;
174    private DateTimePickerLanguage language = DateTimePickerLanguage.EN;
175    private DateTimePickerPosition position = DateTimePickerPosition.BOTTOM_RIGHT;
176
177    public DateTimePickerBase() {
178        textBox = new TextBox();
179        setElement((Element) textBox.getElement());
180        setFormat(format);
181    }
182
183    public TextBox getTextBox() {
184        return textBox;
185    }
186
187    public void setAlignment(final ValueBoxBase.TextAlignment align) {
188        textBox.setAlignment(align);
189    }
190
191    /** {@inheritDoc} */
192    @Override
193    public void setPlaceholder(final String placeHolder) {
194        textBox.setPlaceholder(placeHolder);
195    }
196
197    /** {@inheritDoc} */
198    @Override
199    public String getPlaceholder() {
200        return textBox.getPlaceholder();
201    }
202
203    public void setReadOnly(final boolean readOnly) {
204        textBox.setReadOnly(readOnly);
205    }
206
207    public boolean isReadOnly() {
208        return textBox.isReadOnly();
209    }
210
211
212    /** {@inheritDoc} */
213    @Override
214    public boolean isEnabled() {
215        return textBox.isEnabled();
216    }
217
218    /** {@inheritDoc} */
219    @Override
220    public void setEnabled(final boolean enabled) {
221        textBox.setEnabled(enabled);
222    }
223
224    /** {@inheritDoc} */
225    @Override
226    public void setId(final String id) {
227        textBox.setId(id);
228    }
229
230    /** {@inheritDoc} */
231    @Override
232    public String getId() {
233        return textBox.getId();
234    }
235
236    /** {@inheritDoc} */
237    @Override
238    public void setName(final String name) {
239        textBox.setName(name);
240    }
241
242    /** {@inheritDoc} */
243    @Override
244    public String getName() {
245        return textBox.getName();
246    }
247
248    /** {@inheritDoc} */
249    @Override
250    public void setVisibleOn(final DeviceSize deviceSize) {
251        StyleHelper.setVisibleOn(this, deviceSize);
252    }
253
254    /** {@inheritDoc} */
255    @Override
256    public void setHiddenOn(final DeviceSize deviceSize) {
257        StyleHelper.setHiddenOn(this, deviceSize);
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public void setLanguage(final DateTimePickerLanguage language) {
263        this.language = language;
264
265        // Inject the JS for the language
266        if (language.getJs() != null) {
267            ScriptInjector.fromString(language.getJs().getText())
268                    .setWindow(ScriptInjector.TOP_WINDOW).inject();
269        }
270    }
271
272    /** {@inheritDoc} */
273    @Override
274    public DateTimePickerLanguage getLanguage() {
275        return language;
276    }
277
278    /** {@inheritDoc} */
279    @Override
280    public void setPosition(final DateTimePickerPosition position) {
281        this.position = position;
282    }
283
284    /** {@inheritDoc} */
285    @Override
286    public DateTimePickerPosition getPosition() {
287        return position;
288    }
289
290    /**
291     * Call this whenever changing any settings: minView, startView, format, etc. If you are changing
292     * format and date value, the updates must take in such order:
293     * <p/>
294     * 1. DateTimePicker.reload()
295     * 2. DateTimePicker.setValue(newDate); // Date newDate.
296     * <p/>
297     * Otherwise date value is not updated.
298     */
299    public void reload() {
300        configure();
301    }
302
303    public void show() {
304        show(getElement());
305    }
306
307    public void hide() {
308        hide(getElement());
309    }
310
311    /** {@inheritDoc} */
312    @Override
313    public void setAutoClose(final boolean autoClose) {
314        this.autoClose = autoClose;
315    }
316
317    /** {@inheritDoc} */
318    @Override
319    public void onShow(final Event e) {
320        validatorMixin.setShowing(true);
321        fireEvent(new ShowEvent(e));
322    }
323
324    /** {@inheritDoc} */
325    @Override
326    public HandlerRegistration addShowHandler(final ShowHandler showHandler) {
327        return addHandler(showHandler, ShowEvent.getType());
328    }
329
330    /** {@inheritDoc} */
331    @Override
332    public void onHide(final Event e) {
333        validatorMixin.setShowing(false);
334        validate(getValidateOnBlur());
335        fireEvent(new HideEvent(e));
336    }
337
338    /** {@inheritDoc} */
339    @Override
340    public HandlerRegistration addHideHandler(final HideHandler hideHandler) {
341        return addHandler(hideHandler, HideEvent.getType());
342    }
343
344    /** {@inheritDoc} */
345    @Override
346    public void onChangeDate(final Event e) {
347        fireEvent(new ChangeDateEvent(e));
348        ValueChangeEvent.fire(DateTimePickerBase.this, getValue());
349        hide();
350    }
351
352    /** {@inheritDoc} */
353    @Override
354    public HandlerRegistration addChangeDateHandler(final ChangeDateHandler changeDateHandler) {
355        return addHandler(changeDateHandler, ChangeDateEvent.getType());
356    }
357
358    /** {@inheritDoc} */
359    @Override
360    public void onChangeYear(final Event e) {
361        fireEvent(new ChangeYearEvent(e));
362    }
363
364    /** {@inheritDoc} */
365    @Override
366    public HandlerRegistration addChangeYearHandler(final ChangeYearHandler changeYearHandler) {
367        return addHandler(changeYearHandler, ChangeYearEvent.getType());
368    }
369
370    /** {@inheritDoc} */
371    @Override
372    public void onChangeMonth(final Event e) {
373        fireEvent(new ChangeMonthEvent(e));
374    }
375
376    /** {@inheritDoc} */
377    @Override
378    public HandlerRegistration addChangeMonthHandler(final ChangeMonthHandler changeMonthHandler) {
379        return addHandler(changeMonthHandler, ChangeMonthEvent.getType());
380    }
381
382    /** {@inheritDoc} */
383    @Override
384    public void onOutOfRange(final Event e) {
385        fireEvent(new OutOfRangeEvent(e));
386    }
387
388    /** {@inheritDoc} */
389    @Override
390    public HandlerRegistration addOutOfRangeHandler(final OutOfRangeHandler outOfRangeHandler) {
391        return addHandler(outOfRangeHandler, OutOfRangeEvent.getType());
392    }
393
394    /** {@inheritDoc} */
395    @Override
396    public void setDaysOfWeekDisabled(final DateTimePickerDayOfWeek... daysOfWeekDisabled) {
397        setDaysOfWeekDisabled(getElement(), toDaysOfWeekDisabledString(daysOfWeekDisabled));
398    }
399
400    /** {@inheritDoc} */
401    @Override
402    public void setEndDate(final Date endDate) {
403        // Has to be in the format YYYY-MM-DD
404        setEndDate(startEndDateFormat.format(endDate));
405    }
406
407    /** {@inheritDoc} */
408    @Override
409    public void setEndDate(final String endDate) {
410        // Has to be in the format YYYY-MM-DD
411        setEndDate(getElement(), endDate);
412    }
413
414    /** {@inheritDoc} */
415    @Override
416    public void clearEndDate() {
417        setStartDate(getElement(), null);
418    }
419
420    /** {@inheritDoc} */
421    @Override
422    public void setForceParse(final boolean forceParse) {
423        this.forceParse = forceParse;
424    }
425
426    /** {@inheritDoc} */
427    @Override
428    public void setHighlightToday(final boolean highlightToday) {
429        this.highlightToday = highlightToday;
430    }
431
432    /** {@inheritDoc} */
433    @Override
434    public void setHasKeyboardNavigation(final boolean hasKeyboardNavigation) {
435        this.keyboardNavigation = hasKeyboardNavigation;
436    }
437
438    /** {@inheritDoc} */
439    @Override
440    public void setMaxView(final DateTimePickerView dateTimePickerView) {
441        this.maxView = dateTimePickerView;
442    }
443
444    /** {@inheritDoc} */
445    @Override
446    public void setMinView(final DateTimePickerView dateTimePickerView) {
447        this.minView = dateTimePickerView;
448
449        // We keep the view select the same as the min view
450        if (viewSelect != minView) {
451            setViewSelect(dateTimePickerView);
452        }
453    }
454
455    /** {@inheritDoc} */
456    @Override
457    public void setMinuteStep(final int minuteStep) {
458        this.minuteStep = minuteStep;
459    }
460
461    /** {@inheritDoc} */
462    @Override
463    public void setShowMeridian(final boolean showMeridian) {
464        this.showMeridian = showMeridian;
465    }
466
467    /** {@inheritDoc} */
468    @Override
469    public void setShowTodayButton(final boolean showTodayButton) {
470        this.showTodayButton = showTodayButton;
471    }
472
473    /** {@inheritDoc} */
474    @Override
475    public void setShowClearButton(final boolean showClearButton) {
476        this.showClearButton = showClearButton;
477    }
478
479
480    /** {@inheritDoc} */
481    @Override
482    public void setStartDate(final Date startDate) {
483        // Has to be in the format YYYY-MM-DD
484        setStartDate(startEndDateFormat.format(startDate));
485    }
486
487    /** {@inheritDoc} */
488    @Override
489    public void setStartDate(final String startDate) {
490        // Has to be in the format YYYY-MM-DD
491        setStartDate(getElement(), startDate);
492    }
493
494    /** {@inheritDoc} */
495    @Override
496    public void clearStartDate() {
497        setStartDate(getElement(), null);
498    }
499
500    /** {@inheritDoc} */
501    @Override
502    public void setStartView(final DateTimePickerView dateTimePickerView) {
503        this.startView = dateTimePickerView;
504    }
505
506    /** {@inheritDoc} */
507    @Override
508    public void setViewSelect(final DateTimePickerView dateTimePickerView) {
509        this.viewSelect = dateTimePickerView;
510
511        // We keep the min view the same as the view select
512        if (viewSelect != minView) {
513            setMinView(dateTimePickerView);
514        }
515    }
516
517    /** {@inheritDoc} */
518    @Override
519    public void setWeekStart(final DateTimePickerDayOfWeek weekStart) {
520        this.weekStart = weekStart;
521    }
522
523    /**
524     * Convert GWT date time format to bootstrap date time format
525     *
526     * @param format date time format using GWT notation
527     * @return date time format using bootstrap notation
528     */
529    private static String toBootstrapDateFormat(final String format) {
530        String bootstrap_format = format;
531
532        // Replace long day name "EEEE" with "DD"
533        bootstrap_format = bootstrap_format.replace("EEEE", "DD");
534        // Replace short day name "EE" with "DD"
535        bootstrap_format = bootstrap_format.replaceAll("E{1,3}", "D");
536        // Replace minutes "m" with "i"
537        bootstrap_format = bootstrap_format.replaceAll("m", "i");
538        // Replace "H" with "h" and vice versa
539        bootstrap_format = bootstrap_format.replaceAll("H", "Q");
540        bootstrap_format = bootstrap_format.replaceAll("h", "H");
541        bootstrap_format = bootstrap_format.replaceAll("Q", "h");
542        // Replace "a" with "P" for AM/PM markers
543        bootstrap_format = bootstrap_format.replaceAll("a", "P");
544
545        // If there are at least 3 Ms there is month name in wording
546        if (bootstrap_format.contains("MMM")) {
547            // Replace long date month "MMMM" with "MM"
548            bootstrap_format = bootstrap_format.replace("MMMM", "MM");
549            // Replace month name "MMM" with "M"
550            bootstrap_format = bootstrap_format.replace("MMM", "M");
551        }
552        else {
553            // Replace month number with leading 0 "MM" with "mm"
554            bootstrap_format = bootstrap_format.replace("MM", "mm");
555            // Replace month number "M" with "m"
556            bootstrap_format = bootstrap_format.replace("M", "m");
557        }
558        if (!bootstrap_format.contains("yy")) {
559            // Replace full year format "y" with "yyyy"
560            bootstrap_format = bootstrap_format.replace("y", "yyyy");
561        }
562
563        return bootstrap_format;
564    }
565
566    /**
567     * Sets format of the date using GWT notation
568     *
569     * @param format date time format in GWT notation
570     */
571    public void setGWTFormat(final String format) {
572        this.format = toBootstrapDateFormat(format);
573
574        // Get the old value
575        final Date oldValue = getValue();
576
577        // Make the new DateTimeFormat
578        this.dateTimeFormat = DateTimeFormat.getFormat(format);
579
580        if (oldValue != null) {
581            setValue(oldValue);
582        }
583    }
584
585    /** {@inheritDoc} */
586    @Override
587    public void setFormat(final String format) {
588        this.format = format;
589
590        // Get the old value
591        final Date oldValue = getValue();
592
593        // Make the new DateTimeFormat
594        setDateTimeFormat(format);
595
596        if (oldValue != null) {
597            setValue(oldValue);
598        }
599    }
600
601    private void setDateTimeFormat(final String format) {
602        final StringBuilder fb = new StringBuilder(format);
603        for (int i = 0; i < fb.length(); i++) {
604            if (DATE_TIME_FORMAT_MAP.containsKey(fb.charAt(i))) {
605                fb.setCharAt(i, DATE_TIME_FORMAT_MAP.get(fb.charAt(i)));
606            }
607        }
608
609        this.dateTimeFormat = DateTimeFormat.getFormat(fb.toString());
610    }
611
612    /**
613     * Sets the format view type.
614     *
615     * @param formatViewType
616     * @see DateTimePickerFormatViewType
617     */
618    public void setFormatViewType(DateTimePickerFormatViewType formatViewType) {
619        if (formatViewType != null)
620            this.formatViewType = formatViewType;
621    }
622
623    /** {@inheritDoc} */
624    @Override
625    public Date getValue() {
626        try {
627            return dateTimeFormat != null && textBox.getValue() != null ? dateTimeFormat.parse(textBox.getValue()) : null;
628        } catch (final Exception e) {
629            return null;
630        }
631    }
632
633    public String getBaseValue() {
634        return textBox.getValue();
635    }
636
637    /** {@inheritDoc} */
638    @Override
639    public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<Date> dateValueChangeHandler) {
640        textBox.addValueChangeHandler(new ValueChangeHandler<String>() {
641            @Override
642            public void onValueChange(ValueChangeEvent<String> event) {
643                ValueChangeEvent.fire(DateTimePickerBase.this, getValue());
644            }
645        });
646        return addHandler(dateValueChangeHandler, ValueChangeEvent.getType());
647    }
648
649    /** {@inheritDoc} */
650    @Override
651    public void setValue(final Date value) {
652        setValue(value, false);
653    }
654
655    /** {@inheritDoc} */
656    @Override
657    public void setValue(final Date value, final boolean fireEvents) {
658        errorHandlerMixin.clearErrors();
659        textBox.setValue(value != null ? dateTimeFormat.format(value) : null);
660        update(textBox.getElement());
661
662        if (fireEvents) {
663            ValueChangeEvent.fire(DateTimePickerBase.this, value);
664        }
665    }
666
667    /** {@inheritDoc} */
668    @Override
669    protected void onLoad() {
670        super.onLoad();
671        configure();
672    }
673
674    /** {@inheritDoc} */
675    @Override
676    protected void onUnload() {
677        super.onUnload();
678        remove(getElement());
679    }
680
681    protected void configure() {
682        getElement().setAttribute("data-date-format", format);
683
684        // If configuring not for the first time, datetimepicker must be removed first.
685        this.remove(getElement());
686
687        configure(getElement(), format, formatViewType.getValue(), weekStart.getValue(), toDaysOfWeekDisabledString(daysOfWeekDisabled),
688                  autoClose, startView.getValue(), minView.getValue(), maxView.getValue(), showTodayButton, showClearButton, highlightToday,
689                  keyboardNavigation, forceParse, minuteStep, viewSelect.getValue(), showMeridian, language.getCode(), position.getPosition());
690    }
691
692    protected void execute(final String cmd) {
693        execute(getElement(), cmd);
694    }
695
696    private native void execute(Element e, String cmd) /*-{
697        $wnd.jQuery(e).datetimepicker(cmd);
698    }-*/;
699
700    private native void remove(Element e) /*-{
701        $wnd.jQuery(e).datetimepicker('remove');
702    }-*/;
703
704    private native void show(Element e) /*-{
705        $wnd.jQuery(e).datetimepicker('show');
706    }-*/;
707
708    private native void hide(Element e) /*-{
709        $wnd.jQuery(e).datetimepicker('hide');
710    }-*/;
711
712    private native void update(Element e) /*-{
713        $wnd.jQuery(e).datetimepicker('update');
714    }-*/;
715
716    private native void setStartDate(Element e, String startDate) /*-{
717        $wnd.jQuery(e).datetimepicker('setStartDate', startDate);
718    }-*/;
719
720    private native void setEndDate(Element e, String endDate) /*-{
721        $wnd.jQuery(e).datetimepicker('setEndDate', endDate);
722    }-*/;
723
724    private native void setDaysOfWeekDisabled(Element e, String daysOfWeekDisabled) /*-{
725        $wnd.jQuery(e).datetimepicker('setDaysOfWeekDisabled', daysOfWeekDisabled);
726    }-*/;
727
728    protected native void configure(Element e, String format, String formatViewType, int weekStart,
729                                    String daysOfWeekDisabled, boolean autoClose, int startView, int minView, int maxView, boolean todayBtn, boolean clearBtn,
730                                    boolean highlightToday, boolean keyboardNavigation, boolean forceParse, int minuteStep, int viewSelect,
731                                    boolean showMeridian, String language, String position) /*-{
732        var that = this;
733        $wnd.jQuery(e).datetimepicker({
734            format: format,
735            formatViewType: formatViewType,
736            language: language,
737            weekStart: weekStart,
738            daysOfWeekDisabled: daysOfWeekDisabled,
739            autoclose: autoClose,
740            startView: startView,
741            minView: minView,
742            maxView: maxView,
743            todayBtn: todayBtn,
744            clearBtn: clearBtn,
745            todayHighlight: highlightToday,
746            keyboardNavigation: keyboardNavigation,
747            forceParse: forceParse,
748            minuteStep: minuteStep,
749            showMeridian: showMeridian,
750            pickerPosition: position
751        })
752            .on('show', function (e) {
753                that.@org.gwtbootstrap3.extras.datetimepicker.client.ui.base.DateTimePickerBase::onShow(Lcom/google/gwt/user/client/Event;)(e);
754            })
755            .on("hide", function (e) {
756                that.@org.gwtbootstrap3.extras.datetimepicker.client.ui.base.DateTimePickerBase::onHide(Lcom/google/gwt/user/client/Event;)(e);
757            })
758            .on("changeDate", function (e) {
759                that.@org.gwtbootstrap3.extras.datetimepicker.client.ui.base.DateTimePickerBase::onChangeDate(Lcom/google/gwt/user/client/Event;)(e);
760            })
761            .on("changeYear", function (e) {
762                that.@org.gwtbootstrap3.extras.datetimepicker.client.ui.base.DateTimePickerBase::onChangeYear(Lcom/google/gwt/user/client/Event;)(e);
763            })
764            .on("changeMonth", function (e) {
765                that.@org.gwtbootstrap3.extras.datetimepicker.client.ui.base.DateTimePickerBase::onChangeMonth(Lcom/google/gwt/user/client/Event;)(e);
766            })
767            .on("outOfRange", function (e) {
768                that.@org.gwtbootstrap3.extras.datetimepicker.client.ui.base.DateTimePickerBase::onOutOfRange(Lcom/google/gwt/user/client/Event;)(e);
769            });
770    }-*/;
771
772    protected String toDaysOfWeekDisabledString(final DateTimePickerDayOfWeek... dateTimePickerDayOfWeeks) {
773        this.daysOfWeekDisabled = dateTimePickerDayOfWeeks;
774
775        final StringBuilder builder = new StringBuilder();
776
777        if (dateTimePickerDayOfWeeks != null) {
778            int i = 0;
779            for (final DateTimePickerDayOfWeek dayOfWeek : dateTimePickerDayOfWeeks) {
780                builder.append(dayOfWeek.getValue());
781
782                i++;
783                if (i < dateTimePickerDayOfWeeks.length) {
784                    builder.append(",");
785                }
786            }
787        }
788        return builder.toString();
789    }
790
791    /** {@inheritDoc} */
792    @Override
793    public com.google.web.bindery.event.shared.HandlerRegistration addValidationChangedHandler(ValidationChangedHandler handler) {
794        return validatorMixin.addValidationChangedHandler(handler);
795    }
796
797    /** {@inheritDoc} */
798    @Override
799    public boolean getAllowBlank() {
800        return validatorMixin.getAllowBlank();
801    }
802
803    /** {@inheritDoc} */
804    @Override
805    public void setAllowBlank(boolean allowBlank) {
806        validatorMixin.setAllowBlank(allowBlank);
807    }
808
809    /** {@inheritDoc} */
810    @Override
811    public void addValidator(Validator<Date> validator) {
812        validatorMixin.addValidator(validator);
813    }
814
815    /** {@inheritDoc} */
816    @Override
817    public boolean getValidateOnBlur() {
818        return validatorMixin.getValidateOnBlur();
819    }
820
821    /** {@inheritDoc} */
822    @Override
823    public boolean removeValidator(Validator<Date> validator) {
824        return validatorMixin.removeValidator(validator);
825    }
826
827    /** {@inheritDoc} */
828    @Override
829    public void reset() {
830        validatorMixin.reset();
831    }
832
833    /** {@inheritDoc} */
834    @Override
835    public void setValidateOnBlur(boolean validateOnBlur) {
836        validatorMixin.setValidateOnBlur(validateOnBlur);
837    }
838
839    /** {@inheritDoc} */
840    @Override
841    public void setValidators(@SuppressWarnings("unchecked") Validator<Date>... validators) {
842        validatorMixin.setValidators(validators);
843    }
844
845    /** {@inheritDoc} */
846    @Override
847    public boolean validate() {
848        return validatorMixin.validate();
849    }
850
851    /** {@inheritDoc} */
852    @Override
853    public boolean validate(boolean show) {
854        return validatorMixin.validate(show);
855    }
856
857    /** {@inheritDoc} */
858    @Override
859    public ErrorHandler getErrorHandler() {
860        return errorHandlerMixin.getErrorHandler();
861    }
862
863    /** {@inheritDoc} */
864    @Override
865    public void setErrorHandler(ErrorHandler errorHandler) {
866        errorHandlerMixin.setErrorHandler(errorHandler);
867        validatorMixin.setErrorHandler(errorHandler);
868    }
869
870    /** {@inheritDoc} */
871    @Override
872    public ErrorHandlerType getErrorHandlerType() {
873        return errorHandlerMixin.getErrorHandlerType();
874    }
875
876    /** {@inheritDoc} */
877    @Override
878    public void setErrorHandlerType(ErrorHandlerType errorHandlerType) {
879        errorHandlerMixin.setErrorHandlerType(errorHandlerType);
880    }
881
882    /** {@inheritDoc} */
883    @Override
884    public void showErrors(List<EditorError> errors) {
885        errorHandlerMixin.showErrors(errors);
886    }
887
888}