001package gwt.material.design.client.ui; 002 003/* 004 * #%L 005 * GwtMaterial 006 * %% 007 * Copyright (C) 2015 GwtMaterialDesign 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 com.google.gwt.core.client.JsDate; 024import com.google.gwt.core.client.Scheduler; 025import com.google.gwt.core.client.ScriptInjector; 026import com.google.gwt.dom.client.Document; 027import com.google.gwt.dom.client.Element; 028import com.google.gwt.dom.client.Style; 029import com.google.gwt.editor.client.EditorError; 030import com.google.gwt.editor.client.HasEditorErrors; 031import com.google.gwt.event.dom.client.BlurEvent; 032import com.google.gwt.event.dom.client.BlurHandler; 033import com.google.gwt.event.dom.client.HasBlurHandlers; 034import com.google.gwt.event.logical.shared.*; 035import com.google.gwt.event.shared.HandlerRegistration; 036import com.google.gwt.user.client.ui.HasValue; 037import gwt.material.design.client.base.*; 038import gwt.material.design.client.base.error.ErrorHandler; 039import gwt.material.design.client.base.error.ErrorHandlerType; 040import gwt.material.design.client.base.error.HasErrorHandler; 041import gwt.material.design.client.base.mixin.BlankValidatorMixin; 042import gwt.material.design.client.base.mixin.ErrorHandlerMixin; 043import gwt.material.design.client.base.mixin.ErrorMixin; 044import gwt.material.design.client.base.mixin.GridMixin; 045import gwt.material.design.client.base.validator.HasBlankValidator; 046import gwt.material.design.client.base.validator.HasValidators; 047import gwt.material.design.client.base.validator.ValidationChangedEvent.ValidationChangedHandler; 048import gwt.material.design.client.base.validator.Validator; 049import gwt.material.design.client.constants.*; 050import gwt.material.design.client.ui.html.DateInput; 051import gwt.material.design.client.ui.html.Label; 052 053import java.util.Date; 054import java.util.List; 055 056//@formatter:off 057 058/** 059 * Material Date Picker will provide a visual calendar to your apps. 060 * <p/> 061 * <h3>UiBinder Usage:</h3> 062 * {@code 063 * <m:MaterialDatePicker ui:field="datePicker"> 064 * } 065 * <h3>Java Usage:</h3> 066 * {@code 067 * datePicker.setDate(new Date()); 068 * } 069 * 070 * @author kevzlou7979 071 * @author Ben Dol 072 * @see <a href="http://gwt-material-demo.herokuapp.com/#pickers">Material Date Picker</a> 073 */ 074//@formatter:on 075public class MaterialDatePicker extends MaterialWidget implements HasGrid, HasError, HasOrientation, HasPlaceholder, 076 HasValue<Date>, HasOpenHandlers<MaterialDatePicker>, HasCloseHandlers<MaterialDatePicker>, HasEditorErrors<Date>, 077 HasErrorHandler, HasValidators<Date>, HasBlankValidator, HasBlurHandlers, HasIcon { 078 079 /** 080 * Enum for identifying various selection types for the picker. 081 */ 082 public enum MaterialDatePickerType { 083 DAY, 084 MONTH_DAY, 085 YEAR_MONTH_DAY, 086 YEAR 087 } 088 089 private String placeholder; 090 private Date date; 091 private Date dateMin; 092 private Date dateMax; 093 private String format = "dd mmmm yyyy"; 094 private DateInput dateInput; 095 private Label label = new Label(); 096 private MaterialLabel lblName = new MaterialLabel(); 097 private Element pickatizedDateInput; 098 private MaterialLabel lblError = new MaterialLabel(); 099 private DatePickerLanguage language; 100 101 private Orientation orientation = Orientation.PORTRAIT; 102 private MaterialDatePickerType selectionType = MaterialDatePickerType.DAY; 103 104 private final GridMixin<MaterialDatePicker> gridMixin = new GridMixin<>(this); 105 private final ErrorMixin<MaterialDatePicker, MaterialLabel> errorMixin; 106 private final ErrorHandlerMixin<Date> errorHandlerMixin = new ErrorHandlerMixin<>(this); 107 private final BlankValidatorMixin<MaterialDatePicker, Date> validatorMixin = new BlankValidatorMixin<>(this, errorHandlerMixin.getErrorHandler()); 108 109 private boolean initialized = false; 110 private MaterialIcon icon = new MaterialIcon(); 111 112 public MaterialDatePicker() { 113 super(Document.get().createDivElement(), "input-field"); 114 115 dateInput = new DateInput(); 116 add(dateInput); 117 118 label.add(lblName); 119 add(label); 120 121 add(lblError); 122 123 errorMixin = new ErrorMixin<>(this, lblError, dateInput); 124 } 125 126 @Override 127 protected void onAttach() { 128 super.onAttach(); 129 130 addStyleName(orientation.getCssName()); 131 pickatizedDateInput = initDatePicker(dateInput.getElement(), selectionType.name(), format); 132 initClickHandler(pickatizedDateInput, this); 133 134 label.getElement().setAttribute("for", getPickerId(pickatizedDateInput)); 135 136 this.initialized = true; 137 138 setDate(this.date); 139 setDateMin(this.dateMin); 140 setDateMax(this.dateMax); 141 setPlaceholder(this.placeholder); 142 } 143 144 @Override 145 protected void onDetach() { 146 super.onDetach(); 147 removeClickHandler(pickatizedDateInput, this); 148 } 149 150 @Override 151 public void clear() { 152 if (initialized) { 153 clearErrorOrSuccess(); 154 label.removeStyleName("active"); 155 dateInput.removeStyleName("valid"); 156 dateInput.clear(); 157 } 158 } 159 160 public void removeErrorModifiers() { 161 dateInput.addStyleName("valid"); 162 dateInput.removeStyleName("invalid"); 163 lblName.removeStyleName("green-text"); 164 lblName.removeStyleName("red-text"); 165 } 166 167 /** 168 * Sets the type of selection options (date, month, year,...). 169 * 170 * @param type if <code>null</code>, {@link MaterialDatePickerType#DAY} will be used as fallback. 171 */ 172 public void setDateSelectionType(MaterialDatePickerType type) { 173 if (type != null) { 174 this.selectionType = type; 175 } 176 } 177 178 native void removeClickHandler(Element picker, MaterialDatePicker parent) /*-{ 179 picker.pickadate('picker').off("close", "open", "set"); 180 }-*/; 181 182 native void initClickHandler(Element picker, MaterialDatePicker parent) /*-{ 183 picker.pickadate('picker').on({ 184 close: function () { 185 parent.@gwt.material.design.client.ui.MaterialDatePicker::onClose()(); 186 $wnd.jQuery('.picker').blur(); 187 }, 188 open: function () { 189 parent.@gwt.material.design.client.ui.MaterialDatePicker::onOpen()(); 190 }, 191 set: function (thingSet) { 192 193 if (thingSet.hasOwnProperty('clear')) { 194 parent.@gwt.material.design.client.ui.MaterialDatePicker::onClear()(); 195 } 196 else if (thingSet.select) { 197 parent.@gwt.material.design.client.ui.MaterialDatePicker::onSelect()(); 198 } 199 } 200 }); 201 }-*/; 202 203 void onClose() { 204 CloseEvent.fire(this, this); 205 } 206 207 void onOpen() { 208 label.addStyleName("active"); 209 dateInput.setFocus(true); 210 OpenEvent.fire(this, this); 211 } 212 213 void onSelect() { 214 label.addStyleName("active"); 215 dateInput.addStyleName("valid"); 216 ValueChangeEvent.fire(this, getValue()); 217 } 218 219 void onClear() { 220 clear(); 221 } 222 223 public static native String getPickerId(Element inputSrc) /*-{ 224 return inputSrc.pickadate('picker').get("id"); 225 }-*/; 226 227 public static native Element initDatePicker(Element inputSrc, String typeName, String format) /*-{ 228 var input; 229 if (typeName === "MONTH_DAY") { 230 input = $wnd.jQuery(inputSrc).pickadate({ 231 container: 'body', 232 selectYears: false, 233 selectMonths: true, 234 format: format 235 }); 236 } else if (typeName === "YEAR_MONTH_DAY") { 237 input = $wnd.jQuery(inputSrc).pickadate({ 238 container: 'body', 239 selectYears: true, 240 selectMonths: true, 241 format: format 242 }); 243 } else if (typeName === "YEAR") { 244 input = $wnd.jQuery(inputSrc).pickadate({ 245 container: 'body', 246 selectYears: true, 247 format: format 248 }); 249 } else { 250 input = $wnd.jQuery(inputSrc).pickadate({ 251 container: 'body', 252 format: format 253 }); 254 } 255 256 return input; 257 }-*/; 258 259 /** 260 * Sets the current date of the picker. 261 * 262 * @param date - must not be <code>null</code> 263 */ 264 public void setDate(Date date) { 265 setValue(date); 266 } 267 268 public Date getDateMin() { 269 return dateMin; 270 } 271 272 public void setDateMin(Date dateMin) { 273 this.dateMin = dateMin; 274 if (initialized && dateMin != null) { 275 setPickerDateMin(JsDate.create((double) dateMin.getTime()), pickatizedDateInput); 276 } 277 } 278 279 public native void setPickerDateMin(JsDate date, Element picker) /*-{ 280 picker.pickadate('picker').set('min', date); 281 }-*/; 282 283 public Date getDateMax() { 284 return dateMax; 285 } 286 287 public void setDateMax(Date dateMax) { 288 this.dateMax = dateMax; 289 if (initialized && dateMax != null) { 290 setPickerDateMax(JsDate.create((double) dateMax.getTime()), pickatizedDateInput); 291 } 292 } 293 294 public native void setPickerDateMax(JsDate date, Element picker) /*-{ 295 picker.pickadate('picker').set('max', date); 296 }-*/; 297 298 public native void setPickerDate(JsDate date, Element picker) /*-{ 299 picker.pickadate('picker').set('select', date, { muted: true }); 300 }-*/; 301 302 /** 303 * Same as calling {@link #getValue()} 304 */ 305 public Date getDate() { 306 return getPickerDate(); 307 } 308 309 protected Date getPickerDate() { 310 try { 311 JsDate selectedDate = getDatePickerValue(pickatizedDateInput); 312 return new Date((long) selectedDate.getTime()); 313 } catch (Exception e) { 314 e.printStackTrace(); 315 return null; 316 } 317 } 318 319 public static native JsDate getDatePickerValue(Element picker)/*-{ 320 return picker.pickadate('picker').get('select').obj; 321 }-*/; 322 323 /** 324 * Clears the values of the picker field. 325 */ 326 public void clearValues() { 327 if (pickatizedDateInput != null) { 328 clearValues(pickatizedDateInput); 329 } 330 } 331 332 public native void clearValues(Element picker) /*-{ 333 picker.pickadate('picker').clear(); 334 }-*/; 335 336 public String getPlaceholder() { 337 return placeholder; 338 } 339 340 public void setPlaceholder(String placeholder) { 341 this.placeholder = placeholder; 342 343 if (initialized && placeholder != null) { 344 lblName.setText(placeholder); 345 } 346 } 347 348 public MaterialDatePickerType getSelectionType() { 349 return selectionType; 350 } 351 352 public void setSelectionType(MaterialDatePickerType selectionType) { 353 if(initialized) { 354 throw new IllegalStateException("setSelectionType can be called only before initialization"); 355 } 356 this.selectionType = selectionType; 357 } 358 359 /** 360 * @return the orientation 361 */ 362 @Override 363 public Orientation getOrientation() { 364 return orientation; 365 } 366 367 /** 368 * @param orientation the orientation to set : can be Vertical or Horizontal 369 */ 370 @Override 371 public void setOrientation(Orientation orientation) { 372 if(initialized) { 373 throw new IllegalStateException("setOrientation can be called only before initialization"); 374 } 375 this.orientation = orientation; 376 } 377 378 @Override 379 public void setGrid(String grid) { 380 gridMixin.setGrid(grid); 381 } 382 383 @Override 384 public void setOffset(String offset) { 385 gridMixin.setOffset(offset); 386 } 387 388 @Override 389 public void setError(String error) { 390 errorMixin.setError(error); 391 392 removeErrorModifiers(); 393 lblName.setStyleName("red-text"); 394 dateInput.addStyleName("invalid"); 395 396 } 397 398 @Override 399 public void setSuccess(String success) { 400 errorMixin.setSuccess(success); 401 lblName.setStyleName("green-text"); 402 dateInput.addStyleName("valid"); 403 } 404 405 @Override 406 public void setHelperText(String helperText) { 407 errorMixin.setHelperText(helperText); 408 } 409 410 @Override 411 public void clearErrorOrSuccess() { 412 errorMixin.clearErrorOrSuccess(); 413 removeErrorModifiers(); 414 } 415 416 public String getFormat() { 417 return format; 418 } 419 420 /** 421 * To call before initialization 422 * @param format 423 */ 424 public void setFormat(String format) { 425 if(initialized) { 426 throw new IllegalStateException("setFormat can be called only before initialization"); 427 } 428 this.format = format; 429 } 430 431 @Override 432 public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<Date> handler) { 433 return addHandler(new ValueChangeHandler<Date>() { 434 @Override 435 public void onValueChange(ValueChangeEvent<Date> event) { 436 if(isEnabled()){ 437 handler.onValueChange(event); 438 } 439 } 440 }, ValueChangeEvent.getType()); 441 } 442 443 @Override 444 public Date getValue() { 445 return getPickerDate(); 446 } 447 448 @Override 449 public void setValue(Date value) { 450 setValue(value, false); 451 } 452 453 @Override 454 public void setValue(Date value, boolean fireEvents) { 455 if (value == null) { 456 return; 457 } 458 this.date = value; 459 if (initialized) { 460 setPickerDate(JsDate.create((double) value.getTime()), pickatizedDateInput); 461 label.addStyleName("active"); 462 } 463 if (fireEvents) { 464 ValueChangeEvent.fire(this, value); 465 } 466 } 467 468 @Override 469 public HandlerRegistration addCloseHandler(final CloseHandler<MaterialDatePicker> handler) { 470 return addHandler(new CloseHandler<MaterialDatePicker>() { 471 @Override 472 public void onClose(CloseEvent<MaterialDatePicker> event) { 473 if(isEnabled()){ 474 handler.onClose(event); 475 } 476 } 477 }, CloseEvent.getType()); 478 } 479 480 @Override 481 public HandlerRegistration addOpenHandler(final OpenHandler<MaterialDatePicker> handler) { 482 return addHandler(new OpenHandler<MaterialDatePicker>() { 483 @Override 484 public void onOpen(OpenEvent<MaterialDatePicker> event) { 485 if(isEnabled()){ 486 handler.onOpen(event); 487 } 488 } 489 }, OpenEvent.getType()); 490 } 491 492 @Override 493 public HandlerRegistration addBlurHandler(final BlurHandler handler) { 494 return addDomHandler(new BlurHandler() { 495 @Override 496 public void onBlur(BlurEvent event) { 497 if(isEnabled()){ 498 handler.onBlur(event); 499 } 500 } 501 }, BlurEvent.getType()); 502 } 503 504 @Override 505 public void setEnabled(boolean enabled) { 506 super.setEnabled(enabled); 507 dateInput.setEnabled(enabled); 508 } 509 510 @Override 511 public boolean isAllowBlank() { 512 return validatorMixin.isAllowBlank(); 513 } 514 515 @Override 516 public void setAllowBlank(boolean allowBlank) { 517 validatorMixin.setAllowBlank(allowBlank); 518 } 519 520 @Override 521 public void showErrors(List<EditorError> errors) { 522 errorHandlerMixin.showErrors(errors); 523 } 524 525 @Override 526 public ErrorHandler getErrorHandler() { 527 return errorHandlerMixin.getErrorHandler(); 528 } 529 530 @Override 531 public void setErrorHandler(ErrorHandler errorHandler) { 532 errorHandlerMixin.setErrorHandler(errorHandler); 533 } 534 535 @Override 536 public ErrorHandlerType getErrorHandlerType() { 537 return errorHandlerMixin.getErrorHandlerType(); 538 } 539 540 @Override 541 public void setErrorHandlerType(ErrorHandlerType errorHandlerType) { 542 errorHandlerMixin.setErrorHandlerType(errorHandlerType); 543 } 544 545 @Override 546 public void addValidator(Validator<Date> validator) { 547 validatorMixin.addValidator(validator); 548 } 549 550 @Override 551 public boolean isValidateOnBlur() { 552 return validatorMixin.isValidateOnBlur(); 553 } 554 555 @Override 556 public boolean removeValidator(Validator<Date> validator) { 557 return validatorMixin.removeValidator(validator); 558 } 559 560 @Override 561 public void reset() { 562 validatorMixin.reset(); 563 } 564 565 @Override 566 public void setValidateOnBlur(boolean validateOnBlur) { 567 validatorMixin.setValidateOnBlur(validateOnBlur); 568 } 569 570 @Override 571 public void setValidators(@SuppressWarnings("unchecked") Validator<Date>... validators) { 572 validatorMixin.setValidators(validators); 573 } 574 575 @Override 576 public boolean validate() { 577 return validatorMixin.validate(); 578 } 579 580 @Override 581 public boolean validate(boolean show) { 582 return validatorMixin.validate(show); 583 } 584 585 @Override 586 public HandlerRegistration addValidationChangedHandler(ValidationChangedHandler handler) { 587 return (HandlerRegistration)validatorMixin.addValidationChangedHandler(handler); 588 } 589 590 public DatePickerLanguage getLanguage() { 591 return language; 592 } 593 594 public void setLanguage(DatePickerLanguage language) { 595 this.language = language; 596 597 if (language.getJs() != null) { 598 ScriptInjector.fromString(language.getJs().getText()).setWindow(ScriptInjector.TOP_WINDOW).inject(); 599 } 600 } 601 602 /** 603 * Re initialize the datepicker 604 */ 605 public void reinitialize() { 606 stop(); 607 Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { 608 @Override 609 public void execute() { 610 611 initDatePicker(dateInput.getElement(), selectionType.name(), format); 612 } 613 }); 614 } 615 616 /** 617 * Stop the datepicker instance 618 */ 619 public void stop() { 620 stop(pickatizedDateInput); 621 } 622 623 protected native void stop(Element picker) /*-{ 624 picker.pickadate('picker').stop(); 625 }-*/; 626 627 @Override 628 public MaterialIcon getIcon() { 629 return icon; 630 } 631 632 @Override 633 public void setIconType(IconType iconType) { 634 icon.setIconType(iconType); 635 icon.setIconPrefix(true); 636 lblError.setPaddingLeft(44); 637 insert(icon, 0); 638 } 639 640 @Override 641 public void setIconPosition(IconPosition position) { 642 icon.setIconPosition(position); 643 } 644 645 @Override 646 public void setIconSize(IconSize size) { 647 icon.setIconSize(size); 648 } 649 650 @Override 651 public void setIconFontSize(double size, Style.Unit unit) { 652 icon.setIconFontSize(size, unit); 653 } 654 655 @Override 656 public void setIconColor(String iconColor) { 657 icon.setIconColor(iconColor); 658 } 659 660 @Override 661 public void setIconPrefix(boolean prefix) { 662 icon.setIconPrefix(prefix); 663 } 664 665 @Override 666 public boolean isIconPrefix() { 667 return icon.isIconPrefix(); 668 } 669}