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.addins.client.timepicker; 021 022import com.google.gwt.core.client.Scheduler; 023import com.google.gwt.dom.client.Document; 024import com.google.gwt.dom.client.Style; 025import com.google.gwt.event.dom.client.BlurEvent; 026import com.google.gwt.event.dom.client.FocusEvent; 027import com.google.gwt.event.logical.shared.*; 028import com.google.gwt.event.shared.HandlerRegistration; 029import com.google.gwt.i18n.shared.DateTimeFormat; 030import com.google.gwt.user.client.DOM; 031import gwt.material.design.addins.client.MaterialAddins; 032import gwt.material.design.addins.client.base.constants.AddinsCssName; 033import gwt.material.design.addins.client.timepicker.js.JsTimePicker; 034import gwt.material.design.addins.client.timepicker.js.JsTimePickerOptions; 035import gwt.material.design.client.MaterialDesignBase; 036import gwt.material.design.client.base.*; 037import gwt.material.design.client.base.mixin.*; 038import gwt.material.design.client.constants.*; 039import gwt.material.design.client.ui.*; 040import gwt.material.design.client.ui.html.Label; 041 042import java.util.Date; 043 044import static gwt.material.design.addins.client.timepicker.js.JsTimePicker.$; 045 046//@formatter:off 047 048/** 049 * Material Time Picker - provide a simple way to select a single value from a pre-determined set. 050 * <p> 051 * <h3>XML Namespace Declaration</h3> 052 * <pre> 053 * {@code 054 * xmlns:ma='urn:import:gwt.material.design.addins.client' 055 * } 056 * </pre> 057 * <p> 058 * <h3>UiBinder Usage:</h3> 059 * <pre> 060 * {@code <ma:timepicker.MaterialTimePicker placeholder="Time Arrival" />} 061 * </pre> 062 * 063 * @author kevzlou7979 064 * @author Ben Dol 065 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#timepickers">Material Pickers</a> 066 * @see <a href="https://material.io/guidelines/components/pickers.html#pickers-time-pickers">Material Design Specification</a> 067 * @see <a href="https://github.com/weareoutman/clockpicker">ClockPicker 0.0.7</a> 068 */ 069//@formatter:on 070public class MaterialTimePicker extends AbstractValueWidget<Date> implements JsLoader, HasPlaceholder, 071 HasCloseHandlers<Date>, HasOpenHandlers<Date>, HasIcon, HasReadOnly { 072 073 static { 074 if (MaterialAddins.isDebug()) { 075 MaterialDesignBase.injectDebugJs(MaterialTimePickerDebugClientBundle.INSTANCE.timepickerJsDebug()); 076 MaterialDesignBase.injectCss(MaterialTimePickerDebugClientBundle.INSTANCE.timepickerCssDebug()); 077 } else { 078 MaterialDesignBase.injectJs(MaterialTimePickerClientBundle.INSTANCE.timepickerJs()); 079 MaterialDesignBase.injectCss(MaterialTimePickerClientBundle.INSTANCE.timepickerCss()); 080 } 081 } 082 083 private Date time; 084 private String placeholder; 085 private MaterialPanel container = new MaterialPanel(); 086 private MaterialInput timeInput = new MaterialInput(); 087 private MaterialLabel errorLabel = new MaterialLabel(); 088 private Label placeholderLabel = new Label(); 089 private MaterialIcon icon = new MaterialIcon(); 090 private JsTimePickerOptions options = new JsTimePickerOptions(); 091 092 private ToggleStyleMixin<MaterialInput> validMixin; 093 private ErrorMixin<AbstractValueWidget, MaterialLabel> errorMixin; 094 private ReadOnlyMixin<MaterialTimePicker, MaterialInput> readOnlyMixin; 095 private EnabledMixin<MaterialWidget> enabledMixin; 096 097 098 public MaterialTimePicker() { 099 super(Document.get().createElement("div"), AddinsCssName.TIMEPICKER, CssName.INPUT_FIELD); 100 } 101 102 public MaterialTimePicker(String placeholder) { 103 this(); 104 setPlaceholder(placeholder); 105 } 106 107 public MaterialTimePicker(String placeholder, Date value) { 108 this(placeholder); 109 setValue(value); 110 } 111 112 @Override 113 protected void onLoad() { 114 super.onLoad(); 115 116 setUniqueId(DOM.createUniqueId()); 117 timeInput.setType(InputType.TEXT); 118 container.add(placeholderLabel); 119 container.add(timeInput); 120 container.add(errorLabel); 121 add(container); 122 timeInput.getElement().setAttribute("type", "text"); 123 124 load(); 125 } 126 127 @Override 128 public void load() { 129 options.beforeShow = this::beforeShow; 130 options.afterShow = this::afterShow; 131 options.afterHide = this::afterHide; 132 133 $(timeInput.getElement()).lolliclock(options); 134 $(timeInput.getElement()).blur(); 135 136 registerHandler(addOrientationChangeHandler(event -> { 137 JsTimePicker.$(timeInput.getElement()).lolliclock("setOrientation", event.getOrientation().getCssName()); 138 })); 139 } 140 141 @Override 142 protected void onUnload() { 143 super.onUnload(); 144 145 unload(); 146 } 147 148 @Override 149 public void unload() { 150 $(timeInput.getElement()).lolliclock("remove"); 151 } 152 153 @Override 154 public void reload() { 155 unload(); 156 load(); 157 } 158 159 /** 160 * Programmatically open the time picker component 161 */ 162 public void open() { 163 Scheduler.get().scheduleDeferred(() -> $(timeInput.getElement()).lolliclock("show")); 164 } 165 166 /** 167 * Programmatically close the time picker component 168 */ 169 public void close() { 170 Scheduler.get().scheduleDeferred(() -> $(timeInput.getElement()).lolliclock("hide")); 171 } 172 173 @Override 174 public void clear() { 175 time = null; 176 clearErrorOrSuccess(); 177 placeholderLabel.removeStyleName(CssName.ACTIVE); 178 timeInput.removeStyleName(CssName.VALID); 179 $(timeInput.getElement()).val(""); 180 } 181 182 /** 183 * Side effects: 184 * <ul> 185 * <li>Resets the time to <i>now<i></li> 186 * <li>Clears errors/success message</li> 187 * </ul> 188 */ 189 public void reset() { 190 setValue(new Date()); 191 clearErrorOrSuccess(); 192 } 193 194 public boolean isAutoClose() { 195 return options.autoclose; 196 } 197 198 public void setAutoClose(boolean autoClose) { 199 options.autoclose = autoClose; 200 } 201 202 /** 203 * False (default) change to 24 hours system. 204 * 205 * @return <code>false</code> in case 12 hours mode is set; 206 * <code>true</code> otherwise. 207 */ 208 public boolean isHour24() { 209 return options.hour24; 210 } 211 212 /** 213 * Set the time to 24 hour mode. 214 */ 215 public void setHour24(boolean hour24) { 216 options.hour24 = hour24; 217 } 218 219 /** 220 * @return The placeholder text. 221 */ 222 @Override 223 public String getPlaceholder() { 224 return this.placeholder; 225 } 226 227 /** 228 * @param placeholder The placeholder text to set. 229 */ 230 @Override 231 public void setPlaceholder(String placeholder) { 232 this.placeholder = placeholder; 233 placeholderLabel.setText(placeholder); 234 } 235 236 /** 237 * Called after the lolliclock event <code>afterShow</code>. 238 */ 239 protected void beforeShow() { 240 timeInput.getElement().blur(); 241 242 // Add class 'valid' for visual feedback. 243 getValidMixin().setOn(true); 244 } 245 246 /** 247 * Called after the lolliclock event <code>afterShow</code>. 248 */ 249 protected void afterShow() { 250 OpenEvent.fire(this, this.time); 251 fireEvent(new FocusEvent() {}); 252 } 253 254 /** 255 * Called after the lolliclock event <code>afterHide</code>. 256 */ 257 protected void afterHide() { 258 String timeString = getTime(); 259 Date parsedDate = null; 260 261 if (timeString != null && !timeString.equals("")) { 262 try { 263 parsedDate = DateTimeFormat.getFormat(options.hour24 ? "HH:mm" : "hh:mm aa").parse(timeString); 264 } catch (IllegalArgumentException e) { 265 // Silently catch parse errors 266 } 267 } 268 269 setValue(parsedDate, true); 270 271 // Remove class 'valid' after hide. 272 getValidMixin().setOn(false); 273 274 CloseEvent.fire(this, this.time); 275 fireEvent(new BlurEvent() {}); 276 } 277 278 protected String getTime() { 279 return $(timeInput.getElement()).val().toString(); 280 } 281 282 @Override 283 public Date getValue() { 284 return time; 285 } 286 287 @Override 288 public void setValue(Date time, boolean fireEvents) { 289 this.time = time; 290 if (this.time == null) { 291 return; 292 } 293 placeholderLabel.removeStyleName(CssName.ACTIVE); 294 placeholderLabel.addStyleName(CssName.ACTIVE); 295 $(timeInput.getElement()).val(DateTimeFormat.getFormat(options.hour24 ? "HH:mm" : "hh:mm aa").format(time)); 296 super.setValue(time, fireEvents); 297 } 298 299 public String getUniqueId() { 300 return options.uniqueId; 301 } 302 303 public void setUniqueId(String uniqueId) { 304 options.uniqueId = uniqueId; 305 } 306 307 public String getCancelText() { 308 return options.cancelText; 309 } 310 311 /** 312 * Set the "Cancel" text located on TimePicker's action buttons 313 */ 314 public void setCancelText(String cancelText) { 315 options.cancelText = cancelText; 316 317 if (isAttached()) { 318 reload(); 319 } 320 } 321 322 public String getOkText() { 323 return options.okText; 324 } 325 326 /** 327 * Set the "Ok" text located on TimePicker's action buttons 328 */ 329 public void setOkText(String okText) { 330 options.okText = okText; 331 332 if (isAttached()) { 333 reload(); 334 } 335 } 336 337 @Override 338 public MaterialIcon getIcon() { 339 return icon; 340 } 341 342 @Override 343 public void setIconType(IconType iconType) { 344 icon.setIconType(iconType); 345 icon.setIconPrefix(true); 346 errorLabel.setPaddingLeft(44); 347 container.insert(icon, 0); 348 } 349 350 @Override 351 public void setIconPosition(IconPosition position) { 352 icon.setIconPosition(position); 353 } 354 355 @Override 356 public void setIconSize(IconSize size) { 357 icon.setIconSize(size); 358 } 359 360 @Override 361 public void setIconFontSize(double size, Style.Unit unit) { 362 icon.setIconFontSize(size, unit); 363 } 364 365 @Override 366 public void setIconColor(Color iconColor) { 367 icon.setIconColor(iconColor); 368 } 369 370 @Override 371 public Color getIconColor() { 372 return getIcon().getIconColor(); 373 } 374 375 @Override 376 public void setIconPrefix(boolean prefix) { 377 icon.setIconPrefix(prefix); 378 } 379 380 @Override 381 public boolean isIconPrefix() { 382 return icon.isIconPrefix(); 383 } 384 385 @Override 386 public void setEnabled(boolean enabled) { 387 super.setEnabled(enabled); 388 389 getEnabledMixin().updateWaves(enabled, this); 390 } 391 392 @Override 393 protected EnabledMixin<MaterialWidget> getEnabledMixin() { 394 if (enabledMixin == null) { 395 enabledMixin = new EnabledMixin<>(timeInput); 396 } 397 return enabledMixin; 398 } 399 400 @Override 401 public ErrorMixin<AbstractValueWidget, MaterialLabel> getErrorMixin() { 402 if (errorMixin == null) { 403 errorMixin = new ErrorMixin<>(this, errorLabel, timeInput, placeholderLabel); 404 } 405 return errorMixin; 406 } 407 408 protected ReadOnlyMixin<MaterialTimePicker, MaterialInput> getReadOnlyMixin() { 409 if (readOnlyMixin == null) { 410 readOnlyMixin = new ReadOnlyMixin<>(this, timeInput); 411 } 412 return readOnlyMixin; 413 } 414 415 protected ToggleStyleMixin<MaterialInput> getValidMixin() { 416 if (validMixin == null) { 417 validMixin = new ToggleStyleMixin<>(timeInput, CssName.VALID); 418 } 419 return validMixin; 420 } 421 422 @Override 423 public void setReadOnly(boolean value) { 424 getReadOnlyMixin().setReadOnly(value); 425 } 426 427 @Override 428 public boolean isReadOnly() { 429 return getReadOnlyMixin().isReadOnly(); 430 } 431 432 @Override 433 public void setToggleReadOnly(boolean toggle) { 434 getReadOnlyMixin().setToggleReadOnly(toggle); 435 } 436 437 @Override 438 public boolean isToggleReadOnly() { 439 return getReadOnlyMixin().isToggleReadOnly(); 440 } 441 442 public MaterialInput getTimeInput() { 443 return timeInput; 444 } 445 446 public MaterialPanel getContainer() { 447 return container; 448 } 449 450 public MaterialLabel getErrorLabel() { 451 return errorLabel; 452 } 453 454 public Label getPlaceholderLabel() { 455 return placeholderLabel; 456 } 457 458 @Override 459 public HandlerRegistration addCloseHandler(final CloseHandler<Date> handler) { 460 return addHandler(handler, CloseEvent.getType()); 461 } 462 463 @Override 464 public HandlerRegistration addOpenHandler(final OpenHandler<Date> handler) { 465 return addHandler(handler, OpenEvent.getType()); 466 } 467}