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.Scheduler;
023import com.google.gwt.dom.client.Document;
024import com.google.gwt.dom.client.Style;
025import com.google.gwt.editor.client.Editor;
026import com.google.gwt.editor.client.IsEditor;
027import com.google.gwt.editor.ui.client.adapters.ValueBoxEditor;
028import com.google.gwt.event.dom.client.*;
029import com.google.gwt.event.logical.shared.ValueChangeHandler;
030import com.google.gwt.event.shared.HandlerRegistration;
031import com.google.gwt.i18n.client.AutoDirectionHandler;
032import com.google.gwt.i18n.shared.DirectionEstimator;
033import com.google.gwt.i18n.shared.HasDirectionEstimator;
034import com.google.gwt.uibinder.client.UiChild;
035import com.google.gwt.user.client.DOM;
036import com.google.gwt.user.client.ui.HasName;
037import com.google.gwt.user.client.ui.HasText;
038import com.google.gwt.user.client.ui.ValueBoxBase;
039import com.google.gwt.user.client.ui.ValueBoxBase.TextAlignment;
040import gwt.material.design.client.base.*;
041import gwt.material.design.client.base.mixin.*;
042import gwt.material.design.client.constants.*;
043import gwt.material.design.client.events.DragEndEvent;
044import gwt.material.design.client.events.DragEnterEvent;
045import gwt.material.design.client.events.DragLeaveEvent;
046import gwt.material.design.client.events.*;
047import gwt.material.design.client.events.DragOverEvent;
048import gwt.material.design.client.events.DragStartEvent;
049import gwt.material.design.client.events.DropEvent;
050import gwt.material.design.client.ui.html.Label;
051
052//@formatter:off
053
054/**
055 * MaterialValueBox is an input field that accepts any text based string from user.
056 * <h3>UiBinder Usage:</h3>
057 * <pre>
058 * {@code <m:MaterialTextBox placeholder="First Name" />}
059 * </pre>
060 *
061 * @author kevzlou7979
062 * @author Ben Dol
063 * @author paulux84
064 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#textfields">Material TextBox</a>
065 * @see <a href="https://material.io/guidelines/components/text-fields.html#">Material Design Specification</a>
066 */
067//@formatter:on
068public class MaterialValueBox<T> extends AbstractValueWidget<T> implements HasChangeHandlers, HasName,
069        HasDirectionEstimator, HasText, AutoDirectionHandler.Target, IsEditor<ValueBoxEditor<T>>, HasIcon,
070        HasInputType, HasPlaceholder, HasCounter, HasReadOnly, HasActive {
071
072    private InputType type = InputType.TEXT;
073    private ValueBoxEditor<T> editor;
074    private Label label = new Label();
075    private MaterialLabel errorLabel = new MaterialLabel();
076    private MaterialIcon icon = new MaterialIcon();
077
078    @Editor.Ignore
079    protected ValueBoxBase<T> valueBoxBase;
080
081    private CounterMixin<MaterialValueBox<T>> counterMixin;
082    private ErrorMixin<AbstractValueWidget, MaterialLabel> errorMixin;
083    private ReadOnlyMixin<MaterialValueBox, ValueBoxBase> readOnlyMixin;
084    private FocusableMixin<MaterialWidget> focusableMixin;
085    private ActiveMixin<MaterialValueBox> activeMixin;
086
087    public class MaterialValueBoxEditor<V> extends ValueBoxEditor<V> {
088        private final ValueBoxBase<V> valueBoxBase;
089
090        private MaterialValueBoxEditor(ValueBoxBase<V> valueBoxBase) {
091            super(valueBoxBase);
092            this.valueBoxBase = valueBoxBase;
093        }
094
095        @Override
096        public void setValue(V value) {
097            super.setValue(value);
098            if (valueBoxBase.getText() != null && !valueBoxBase.getText().isEmpty()) {
099                label.addStyleName(CssName.ACTIVE);
100            } else {
101                label.removeStyleName(CssName.ACTIVE);
102            }
103        }
104    }
105
106    protected MaterialValueBox() {
107        super(Document.get().createDivElement(), CssName.INPUT_FIELD);
108    }
109
110    public MaterialValueBox(ValueBoxBase<T> tValueBox) {
111        this();
112        setup(tValueBox);
113    }
114
115    public void setup(ValueBoxBase<T> tValueBox) {
116        valueBoxBase = tValueBox;
117        add(valueBoxBase);
118    }
119
120    @Deprecated
121    @UiChild(limit = 1)
122    public void addValueBox(ValueBoxBase<T> widget) {
123        setup(widget);
124    }
125
126    @Override
127    protected void onLoad() {
128        super.onLoad();
129
130        String id = DOM.createUniqueId();
131        valueBoxBase.getElement().setId(id);
132        label.getElement().setAttribute("for", id);
133
134        // Make valueBoxBase the primary focus target
135        getFocusableMixin().setUiObject(new MaterialWidget(valueBoxBase.getElement()));
136    }
137
138    /**
139     * Resets the text box by removing its content and resetting visual state.
140     */
141    public void clear() {
142        valueBoxBase.setText("");
143        clearErrorOrSuccess();
144        label.removeStyleName(CssName.ACTIVE);
145    }
146
147    public void removeErrorModifiers() {
148        valueBoxBase.getElement().removeClassName(CssName.VALID);
149        valueBoxBase.getElement().removeClassName(CssName.INVALID);
150    }
151
152    @Override
153    public String getText() {
154        return valueBoxBase.getText();
155    }
156
157    @Override
158    public void setText(String text) {
159        valueBoxBase.setText(text);
160
161        if (text != null && !text.isEmpty()) {
162            label.addStyleName(CssName.ACTIVE);
163        }
164    }
165
166    /**
167     * Set the label of this field.
168     * <p>
169     * This will be displayed above the field when values are
170     * assigned to the box, otherwise the value is displayed
171     * inside the box.
172     * </p>
173     */
174    public void setLabel(String label) {
175        this.label.setText(label);
176
177        if(!getPlaceholder().isEmpty()) {
178            this.label.setStyleName(CssName.ACTIVE);
179        }
180    }
181
182    @Override
183    public String getPlaceholder() {
184        return valueBoxBase.getElement().getAttribute("placeholder");
185    }
186
187    @Override
188    public void setPlaceholder(String placeholder) {
189        valueBoxBase.getElement().setAttribute("placeholder", placeholder);
190
191        if(!label.getText().isEmpty()) {
192            label.setStyleName(CssName.ACTIVE);
193        }
194    }
195
196    @Override
197    public InputType getType() {
198        return type;
199    }
200
201    @Override
202    public void setType(InputType type) {
203        this.type = type;
204        valueBoxBase.getElement().setAttribute("type", type.getType());
205        if (getType() != InputType.SEARCH) {
206            add(label);
207            errorLabel.setVisible(false);
208            add(errorLabel);
209        }
210    }
211
212    @Override
213    public T getValue() {
214        return valueBoxBase.getValue();
215    }
216
217    @Override
218    public void setValue(T value, boolean fireEvents) {
219        valueBoxBase.setValue(value, fireEvents);
220
221        if (value != null && !value.toString().isEmpty()) {
222            label.addStyleName(CssName.ACTIVE);
223        }
224    }
225
226    @Override
227    public void setDirection(Direction direction) {
228        valueBoxBase.setDirection(direction);
229    }
230
231    @Override
232    public Direction getDirection() {
233        return valueBoxBase.getDirection();
234    }
235
236    @Override
237    public ValueBoxEditor<T> asEditor() {
238        if (editor == null) {
239            editor = new MaterialValueBoxEditor<>(valueBoxBase);
240        }
241        return editor;
242    }
243
244    @Override
245    public DirectionEstimator getDirectionEstimator() {
246        return valueBoxBase.getDirectionEstimator();
247    }
248
249    @Override
250    public void setDirectionEstimator(boolean enabled) {
251        valueBoxBase.setDirectionEstimator(enabled);
252    }
253
254    @Override
255    public void setDirectionEstimator(DirectionEstimator directionEstimator) {
256        valueBoxBase.setDirectionEstimator(directionEstimator);
257    }
258
259    @Override
260    public void setName(String name) {
261        valueBoxBase.setName(name);
262    }
263
264    @Override
265    public String getName() {
266        return valueBoxBase.getName();
267    }
268
269    @Override
270    public void setError(String error) {
271        super.setError(error);
272        removeErrorModifiers();
273        valueBoxBase.getElement().addClassName(CssName.INVALID);
274    }
275
276    @Override
277    public void setSuccess(String success) {
278        super.setSuccess(success);
279        removeErrorModifiers();
280        valueBoxBase.getElement().addClassName(CssName.VALID);
281    }
282
283    @Override
284    public void clearErrorOrSuccess() {
285        super.clearErrorOrSuccess();
286        removeErrorModifiers();
287    }
288
289    @Override
290    public MaterialIcon getIcon() {
291        return icon;
292    }
293
294    @Override
295    public void setIconType(IconType iconType) {
296        icon.setIconType(iconType);
297        icon.setIconPrefix(true);
298        errorLabel.setPaddingLeft(44);
299        insert(icon, 0);
300    }
301
302    @Override
303    public void setIconPosition(IconPosition position) {
304        icon.setIconPosition(position);
305    }
306
307    @Override
308    public void setIconSize(IconSize size) {
309        icon.setIconSize(size);
310    }
311
312    @Override
313    public void setIconFontSize(double size, Style.Unit unit) {
314        icon.setIconFontSize(size, unit);
315    }
316
317    @Override
318    public void setIconColor(Color iconColor) {
319        icon.setIconColor(iconColor);
320    }
321
322    @Override
323    public Color getIconColor() {
324        return icon.getIconColor();
325    }
326
327    @Override
328    public void setIconPrefix(boolean prefix) {
329        icon.setIconPrefix(prefix);
330    }
331
332    @Override
333    public boolean isIconPrefix() {
334        return icon.isIconPrefix();
335    }
336
337    @Override
338    public void setLength(int length) {
339        getCounterMixin().setLength(length);
340    }
341
342    @Override
343    public int getLength() {
344        return getCounterMixin().getLength();
345    }
346
347    @Editor.Ignore
348    public ValueBoxBase<T> asValueBoxBase() {
349        return valueBoxBase;
350    }
351
352    @Override
353    public int getTabIndex() {
354        return valueBoxBase.getTabIndex();
355    }
356
357    @Override
358    public void setAccessKey(char key) {
359        valueBoxBase.setAccessKey(key);
360    }
361
362    @Override
363    public void setFocus(final boolean focused) {
364        Scheduler.get().scheduleDeferred(() -> {
365            valueBoxBase.setFocus(focused);
366            if (focused) {
367                label.addStyleName(CssName.ACTIVE);
368            } else {
369                updateLabelActiveStyle();
370            }
371        });
372    }
373
374    /**
375     * Updates the style of the field label according to the field value if the
376     * field value is empty - null or "" - removes the label 'active' style else
377     * will add the 'active' style to the field label.
378     */
379    protected void updateLabelActiveStyle() {
380        if (this.valueBoxBase.getText() != null && !this.valueBoxBase.getText().isEmpty()) {
381            label.addStyleName(CssName.ACTIVE);
382        } else {
383            label.removeStyleName(CssName.ACTIVE);
384        }
385    }
386
387    public String getSelectedText() {
388        return valueBoxBase.getSelectedText();
389    }
390
391    public int getSelectionLength() {
392        return valueBoxBase.getSelectionLength();
393    }
394
395    public void setSelectionRange(int pos, int length) {
396        valueBoxBase.setSelectionRange(pos, length);
397    }
398
399    @Override
400    public void setActive(boolean active) {
401        getActiveMixin().setActive(active);
402    }
403
404    @Override
405    public boolean isActive() {
406        return getActiveMixin().isActive();
407    }
408
409    @Override
410    public void setReadOnly(boolean readOnly) {
411        getReadOnlyMixin().setReadOnly(readOnly);
412    }
413
414    @Override
415    public boolean isReadOnly() {
416        return getReadOnlyMixin().isReadOnly();
417    }
418
419    @Override
420    public void setToggleReadOnly(boolean toggle) {
421        getReadOnlyMixin().setToggleReadOnly(toggle);
422    }
423
424    @Override
425    public boolean isToggleReadOnly() {
426        return getReadOnlyMixin().isToggleReadOnly();
427    }
428
429    public void setCursorPos(int pos) {
430        valueBoxBase.setCursorPos(pos);
431    }
432
433    public void setAlignment(TextAlignment align) {
434        valueBoxBase.setAlignment(align);
435    }
436
437    @Override
438    public void setTabIndex(int tabIndex) {
439        valueBoxBase.setTabIndex(tabIndex);
440    }
441
442    @Override
443    public void setEnabled(boolean enabled) {
444        super.setEnabled(enabled);
445        valueBoxBase.setEnabled(enabled);
446    }
447
448    @Override
449    public boolean isEnabled() {
450        return valueBoxBase.isEnabled();
451    }
452
453    @Ignore
454    public ValueBoxBase<T> getValueBoxBase() {
455        return valueBoxBase;
456    }
457
458    public Label getLabel() {
459        return label;
460    }
461
462    public MaterialLabel getErrorLabel() {
463        return errorLabel;
464    }
465
466    @Override
467    public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<T> handler) {
468        return valueBoxBase.addValueChangeHandler(event -> {
469            if (isEnabled()) {
470                handler.onValueChange(event);
471            }
472        });
473    }
474
475    @Override
476    public HandlerRegistration addDragStartHandler(DragStartEvent.DragStartHandler handler) {
477        return valueBoxBase.addHandler(event -> {
478            if (isEnabled()) {
479                handler.onDragStart(event);
480            }
481        }, DragStartEvent.getType());
482    }
483
484    @Override
485    public HandlerRegistration addDragMoveHandler(DragMoveEvent.DragMoveHandler handler) {
486        return valueBoxBase.addHandler(event -> {
487            if (isEnabled()) {
488                handler.onDragMove(event);
489            }
490        }, DragMoveEvent.getType());
491    }
492
493    @Override
494    public HandlerRegistration addDragEndHandler(DragEndEvent.DragEndHandler handler) {
495        return valueBoxBase.addHandler(event -> {
496            if (isEnabled()) {
497                handler.onDragEnd(event);
498            }
499        }, DragEndEvent.getType());
500    }
501
502    @Override
503    public HandlerRegistration addDropActivateHandler(DropActivateEvent.DropActivateHandler handler) {
504        return valueBoxBase.addHandler(event -> {
505            if (isEnabled()) {
506                handler.onDropActivate(event);
507            }
508        }, DropActivateEvent.getType());
509    }
510
511    @Override
512    public HandlerRegistration addDragEnterHandler(DragEnterEvent.DragEnterHandler handler) {
513        return valueBoxBase.addHandler(event -> {
514            if (isEnabled()) {
515                handler.onDragEnter(event);
516            }
517        }, DragEnterEvent.getType());
518    }
519
520    @Override
521    public HandlerRegistration addDragLeaveHandler(DragLeaveEvent.DragLeaveHandler handler) {
522        return valueBoxBase.addHandler(event -> {
523            if (isEnabled()) {
524                handler.onDragLeave(event);
525            }
526        }, DragLeaveEvent.getType());
527    }
528
529    @Override
530    public HandlerRegistration addDragOverHandler(DragOverEvent.DragOverHandler handler) {
531        return valueBoxBase.addHandler(event -> {
532            if (isEnabled()) {
533                handler.onDragOver(event);
534            }
535        }, DragOverEvent.getType());
536    }
537
538    @Override
539    public HandlerRegistration addDropDeactivateHandler(DropDeactivateEvent.DropDeactivateHandler handler) {
540        return valueBoxBase.addHandler(event -> {
541            if (isEnabled()) {
542                handler.onDropDeactivate(event);
543            }
544        }, DropDeactivateEvent.getType());
545    }
546
547    @Override
548    public HandlerRegistration addDropHandler(DropEvent.DropHandler handler) {
549        return valueBoxBase.addHandler(event -> {
550            if (isEnabled()) {
551                handler.onDrop(event);
552            }
553        }, DropEvent.getType());
554    }
555
556    @Override
557    public HandlerRegistration addKeyUpHandler(final KeyUpHandler handler) {
558        return valueBoxBase.addDomHandler(event -> {
559            if (isEnabled()) {
560                handler.onKeyUp(event);
561            }
562        }, KeyUpEvent.getType());
563    }
564
565    @Override
566    public HandlerRegistration addChangeHandler(final ChangeHandler handler) {
567        return valueBoxBase.addChangeHandler(event -> {
568            if (isEnabled()) {
569                handler.onChange(event);
570            }
571        });
572    }
573
574    @Override
575    public HandlerRegistration addFocusHandler(final FocusHandler handler) {
576        return valueBoxBase.addFocusHandler(event -> {
577            if (isEnabled()) {
578                handler.onFocus(event);
579            }
580        });
581    }
582
583    @Override
584    public HandlerRegistration addBlurHandler(final BlurHandler handler) {
585        return valueBoxBase.addBlurHandler(event -> {
586            if (isEnabled()) {
587                handler.onBlur(event);
588            }
589        });
590    }
591
592    @Override
593    public HandlerRegistration addGestureStartHandler(final GestureStartHandler handler) {
594        return valueBoxBase.addGestureStartHandler(event -> {
595            if (isEnabled()) {
596                handler.onGestureStart(event);
597            }
598        });
599    }
600
601    @Override
602    public HandlerRegistration addGestureChangeHandler(final GestureChangeHandler handler) {
603        return valueBoxBase.addGestureChangeHandler(event -> {
604            if (isEnabled()) {
605                handler.onGestureChange(event);
606            }
607        });
608    }
609
610    @Override
611    public HandlerRegistration addGestureEndHandler(final GestureEndHandler handler) {
612        return valueBoxBase.addGestureEndHandler(event -> {
613            if (isEnabled()) {
614                handler.onGestureEnd(event);
615            }
616        });
617    }
618
619    @Override
620    public HandlerRegistration addKeyDownHandler(final KeyDownHandler handler) {
621        return valueBoxBase.addKeyDownHandler(event -> {
622            if (isEnabled()) {
623                handler.onKeyDown(event);
624            }
625        });
626    }
627
628    @Override
629    public HandlerRegistration addKeyPressHandler(final KeyPressHandler handler) {
630        return valueBoxBase.addKeyPressHandler(event -> {
631            if (isEnabled()) {
632                handler.onKeyPress(event);
633            }
634        });
635    }
636
637    @Override
638    public HandlerRegistration addMouseDownHandler(final MouseDownHandler handler) {
639        return valueBoxBase.addMouseDownHandler(event -> {
640            if (isEnabled()) {
641                handler.onMouseDown(event);
642            }
643        });
644    }
645
646    @Override
647    public HandlerRegistration addMouseUpHandler(final MouseUpHandler handler) {
648        return valueBoxBase.addMouseUpHandler(event -> {
649            if (isEnabled()) {
650                handler.onMouseUp(event);
651            }
652        });
653    }
654
655    @Override
656    public HandlerRegistration addMouseOutHandler(final MouseOutHandler handler) {
657        return valueBoxBase.addMouseOutHandler(event -> {
658            if (isEnabled()) {
659                handler.onMouseOut(event);
660            }
661        });
662    }
663
664    @Override
665    public HandlerRegistration addMouseOverHandler(final MouseOverHandler handler) {
666        return valueBoxBase.addMouseOverHandler(event -> {
667            if (isEnabled()) {
668                handler.onMouseOver(event);
669            }
670        });
671    }
672
673    @Override
674    public HandlerRegistration addMouseMoveHandler(final MouseMoveHandler handler) {
675        return valueBoxBase.addMouseMoveHandler(event -> {
676            if (isEnabled()) {
677                handler.onMouseMove(event);
678            }
679        });
680    }
681
682    @Override
683    public HandlerRegistration addMouseWheelHandler(final MouseWheelHandler handler) {
684        return valueBoxBase.addMouseWheelHandler(event -> {
685            if (isEnabled()) {
686                handler.onMouseWheel(event);
687            }
688        });
689    }
690
691    @Override
692    public HandlerRegistration addTouchStartHandler(final TouchStartHandler handler) {
693        return valueBoxBase.addTouchStartHandler(event -> {
694            if (isEnabled()) {
695                handler.onTouchStart(event);
696            }
697        });
698    }
699
700    @Override
701    public HandlerRegistration addTouchMoveHandler(final TouchMoveHandler handler) {
702        return valueBoxBase.addTouchMoveHandler(event -> {
703            if (isEnabled()) {
704                handler.onTouchMove(event);
705            }
706        });
707    }
708
709    @Override
710    public HandlerRegistration addTouchEndHandler(final TouchEndHandler handler) {
711        return valueBoxBase.addTouchEndHandler(event -> {
712            if (isEnabled()) {
713                handler.onTouchEnd(event);
714            }
715        });
716    }
717
718    @Override
719    public HandlerRegistration addTouchCancelHandler(final TouchCancelHandler handler) {
720        return valueBoxBase.addTouchCancelHandler(event -> {
721            if (isEnabled()) {
722                handler.onTouchCancel(event);
723            }
724        });
725    }
726
727    @Override
728    public HandlerRegistration addDoubleClickHandler(final DoubleClickHandler handler) {
729        return valueBoxBase.addDoubleClickHandler(event -> {
730            if (isEnabled()) {
731                handler.onDoubleClick(event);
732            }
733        });
734    }
735
736    @Override
737    public HandlerRegistration addClickHandler(final ClickHandler handler) {
738        return valueBoxBase.addClickHandler(event -> {
739            if (isEnabled()) {
740                handler.onClick(event);
741            }
742        });
743    }
744
745    @Override
746    protected FocusableMixin<MaterialWidget> getFocusableMixin() {
747        if (focusableMixin == null) {
748            focusableMixin = new FocusableMixin<>(new MaterialWidget(valueBoxBase.getElement()));
749        }
750        return focusableMixin;
751    }
752
753    @Override
754    protected ErrorMixin<AbstractValueWidget, MaterialLabel> getErrorMixin() {
755        if (errorMixin == null) {
756            errorMixin = new ErrorMixin<>(this, errorLabel, valueBoxBase, label);
757        }
758        return errorMixin;
759    }
760
761    protected ReadOnlyMixin<MaterialValueBox, ValueBoxBase> getReadOnlyMixin() {
762        if (readOnlyMixin == null) {
763            readOnlyMixin = new ReadOnlyMixin<>(this, valueBoxBase);
764        }
765        return readOnlyMixin;
766    }
767
768    protected ActiveMixin<MaterialValueBox> getActiveMixin() {
769        if (activeMixin == null) {
770            activeMixin = new ActiveMixin<>(this, label);
771        }
772        return activeMixin;
773    }
774
775    protected CounterMixin<MaterialValueBox<T>> getCounterMixin() {
776        if (counterMixin == null) {
777            counterMixin = new CounterMixin<>(this);
778        }
779        return counterMixin;
780    }
781}