001    package org.gwtbootstrap3.extras.datepicker.client.ui.base;
002    
003    /*
004     * #%L
005     * GwtBootstrap3
006     * %%
007     * Copyright (C) 2013 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    
023    import com.google.gwt.core.client.ScriptInjector;
024    import com.google.gwt.dom.client.Element;
025    import com.google.gwt.dom.client.Style;
026    import com.google.gwt.editor.client.IsEditor;
027    import com.google.gwt.editor.client.LeafValueEditor;
028    import com.google.gwt.editor.client.adapters.TakesValueEditor;
029    import com.google.gwt.event.logical.shared.ValueChangeEvent;
030    import com.google.gwt.event.logical.shared.ValueChangeHandler;
031    import com.google.gwt.event.shared.HandlerRegistration;
032    import com.google.gwt.i18n.client.DateTimeFormat;
033    import com.google.gwt.user.client.Event;
034    import com.google.gwt.user.client.ui.*;
035    import org.gwtbootstrap3.client.shared.event.HideEvent;
036    import org.gwtbootstrap3.client.shared.event.HideHandler;
037    import org.gwtbootstrap3.client.shared.event.ShowEvent;
038    import org.gwtbootstrap3.client.shared.event.ShowHandler;
039    import org.gwtbootstrap3.client.ui.TextBox;
040    import org.gwtbootstrap3.client.ui.base.HasId;
041    import org.gwtbootstrap3.client.ui.base.HasPlaceholder;
042    import org.gwtbootstrap3.client.ui.base.HasResponsiveness;
043    import org.gwtbootstrap3.client.ui.base.ValueBoxBase;
044    import org.gwtbootstrap3.client.ui.base.helper.StyleHelper;
045    import org.gwtbootstrap3.client.ui.constants.DeviceSize;
046    import org.gwtbootstrap3.extras.datepicker.client.ui.base.constants.*;
047    import org.gwtbootstrap3.extras.datepicker.client.ui.base.events.*;
048    
049    import java.util.Date;
050    import java.util.HashMap;
051    import java.util.Map;
052    
053    /**
054     * @author Joshua Godi
055     */
056    public class DatePickerBase extends Widget
057            implements HasEnabled, HasId, HasResponsiveness, HasVisibility, HasPlaceholder, HasAutoClose, HasDaysOfWeekDisabled, HasEndDate, HasForceParse,
058            HasFormat, HasHighlightToday, HasKeyboardNavigation, HasMinView, HasShowTodayButton, HasStartDate, HasStartView, HasViewSelect, HasWeekStart,
059            HasDateTimePickerHandlers, HasLanguage, HasName, HasValue<Date>, HasPosition, IsEditor<LeafValueEditor<Date>> {
060    
061        // Check http://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/DateTimeFormat.html
062        // for more information on syntax
063        private static final Map<Character, Character> DATE_TIME_FORMAT_MAP = new HashMap<Character, Character>();
064    
065        static {
066            DATE_TIME_FORMAT_MAP.put('m', 'M'); // months
067        }
068    
069        private final TextBox textBox;
070        private DateTimeFormat dateTimeFormat;
071        private final DateTimeFormat startEndDateFormat = DateTimeFormat.getFormat("MM-dd-yyyy");
072        private LeafValueEditor<Date> editor;
073    
074        /**
075         * DEFAULT values
076         */
077        private String format = "mm/dd/yyyy";
078        private DatePickerDayOfWeek weekStart = DatePickerDayOfWeek.SUNDAY;
079        private DatePickerDayOfWeek[] daysOfWeekDisabled = {};
080        private boolean autoClose = false;
081        private DatePickerMinView startView = DatePickerMinView.DAY;
082        private DatePickerMinView minView = DatePickerMinView.DAY;
083    
084        private boolean showTodayButton = false;
085        private boolean highlightToday = false;
086        private boolean keyboardNavigation = true;
087        private boolean forceParse = true;
088    
089        private DatePickerMinView viewSelect = DatePickerMinView.DAY;
090    
091        private Widget container = null;
092        private DatePickerLanguage language = DatePickerLanguage.EN;
093        private DatePickerPosition position = DatePickerPosition.TOP_LEFT;
094    
095        public DatePickerBase() {
096            textBox = new TextBox();
097            setElement((Element) textBox.getElement());
098            setFormat(format);
099        }
100    
101        public void setContainer(final Widget container) {
102            this.container = container;
103        }
104    
105        public Widget getContainer() {
106            return container;
107        }
108    
109        public TextBox getTextBox() {
110            return textBox;
111        }
112    
113        public void setAlignment(final ValueBoxBase.TextAlignment align) {
114            textBox.setAlignment(align);
115        }
116    
117        @Override
118        public void setPlaceholder(final String placeHolder) {
119            textBox.setPlaceholder(placeHolder);
120        }
121    
122        @Override
123        public String getPlaceholder() {
124            return textBox.getPlaceholder();
125        }
126    
127        public void setReadOnly(final boolean readOnly) {
128            textBox.setReadOnly(readOnly);
129        }
130    
131        public boolean isReadOnly() {
132            return textBox.isReadOnly();
133        }
134    
135        @Override
136        public boolean isEnabled() {
137            return textBox.isEnabled();
138        }
139    
140        @Override
141        public void setEnabled(final boolean enabled) {
142            textBox.setEnabled(enabled);
143        }
144    
145        @Override
146        public void setId(final String id) {
147            textBox.setId(id);
148        }
149    
150        @Override
151        public String getId() {
152            return textBox.getId();
153        }
154    
155        @Override
156        public void setName(final String name) {
157            textBox.setName(name);
158        }
159    
160        @Override
161        public String getName() {
162            return textBox.getName();
163        }
164    
165        @Override
166        public void setVisibleOn(final DeviceSize deviceSize) {
167            StyleHelper.setVisibleOn(this, deviceSize);
168        }
169    
170        @Override
171        public void setHiddenOn(final DeviceSize deviceSize) {
172            StyleHelper.setHiddenOn(this, deviceSize);
173        }
174    
175        @Override
176        public void setLanguage(final DatePickerLanguage language) {
177            this.language = language;
178    
179            // Inject the JS for the language
180            if (language.getJs() != null) {
181                ScriptInjector.fromString(language.getJs().getText()).setWindow(ScriptInjector.TOP_WINDOW).inject();
182            }
183        }
184    
185        @Override
186        public DatePickerLanguage getLanguage() {
187            return language;
188        }
189    
190        @Override
191        public void setPosition(final DatePickerPosition position) {
192            this.position = position;
193        }
194    
195        @Override
196        public DatePickerPosition getPosition() {
197            return position;
198        }
199    
200        /**
201         * Call this whenever changing any settings: minView, startView, format, etc. If you are changing
202         * format and date value, the updates must take in such order:
203         * <p/>
204         * locales.cache.1.4.0. DateTimePicker.reload()
205         * 2. DateTimePicker.setValue(newDate); // Date newDate.
206         * <p/>
207         * Otherwise date value is not updated.
208         */
209        public void reload() {
210            configure();
211        }
212    
213        public void show() {
214            show(getElement());
215        }
216    
217        public void hide() {
218            hide(getElement());
219        }
220    
221        @Override
222        public void setAutoClose(final boolean autoClose) {
223            this.autoClose = autoClose;
224        }
225    
226        @Override
227        public void onShow(final Event e) {
228            // On show we put focus on the textbox
229            textBox.setFocus(true);
230    
231            fireEvent(new ShowEvent(e));
232        }
233    
234        @Override
235        public HandlerRegistration addShowHandler(final ShowHandler showHandler) {
236            return addHandler(showHandler, ShowEvent.getType());
237        }
238    
239        @Override
240        public void onHide(final Event e) {
241            // On hide we remove focus from the textbox
242            textBox.setFocus(false);
243    
244            fireEvent(new HideEvent(e));
245        }
246    
247        @Override
248        public HandlerRegistration addHideHandler(final HideHandler hideHandler) {
249            return addHandler(hideHandler, HideEvent.getType());
250        }
251    
252        @Override
253        public void onChangeDate(final Event e) {
254            fireEvent(new ChangeDateEvent(e));
255        }
256    
257        @Override
258        public HandlerRegistration addChangeDateHandler(final ChangeDateHandler changeDateHandler) {
259            return addHandler(changeDateHandler, ChangeDateEvent.getType());
260        }
261    
262        @Override
263        public void onChangeYear(final Event e) {
264            fireEvent(new ChangeYearEvent(e));
265        }
266    
267        @Override
268        public HandlerRegistration addChangeYearHandler(final ChangeYearHandler changeYearHandler) {
269            return addHandler(changeYearHandler, ChangeYearEvent.getType());
270        }
271    
272        @Override
273        public void onChangeMonth(final Event e) {
274            fireEvent(new ChangeMonthEvent(e));
275        }
276    
277        @Override
278        public HandlerRegistration addChangeMonthHandler(final ChangeMonthHandler changeMonthHandler) {
279            return addHandler(changeMonthHandler, ChangeMonthEvent.getType());
280        }
281    
282        @Override
283        public void onClearDate(final Event e) {
284            fireEvent(new ClearDateEvent(e));
285        }
286    
287        @Override
288        public HandlerRegistration addClearDateHandler(final ClearDateHandler clearDateHandler) {
289            return addHandler(clearDateHandler, ClearDateEvent.getType());
290        }
291    
292        @Override
293        public void setDaysOfWeekDisabled(final DatePickerDayOfWeek... daysOfWeekDisabled) {
294            setDaysOfWeekDisabled(getElement(), toDaysOfWeekDisabledString(daysOfWeekDisabled));
295        }
296    
297        @Override
298        public void setEndDate(final Date endDate) {
299            // Has to be in the format YYYY-MM-DD
300            setEndDate(startEndDateFormat.format(endDate));
301        }
302    
303        @Override
304        public void setEndDate(final String endDate) {
305            // Has to be in the format YYYY-MM-DD
306            setEndDate(getElement(), endDate);
307        }
308    
309        @Override
310        public void clearEndDate() {
311            setStartDate(getElement(), null);
312        }
313    
314        @Override
315        public void setForceParse(final boolean forceParse) {
316            this.forceParse = forceParse;
317        }
318    
319        @Override
320        public void setHighlightToday(final boolean highlightToday) {
321            this.highlightToday = highlightToday;
322        }
323    
324        @Override
325        public void setHasKeyboardNavigation(final boolean hasKeyboardNavigation) {
326            this.keyboardNavigation = hasKeyboardNavigation;
327        }
328    
329        @Override
330        public void setMinView(final DatePickerMinView datePickerMinView) {
331            this.minView = datePickerMinView;
332    
333            // We keep the view select the same as the min view
334            if (viewSelect != minView) {
335                setViewSelect(datePickerMinView);
336            }
337        }
338    
339        @Override
340        public void setShowTodayButton(final boolean showTodayButton) {
341            this.showTodayButton = showTodayButton;
342        }
343    
344        @Override
345        public void setStartDate(final Date startDate) {
346            // Has to be in the format DD-MM-YYYY
347            setStartDate(startEndDateFormat.format(startDate));
348        }
349    
350        @Override
351        public void setStartDate(final String startDate) {
352            // Has to be in the format DD-MM-YYYY
353            setStartDate(getElement(), startDate);
354        }
355    
356        @Override
357        public void clearStartDate() {
358            setStartDate(getElement(), null);
359        }
360    
361        @Override
362        public void setStartView(final DatePickerMinView datePickerMinView) {
363            this.startView = datePickerMinView;
364        }
365    
366        @Override
367        public void setViewSelect(final DatePickerMinView datePickerMinView) {
368            this.viewSelect = datePickerMinView;
369    
370            // We keep the min view the same as the view select
371            if (viewSelect != minView) {
372                setMinView(datePickerMinView);
373            }
374        }
375    
376        @Override
377        public void setWeekStart(final DatePickerDayOfWeek weekStart) {
378            this.weekStart = weekStart;
379        }
380    
381        @Override
382        public void setFormat(final String format) {
383            this.format = format;
384    
385            // Get the old value
386            final Date oldValue = getValue();
387    
388            // Make the new DateTimeFormat
389            setDateTimeFormat(format);
390    
391            if (oldValue != null) {
392                setValue(oldValue);
393            }
394        }
395    
396        private void setDateTimeFormat(final String format) {
397            final StringBuilder fb = new StringBuilder(format);
398            for (int i = 0; i < fb.length(); i++) {
399                if (DATE_TIME_FORMAT_MAP.containsKey(fb.charAt(i))) {
400                    fb.setCharAt(i, DATE_TIME_FORMAT_MAP.get(fb.charAt(i)));
401                }
402            }
403    
404            this.dateTimeFormat = DateTimeFormat.getFormat(fb.toString());
405        }
406    
407        @Override
408        public Date getValue() {
409            try {
410                return dateTimeFormat != null && textBox.getValue() != null ? dateTimeFormat.parse(textBox.getValue()) : null;
411            } catch (final Exception e) {
412                return null;
413            }
414        }
415    
416        public String getBaseValue() {
417            return textBox.getValue();
418        }
419    
420        @Override
421        public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<Date> dateValueChangeHandler) {
422            return addHandler(dateValueChangeHandler, ValueChangeEvent.getType());
423        }
424    
425        @Override
426        public void setValue(final Date value) {
427            setValue(value, false);
428        }
429    
430        @Override
431        public void setValue(final Date value, final boolean fireEvents) {
432            textBox.setValue(value != null ? dateTimeFormat.format(value) : null);
433            update(textBox.getElement());
434    
435            if (fireEvents) {
436                ValueChangeEvent.fire(DatePickerBase.this, value);
437            }
438        }
439    
440        @Override
441        public LeafValueEditor<Date> asEditor() {
442            if (editor == null) {
443                editor = TakesValueEditor.of(this);
444            }
445            return editor;
446        }
447    
448        /**
449         * {@inheritDoc}
450         */
451        @Override
452        protected void onLoad() {
453            super.onLoad();
454            configure();
455    
456            // With the new update the parent must have position: relative for positioning to work
457            if (getElement().getParentElement() != null) {
458                getElement().getParentElement().getStyle().setPosition(Style.Position.RELATIVE);
459            }
460        }
461    
462        @Override
463        protected void onUnload() {
464            super.onUnload();
465            remove(getElement());
466        }
467    
468        protected void configure() {
469            // If the user hasn't specified the container, default to the widget's parent
470            // This makes sure the modal scroll with the content correctly
471            if (container == null) {
472                configure(this, this.getParent());
473            } else {
474                configure(this, container);
475            }
476        }
477    
478        protected void configure(final Widget w, final Widget container) {
479            w.getElement().setAttribute("data-date-format", format);
480    
481            // If configuring not for the first time, datepicker must be removed first.
482            this.remove(w.getElement());
483    
484            configure(w.getElement(), container.getElement(), format, weekStart.getValue(), toDaysOfWeekDisabledString(daysOfWeekDisabled), autoClose,
485                    startView.getValue(), minView.getValue(), showTodayButton, highlightToday, keyboardNavigation, forceParse, viewSelect.getValue(),
486                    language.getCode(), position.getPosition());
487        }
488    
489        protected void execute(final String cmd) {
490            execute(getElement(), cmd);
491        }
492    
493        private native void execute(Element e, String cmd) /*-{
494            $wnd.jQuery(e).datepicker(cmd);
495        }-*/;
496    
497        private native void remove(Element e) /*-{
498            $wnd.jQuery(e).datepicker('remove');
499            $wnd.jQuery(e).off('show');
500            $wnd.jQuery(e).off('hide');
501            $wnd.jQuery(e).off('changeDate');
502            $wnd.jQuery(e).off('changeYear');
503            $wnd.jQuery(e).off('changeMonth');
504            $wnd.jQuery(e).off('clearDate');
505        }-*/;
506    
507        private native void show(Element e) /*-{
508            $wnd.jQuery(e).datepicker('show');
509        }-*/;
510    
511        private native void hide(Element e) /*-{
512            $wnd.jQuery(e).datepicker('hide');
513        }-*/;
514    
515        private native void update(Element e) /*-{
516            $wnd.jQuery(e).datepicker('update');
517        }-*/;
518    
519        private native void setStartDate(Element e, String startDate) /*-{
520            $wnd.jQuery(e).datepicker('setStartDate', startDate);
521        }-*/;
522    
523        private native void setEndDate(Element e, String endDate) /*-{
524            $wnd.jQuery(e).datepicker('setEndDate', endDate);
525        }-*/;
526    
527        private native void setDaysOfWeekDisabled(Element e, String daysOfWeekDisabled) /*-{
528            $wnd.jQuery(e).datepicker('setDaysOfWeekDisabled', daysOfWeekDisabled);
529        }-*/;
530    
531        protected native void configure(Element e, Element p, String format, int weekStart, String daysOfWeekDisabled, boolean autoClose, int startView,
532                                        int minViewMode, boolean todayBtn, boolean highlightToday, boolean keyboardNavigation, boolean forceParse, int viewSelect, String language,
533                                        String orientation) /*-{
534    
535            if (todayBtn) {
536                todayBtn = "linked";
537            }
538    
539            var that = this;
540            $wnd.jQuery(e).datepicker({
541                format: format,
542                language: language,
543                weekStart: weekStart,
544                daysOfWeekDisabled: daysOfWeekDisabled,
545                autoclose: autoClose,
546                startView: startView,
547                minViewMode: minViewMode,
548                todayBtn: todayBtn,
549                todayHighlight: highlightToday,
550                keyboardNavigation: keyboardNavigation,
551                forceParse: forceParse,
552                orientation: orientation,
553                container: p
554            })
555                .on('show', function (e) {
556                    that.@org.gwtbootstrap3.extras.datepicker.client.ui.base.DatePickerBase::onShow(Lcom/google/gwt/user/client/Event;)(e);
557                })
558                .on("hide", function (e) {
559                    that.@org.gwtbootstrap3.extras.datepicker.client.ui.base.DatePickerBase::onHide(Lcom/google/gwt/user/client/Event;)(e);
560                })
561                .on("changeDate", function (e) {
562                    that.@org.gwtbootstrap3.extras.datepicker.client.ui.base.DatePickerBase::onChangeDate(Lcom/google/gwt/user/client/Event;)(e);
563                })
564                .on("changeYear", function (e) {
565                    that.@org.gwtbootstrap3.extras.datepicker.client.ui.base.DatePickerBase::onChangeYear(Lcom/google/gwt/user/client/Event;)(e);
566                })
567                .on("changeMonth", function (e) {
568                    that.@org.gwtbootstrap3.extras.datepicker.client.ui.base.DatePickerBase::onChangeMonth(Lcom/google/gwt/user/client/Event;)(e);
569                })
570                .on("clearDate", function (e) {
571                    that.@org.gwtbootstrap3.extras.datepicker.client.ui.base.DatePickerBase::onClearDate(Lcom/google/gwt/user/client/Event;)(e);
572                });
573        }-*/;
574    
575        protected String toDaysOfWeekDisabledString(final DatePickerDayOfWeek... datePickerDayOfWeeks) {
576            this.daysOfWeekDisabled = datePickerDayOfWeeks;
577    
578            final StringBuilder builder = new StringBuilder();
579    
580            if (datePickerDayOfWeeks != null) {
581                int i = 0;
582                for (final DatePickerDayOfWeek dayOfWeek : datePickerDayOfWeeks) {
583                    builder.append(dayOfWeek.getValue());
584    
585                    i++;
586                    if (i < datePickerDayOfWeeks.length) {
587                        builder.append(",");
588                    }
589                }
590            }
591            return builder.toString();
592        }
593    }