Search code examples
vaadinvaadin8

Vaadin: open calendar on field focus for datefield


Vaadin widgets are simple and awesome! But they are also poorly configurable. I need my DateField widget to open calendar on focus event. I didn't find that functionality in official Vaadin documentation. I found some 3rd party widget here, but it's compiled for Vaadin 7.7 and I use latest Vaadin (8.0.6). Also it has Joda-time 2.1 dependency which is highly undesirable in my project. So, is there any simple way to tune stock vaadin DateField widget to open it's calendar on field focus, or do I need to write my own component for that? Any help is appreciated.


Solution

  • As I was saying in my comment, as far as I know, currently the framework does not offer an implicit way to programmatically open the calendar popup. The same thing goes for some other components such as the grid editor, or the combo item list.

    One quick workaround I can think of, is to add a javascript extension that registers focus listeners for all date fields, and clicks the button when a date field is focused. Please find below a sample.

    P.S. If you only need to apply this to only some date fields, you can add IDs and pass them to the JS, where you'll do something like document.getElementById('myDateFieldId') instead of document.getElementsByClassName("v-datefield").

    1) Layout with components

    public class MyDateFieldComponent extends HorizontalLayout {
        public MyDateFieldComponent() {
            // basic setup
            DateField fromDateField = new DateField("From", LocalDate.of(2011, Month.FEBRUARY, 6));
            DateField toDateField = new DateField("To", LocalDate.of(2018, Month.FEBRUARY, 6));
            setSpacing(true);
            addComponents(fromDateField, toDateField);
    
            // add the extension
            addExtension(new CalendarFocusPopupOpenerExtension());
        }
    }
    

    2) Extension - java/server side

    import com.vaadin.annotations.JavaScript;
    import com.vaadin.server.AbstractJavaScriptExtension;
    
    @JavaScript("calendar-focus-popup-opener-extension.js")
    public class CalendarFocusPopupOpenerExtension extends AbstractJavaScriptExtension {
        public CalendarFocusPopupOpenerExtension() {
            // call the bind function defined in the associated JS
            callFunction("bind");
        }
    }
    

    3) Extension - js/client side

    window.com_example_calendar_CalendarFocusPopupOpenerExtension = function () {
        this.bind = function () {
            if (document.readyState === "complete") {
                // if executed when document already loaded, just bind
                console.log("Doc already loaded, binding");
                bindToAllDateFields();
            } else {
                // otherwise, bind when finished loading
                console.log("Doc nod loaded, binding later");
                window.onload = function () {
                    console.log("Doc finally loaded, binding");
                    bindToAllDateFields();
                }
            }
        };
    
        function bindToAllDateFields() {
            // get all the date fields to assign focus handlers to
            var dateFields = document.getElementsByClassName("v-datefield");
            for (var i = 0; i < dateFields.length; i++) {
                addFocusListeners(dateFields[i]);
            }
        }
    
        function addFocusListeners(dateField) {
            // when focusing the date field, click the button
            dateField.onfocus = function () {
                dateField.getElementsByTagName("button")[0].click();
            };
    
            // or when focusing the date field input, click the button
            dateField.getElementsByTagName("input")[0].onfocus = function () {
                dateField.getElementsByTagName("button")[0].click();
            };
        }
    };
    

    4) Result Calendar popup open on focus


    LATER UPDATE

    A second approach could be to assign some IDs to your fields, and then check periodically to see when all are visible, and as soon as they are, bind the focus listeners.

    1) Layout with components

    public class MyDateFieldComponent extends HorizontalLayout {
        public MyDateFieldComponent() {
            // basic setup
            DateField fromDateField = new DateField("From", LocalDate.of(2011, Month.FEBRUARY, 6));
            fromDateField.setId("fromDateField"); // use id to bind
            fromDateField.setVisible(false); // initially hide it
    
            DateField toDateField = new DateField("To", LocalDate.of(2018, Month.FEBRUARY, 6));
            toDateField.setId("toDateField"); // use id to bind
            toDateField.setVisible(false); // initially hide it
    
            // simulate a delay until the fields are available
            Button showFieldsButton = new Button("Show fields", e -> {
                fromDateField.setVisible(true);
                toDateField.setVisible(true);
            });
    
            setSpacing(true);
            addComponents(showFieldsButton, fromDateField, toDateField);
    
            // add the extension
            addExtension(new CalendarFocusPopupOpenerExtension(fromDateField.getId(), toDateField.getId()));
        }
    }
    

    2) Extension - java/server side

    @JavaScript("calendar-focus-popup-opener-extension.js")
    public class CalendarFocusPopupOpenerExtension extends AbstractJavaScriptExtension {
        public CalendarFocusPopupOpenerExtension(String... idsToBindTo) {
            // send the arguments as an array of strings
            JsonArray arguments = Json.createArray();
            for (int i = 0; i < idsToBindTo.length; i++) {
                arguments.set(i, idsToBindTo[i]);
            }
    
            // call the bind defined in the associated JS
            callFunction("bind", arguments);
        }
    }
    

    3) Extension - js/client side

    window.com_example_calendar_CalendarFocusPopupOpenerExtension = function () {
        var timer;
    
        this.bind = function (idsToBindTo) {
            // check every second to see if the fields are available. interval can be tweaked as required
            timer = setInterval(function () {
                bindWhenFieldsAreAvailable(idsToBindTo);
            }, 1000);
        };
    
        function bindWhenFieldsAreAvailable(idsToBindTo) {
            console.log("Looking for the following date field ids: [" + idsToBindTo + "]");
            var dateFields = [];
            for (var i = 0; i < idsToBindTo.length; i++) {
                var dateFieldId = idsToBindTo[i];
                var dateField = document.getElementById(dateFieldId);
                if (!dateField) {
                    // field not present, wait
                    console.log("Date field with id [" + dateFieldId + "] not found, sleeping");
                    return;
                } else {
                    // field present, add it to the list
                    console.log("Date field with id [" + dateFieldId + "] found, adding to binding list");
                    dateFields.push(dateField);
                }
            }
    
            // all fields present and accounted for, bind the listeners!
            clearInterval(timer);
            console.log("All fields available, binding focus listeners");
            bindTo(dateFields);
        }
    
        function bindTo(dateFields) {
            // assign focus handlers to all date fields
            for (var i = 0; i < dateFields.length; i++) {
                addFocusListeners(dateFields[i]);
            }
        }
    
        function addFocusListeners(dateField) {
            // when focusing the date field, click the button
            dateField.onfocus = function () {
                dateField.getElementsByTagName("button")[0].click();
            };
    
            // or when focusing the date field input, click the button
            dateField.getElementsByTagName("input")[0].onfocus = function () {
                dateField.getElementsByTagName("button")[0].click();
            };
        }
    };
    

    4) ResultCalendar popup open on focus