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}