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 }