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