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