001/* 002 * #%L 003 * GwtMaterial 004 * %% 005 * Copyright (C) 2015 - 2017 GwtMaterialDesign 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package gwt.material.design.client.ui; 021 022import com.google.gwt.core.client.JsDate; 023import com.google.gwt.core.client.Scheduler; 024import com.google.gwt.core.client.ScriptInjector; 025import com.google.gwt.dom.client.Document; 026import com.google.gwt.dom.client.Element; 027import com.google.gwt.dom.client.Style; 028import com.google.gwt.event.dom.client.BlurEvent; 029import com.google.gwt.event.dom.client.FocusEvent; 030import com.google.gwt.event.logical.shared.*; 031import com.google.gwt.event.shared.HandlerRegistration; 032import com.google.gwt.user.client.DOM; 033import gwt.material.design.client.base.*; 034import gwt.material.design.client.base.helper.DateFormatHelper; 035import gwt.material.design.client.base.mixin.ErrorMixin; 036import gwt.material.design.client.base.mixin.ReadOnlyMixin; 037import gwt.material.design.client.constants.*; 038import gwt.material.design.client.js.JsDatePickerOptions; 039import gwt.material.design.client.js.JsMaterialElement; 040import gwt.material.design.client.ui.html.DateInput; 041import gwt.material.design.client.ui.html.Label; 042 043import java.util.Date; 044 045import static gwt.material.design.client.js.JsMaterialElement.$; 046 047//@formatter:off 048 049/** 050 * Material Date Picker will provide a visual calendar to your apps. 051 * <p/> 052 * <h3>UiBinder Usage:</h3> 053 * {@code 054 * <m:MaterialDatePicker ui:field="datePicker"> 055 * } 056 * <h3>Java Usage:</h3> 057 * {@code 058 * datePicker.setDate(new Date()); 059 * } 060 * 061 * @author kevzlou7979 062 * @author Ben Dol 063 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#pickers">Material Date Picker</a> 064 * @see <a href="https://material.io/guidelines/components/pickers.html#pickers-date-pickers">Material Design Specification</a> 065 */ 066//@formatter:on 067public class MaterialDatePicker extends AbstractValueWidget<Date> implements JsLoader, HasPlaceholder, 068 HasOpenHandlers<MaterialDatePicker>, HasCloseHandlers<MaterialDatePicker>, HasIcon, HasReadOnly { 069 070 /** 071 * Enum for identifying various selection types for the picker. 072 */ 073 public enum MaterialDatePickerType { 074 DAY, 075 MONTH_DAY, 076 YEAR_MONTH_DAY, 077 YEAR 078 } 079 080 private String placeholder; 081 private String tabIndex = "0"; 082 private Date date; 083 private Date dateMin; 084 private Date dateMax; 085 private DatePickerLanguage language; 086 private Orientation orientation; 087 private DatePickerContainer container = DatePickerContainer.SELF; 088 private MaterialDatePickerType selectionType = MaterialDatePickerType.DAY; 089 private int yearsToDisplay = 10; 090 private boolean autoClose; 091 private boolean suppressChangeEvent; 092 protected Element pickatizedDateInput; 093 private DateInput dateInput = new DateInput(); 094 private Label label = new Label(); 095 private MaterialLabel placeholderLabel = new MaterialLabel(); 096 private MaterialLabel errorLabel = new MaterialLabel(); 097 private MaterialIcon icon = new MaterialIcon(); 098 099 private JsDatePickerOptions options = new JsDatePickerOptions(); 100 private HandlerRegistration autoCloseHandlerRegistration, attachHandler; 101 102 private ErrorMixin<AbstractValueWidget, MaterialLabel> errorMixin; 103 private ReadOnlyMixin<MaterialDatePicker, DateInput> readOnlyMixin; 104 105 public MaterialDatePicker() { 106 super(Document.get().createDivElement(), CssName.INPUT_FIELD); 107 108 add(dateInput); 109 label.add(placeholderLabel); 110 add(label); 111 add(errorLabel); 112 } 113 114 public MaterialDatePicker(String placeholder) { 115 this(); 116 setPlaceholder(placeholder); 117 } 118 119 public MaterialDatePicker(String placeholder, Date value) { 120 this(placeholder); 121 setDate(value); 122 } 123 124 public MaterialDatePicker(String placeholder, Date value, MaterialDatePickerType selectionType) { 125 this(placeholder, value); 126 setSelectionType(selectionType); 127 } 128 129 @Override 130 protected void onLoad() { 131 super.onLoad(); 132 133 load(); 134 } 135 136 @Override 137 public void load() { 138 pickatizedDateInput = $(dateInput.getElement()).pickadate(options).asElement(); 139 140 getPicker().on("set", thing -> { 141 if (thing.hasOwnProperty("clear")) { 142 clear(); 143 } else if (thing.hasOwnProperty("select")) { 144 select(); 145 } 146 }); 147 148 getPicker().on(options).on("open", (e, param1) -> { 149 onOpen(); 150 return true; 151 }).on("close", (e, param1) -> { 152 onClose(); 153 $(pickatizedDateInput).blur(); 154 return true; 155 }); 156 157 label.getElement().setAttribute("for", getPickerId()); 158 setPopupEnabled(isEnabled()); 159 setAutoClose(autoClose); 160 setDate(date); 161 setDateMin(dateMin); 162 setDateMax(dateMax); 163 } 164 165 @Override 166 public void onUnload() { 167 super.onUnload(); 168 169 unload(); 170 } 171 172 @Override 173 public void unload() { 174 JsMaterialElement picker = getPicker(); 175 if (picker != null) { 176 picker.off("set"); 177 picker.off("open"); 178 picker.off("close"); 179 } 180 } 181 182 @Override 183 public void reload() { 184 unload(); 185 load(); 186 } 187 188 /** 189 * As of now use {@link MaterialDatePicker#setSelectionType(MaterialDatePickerType)} 190 * 191 * @param type 192 */ 193 @Deprecated 194 public void setDateSelectionType(MaterialDatePickerType type) { 195 if (type != null) { 196 this.selectionType = type; 197 } 198 } 199 200 public String getPickerId() { 201 return getPicker().get("id").toString(); 202 } 203 204 public Element getPickerRootElement() { 205 return $("#" + getPickerId() + "_root").asElement(); 206 } 207 208 /** 209 * Sets the current date of the picker. 210 * 211 * @param date - must not be <code>null</code> 212 */ 213 public void setDate(Date date) { 214 setValue(date); 215 } 216 217 /** 218 * Get the minimum date limit. 219 */ 220 public Date getDateMin() { 221 return dateMin; 222 } 223 224 /** 225 * Set the minimum date limit. 226 */ 227 public void setDateMin(Date dateMin) { 228 this.dateMin = dateMin; 229 230 if (isAttached() && dateMin != null) { 231 getPicker().set("min", JsDate.create((double) dateMin.getTime())); 232 } 233 } 234 235 /** 236 * Get the maximum date limit. 237 */ 238 public Date getDateMax() { 239 return dateMax; 240 } 241 242 /** 243 * Set the maximum date limit. 244 */ 245 public void setDateMax(Date dateMax) { 246 this.dateMax = dateMax; 247 248 if (isAttached() && dateMax != null) { 249 getPicker().set("max", JsDate.create((double) dateMax.getTime())); 250 } 251 } 252 253 /** 254 * Set the pickers date. 255 */ 256 public void setPickerDate(JsDate date, Element picker) { 257 try { 258 $(picker).pickadate("picker").set("select", date, () -> { 259 DOM.createFieldSet().setPropertyObject("muted", true); 260 }); 261 } catch (Exception e) { 262 e.printStackTrace(); 263 } 264 } 265 266 /** 267 * Get the pickers date. 268 */ 269 protected Date getPickerDate() { 270 try { 271 JsDate pickerDate = getPicker().get("select").obj; 272 return new Date((long) pickerDate.getTime()); 273 } catch (Exception e) { 274 e.printStackTrace(); 275 return null; 276 } 277 } 278 279 protected JsMaterialElement getPicker() { 280 return $(pickatizedDateInput).pickadate("picker"); 281 } 282 283 public Date getDate() { 284 return getPickerDate(); 285 } 286 287 @Override 288 public String getPlaceholder() { 289 return placeholder; 290 } 291 292 @Override 293 public void setPlaceholder(String placeholder) { 294 this.placeholder = placeholder; 295 296 if (placeholder != null) { 297 placeholderLabel.setText(placeholder); 298 } 299 } 300 301 /** 302 * Get the pickers selection type. 303 */ 304 public MaterialDatePickerType getSelectionType() { 305 return selectionType; 306 } 307 308 /** 309 * Set the pickers selection type. 310 */ 311 public void setSelectionType(MaterialDatePickerType selectionType) { 312 this.selectionType = selectionType; 313 switch (selectionType) { 314 case MONTH_DAY: 315 options.selectMonths = true; 316 break; 317 case YEAR_MONTH_DAY: 318 options.selectYears = yearsToDisplay; 319 options.selectMonths = true; 320 break; 321 case YEAR: 322 options.selectYears = yearsToDisplay; 323 options.selectMonths = false; 324 break; 325 } 326 } 327 328 /** 329 * Set the pickers selection type with the ability to set the number of years to display 330 * in the dropdown list. 331 */ 332 public void setSelectionType(MaterialDatePickerType selectionType, int yearsToDisplay) { 333 setSelectionType(selectionType); 334 setYearsToDisplay(yearsToDisplay); 335 } 336 337 @Override 338 public void setOrientation(Orientation orientation) { 339 this.orientation = orientation; 340 341 JsMaterialElement picker = getPicker(); 342 if (picker != null && orientation != null) { 343 picker.root.removeClass(orientation.getCssName()); 344 } 345 if (picker != null && orientation != null) { 346 picker.root.addClass(orientation.getCssName()); 347 } 348 } 349 350 @Override 351 public Orientation getOrientation() { 352 return orientation; 353 } 354 355 @Override 356 public void setError(String error) { 357 super.setError(error); 358 dateInput.addStyleName(CssName.INVALID); 359 dateInput.removeStyleName(CssName.VALID); 360 } 361 362 @Override 363 public void setSuccess(String success) { 364 super.setSuccess(success); 365 dateInput.addStyleName(CssName.VALID); 366 dateInput.removeStyleName(CssName.INVALID); 367 } 368 369 @Override 370 public void clearErrorOrSuccess() { 371 super.clearErrorOrSuccess(); 372 dateInput.removeStyleName(CssName.VALID); 373 dateInput.removeStyleName(CssName.INVALID); 374 } 375 376 public String getFormat() { 377 return options.format; 378 } 379 380 /** 381 * To call before initialization. 382 */ 383 public void setFormat(String format) { 384 options.format = DateFormatHelper.format(format); 385 } 386 387 @Override 388 public Date getValue() { 389 if (isAttached()) { 390 return getPickerDate(); 391 } 392 else { 393 return this.date; 394 } 395 } 396 397 @Override 398 public void setValue(Date value, boolean fireEvents) { 399 this.date = value; 400 if (value == null) { 401 clear(); 402 return; 403 } 404 if (isAttached()) { 405 suppressChangeEvent = !fireEvents; 406 setPickerDate(JsDate.create((double) value.getTime()), pickatizedDateInput); 407 suppressChangeEvent = false; 408 label.addStyleName(CssName.ACTIVE); 409 } 410 super.setValue(value, fireEvents); 411 } 412 413 @Override 414 public void setValue(Date value) { 415 setValue(value, false); 416 } 417 418 @Override 419 public void setEnabled(boolean enabled) { 420 super.setEnabled(enabled); 421 dateInput.setEnabled(enabled); 422 if (isAttached()) { 423 setPopupEnabled(enabled); 424 } 425 } 426 427 @Override 428 public boolean isEnabled() { 429 return dateInput.isEnabled(); 430 } 431 432 @Override 433 public void setTabIndex(int index) { 434 tabIndex = String.valueOf(index); 435 dateInput.setTabIndex(index); 436 } 437 438 @Override 439 public int getTabIndex() { 440 return dateInput.getTabIndex(); 441 } 442 443 public DatePickerLanguage getLanguage() { 444 return language; 445 } 446 447 public void setLanguage(DatePickerLanguage language) { 448 this.language = language; 449 450 if (attachHandler != null) { 451 attachHandler.removeHandler(); 452 attachHandler = null; 453 } 454 455 if (isAttached()) { 456 setupLanguage(language); 457 } else { 458 attachHandler = registerHandler(addAttachHandler(attachEvent -> setupLanguage(language))); 459 } 460 } 461 462 protected void setupLanguage(DatePickerLanguage language) { 463 if (language.getJs() != null) { 464 ScriptInjector.fromString(language.getJs().getText()).setWindow(ScriptInjector.TOP_WINDOW).inject(); 465 getPicker().stop(); 466 Scheduler.get().scheduleDeferred(() -> load()); 467 } 468 } 469 470 @Override 471 public MaterialIcon getIcon() { 472 return icon; 473 } 474 475 @Override 476 public void setIconType(IconType iconType) { 477 icon.setIconType(iconType); 478 icon.setIconPrefix(true); 479 errorLabel.setPaddingLeft(44); 480 insert(icon, 0); 481 } 482 483 @Override 484 public void setIconPosition(IconPosition position) { 485 icon.setIconPosition(position); 486 } 487 488 @Override 489 public void setIconSize(IconSize size) { 490 icon.setIconSize(size); 491 } 492 493 @Override 494 public void setIconFontSize(double size, Style.Unit unit) { 495 icon.setIconFontSize(size, unit); 496 } 497 498 @Override 499 public void setIconColor(Color iconColor) { 500 icon.setIconColor(iconColor); 501 } 502 503 @Override 504 public Color getIconColor() { 505 return icon.getIconColor(); 506 } 507 508 @Override 509 public void setIconPrefix(boolean prefix) { 510 icon.setIconPrefix(prefix); 511 } 512 513 @Override 514 public boolean isIconPrefix() { 515 return icon.isIconPrefix(); 516 } 517 518 @Override 519 public void setReadOnly(boolean value) { 520 getReadOnlyMixin().setReadOnly(value); 521 } 522 523 @Override 524 public boolean isReadOnly() { 525 return getReadOnlyMixin().isReadOnly(); 526 } 527 528 @Override 529 public void setToggleReadOnly(boolean toggle) { 530 getReadOnlyMixin().setToggleReadOnly(toggle); 531 } 532 533 @Override 534 public boolean isToggleReadOnly() { 535 return getReadOnlyMixin().isToggleReadOnly(); 536 } 537 538 public DateInput getDateInput() { 539 return dateInput; 540 } 541 542 public boolean isAutoClose() { 543 return autoClose; 544 } 545 546 /** 547 * Enables or disables auto closing when selecting a date. 548 */ 549 public void setAutoClose(boolean autoClose) { 550 this.autoClose = autoClose; 551 552 if (autoCloseHandlerRegistration != null) { 553 autoCloseHandlerRegistration.removeHandler(); 554 autoCloseHandlerRegistration = null; 555 } 556 557 if (autoClose) { 558 autoCloseHandlerRegistration = registerHandler(addValueChangeHandler(event -> close())); 559 } 560 } 561 562 public int getYearsToDisplay() { 563 return options.selectYears; 564 } 565 566 /** 567 * Ability to set the number of years to display 568 * in the dropdown list. 569 */ 570 public void setYearsToDisplay(int yearsToDisplay) { 571 options.selectYears = yearsToDisplay; 572 } 573 574 public DatePickerContainer getContainer() { 575 return container; 576 } 577 578 /** 579 * Set the Root Picker Container (Default : SELF) 580 */ 581 public void setContainer(DatePickerContainer container) { 582 this.container = container; 583 options.container = container == DatePickerContainer.SELF ? getElement().getId() : container.getCssName(); 584 } 585 586 public Label getLabel() { 587 return label; 588 } 589 590 public MaterialLabel getPlaceholderLabel() { 591 return placeholderLabel; 592 } 593 594 public MaterialLabel getErrorLabel() { 595 return errorLabel; 596 } 597 598 /** 599 * Programmatically close the date picker component 600 */ 601 public void close() { 602 Scheduler.get().scheduleDeferred(() -> getPicker().close()); 603 } 604 605 /** 606 * Programmatically open the date picker component 607 */ 608 public void open() { 609 Scheduler.get().scheduleDeferred(() -> getPicker().open()); 610 } 611 612 public boolean isOpen() { 613 return Boolean.parseBoolean(getPicker().get("open").toString()); 614 } 615 616 protected void select() { 617 label.addStyleName(CssName.ACTIVE); 618 dateInput.addStyleName(CssName.VALID); 619 620 // Ensure the value change event is 621 // triggered on selecting a date if the picker is open 622 // to avoid conflicts on setValue(value, fireEvents). 623 if (isOpen() && !suppressChangeEvent) { 624 ValueChangeEvent.fire(this, getValue()); 625 } 626 } 627 628 protected void onClose() { 629 CloseEvent.fire(this, this); 630 fireEvent(new BlurEvent() {}); 631 } 632 633 protected void onOpen() { 634 label.addStyleName(CssName.ACTIVE); 635 dateInput.setFocus(true); 636 OpenEvent.fire(this, this); 637 fireEvent(new FocusEvent() {}); 638 } 639 640 /** 641 * Replaced by {@link MaterialDatePicker#clear()} 642 */ 643 @Deprecated 644 public void clearValues() { 645 clear(); 646 } 647 648 /** 649 * Replace by {@link MaterialDatePicker#unload()} 650 */ 651 @Deprecated 652 public void stop() { 653 unload(); 654 } 655 656 @Override 657 public void clear() { 658 this.date = null; 659 dateInput.clear(); 660 if (getPicker() != null) { 661 getPicker().set("select", null); 662 } 663 // Clear all active / error styles on datepicker 664 clearErrorOrSuccess(); 665 label.removeStyleName(CssName.ACTIVE); 666 dateInput.removeStyleName(CssName.VALID); 667 668 } 669 670 protected void setPopupEnabled(boolean enabled) { 671 if (getPicker() != null) { 672 if (!enabled) { 673 $(getPickerRootElement()).attr("tabindex", "-1"); 674 } else { 675 $(getPickerRootElement()).attr("tabindex", tabIndex); 676 } 677 } 678 } 679 680 @Override 681 public HandlerRegistration addCloseHandler(final CloseHandler<MaterialDatePicker> handler) { 682 return addHandler(handler, CloseEvent.getType()); 683 } 684 685 @Override 686 public HandlerRegistration addOpenHandler(final OpenHandler<MaterialDatePicker> handler) { 687 return addHandler(handler, OpenEvent.getType()); 688 } 689 690 @Override 691 protected ErrorMixin<AbstractValueWidget, MaterialLabel> getErrorMixin() { 692 if (errorMixin == null) { 693 errorMixin = new ErrorMixin<>(this, errorLabel, dateInput, placeholderLabel); 694 } 695 return errorMixin; 696 } 697 698 protected ReadOnlyMixin<MaterialDatePicker, DateInput> getReadOnlyMixin() { 699 if (readOnlyMixin == null) { 700 readOnlyMixin = new ReadOnlyMixin<>(this, dateInput); 701 } 702 return readOnlyMixin; 703 } 704}