Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get value of invalid html5 date input

I am building a calendar picker to accompany (and replace the default calendar date picker provided by some browsers) an input of type="date".

It is documented that for "simplicity" that Chrome's (and possibly other browsers) date input allows invalid dates to be inputted, but they will fail to validate (ie. 31/06/2017 can be inputted, but produces a "value" of "").

When an invalid date is entered into the input, how can I get the value of that invalid date?

var date_input = document.getElementById('date_input');
var output_div = document.getElementById('output_div');

output_div.innerHTML = date_input.value;

date_input.addEventListener('input', function(e){
    output_div.innerHTML = e.target.value;
});
<p>To quickly input an invalid date, select the day field and press the down arrow (on Chrome OSX this displays an empty div below the input instead of the invalid date). This will select 31/06/2017</p>
<input type="date" value="2017-06-01" id="date_input" />
<br />
<br />
<div id="output_div" />
like image 605
haxxxton Avatar asked Oct 18 '22 10:10

haxxxton


1 Answers

Not like this

var date_input = document.getElementById( 'date_input' );
var output_div = document.getElementById( 'output_div' );

output_div.textContent = date_input.valueAsDate; // :-)

date_input.addEventListener( 'input', function( evt ){
    var val = date_input.value,
        sel = document.getSelection();
    if ( date_input.validity.badInput ) {
        // date_input.focus(); // with or without this,
        // date_input.select(); // this doesn't work but,
        // due to the added <span> wrapper:
        sel.selectAllChildren( date_input.parentElement );
        // this does. So:
        val = sel.toString() || function() {
            var copied = document.execCommand( 'copy' );
            sel.empty(); // clear the selection
            return "Invalid date" + ( copied ? " copied to clipboard." : "" );
        }();
    }
    output_div.textContent = val;
});
#output_div {
    margin-top: 1em;
}
<p>To quickly input an invalid date, select the day field and press the down arrow (on Chrome OSX this displays an empty div below the input instead of the invalid date). This will select 31/06/2017</p>
<span><input type="date" value="2017-06-01" id="date_input"></span>
<div id="output_div"></div>

Apparently since the value shown is actually the output of the shadow DOM child of the <input>, and we can't access the user-agent's shadow DOM, the visible value is effectively unknown to JS.

For accessibility reasons, the visible value has to be accessible to screen readers and the like, and can also be manually copied to the clipboard.

Having tried multiple ways of programmatically selecting and copying the text, I have run out of ideas and patience, and could only manage to copy it to the clipboard.

  • If someone else can figure out how to read the aria-value* attributes of the #shadow-root (user-agent) with JS, then you have a way forward.
  • If someone else can figure out how to assign the selection string to a var, you have another way forward.

However

Since the invalid input is limited to being predictably out of range, you could monitor what the last valid value was before it was changed to something invalid, then calculate what it must surely now be.

  • If using the datepicker, all the inputs will be valid.
  • If typing, monitor the keystrokes.
  • If using the arrow buttons, and the date was 30/06/2017 before a change that rendered it invalid, and 29/06/2017, 30/05/2017, 30/07/2017, 30/06/2016 and 30/06/2018 are all valid, the invalid date must be 31/06/2017.

I have not experimented (yet) with making that work, but am confident it can. I would be posting that solution with this answer, but half my day has been swallowed by this question already and I'm hungry. If a sincere interest is expressed, I will happily try and provide the codez.

Still no good

This will display invalid dates if they're created using the arrow buttons, and only if the last date was valid. I could probably find a way around that, but officially give up! Life's too short.

According to MDN:

... the displayed date format will be chosen based on the set locale of the user's operating system.

This would make tracking keystrokes unreasonably non-trivial.

However, I just discovered <input type="datetime-local"> which might solve that issue, and could possibly solve others.

So this does something useful:

But trying to force this to work is beginning to feel like trying to insert a watermelon where one would not usually be expected to fit.

It's a ham-fisted kludge with little to no charm, that is likely to break if anyone looks at it too hard. Best disclaimer ever right ;)

var date = "",
    input = document.querySelector( "input" ),
    output = document.querySelector( "output" ),
    bits = function( d ) {
        return [ d.getFullYear(), d.getMonth() + 1, d.getDate() ];
    },
    adjustedDate = function( o ) {
        var d = new Date( date ),
            b = bits( d ),
            year = b[ 0 ],
            month = b[ 1 ],
            day = b[ 2 ];
        switch ( o.i ) {
            case "d": return [ year, month, ( day + o.v ) || 31 ];
            case "m": return [ year, month + o.v, day ];
            case "y": return [ year + o.v, month, day ];
        }
    },
    calcDate = function() {
        var ad, vd, rd;
        [ { i: "d", v: 1 },
          { i: "d", v: -1 },
          { i: "m", v: 1 },
          { i: "m", v: -1 },
          { i: "y", v: 1 },
          { i: "y", v: -1 }
        ].forEach( ( v ) => {
            ad = adjustedDate( v ).join( "-" );
            vd = bits( new Date( ad ) ).join( "-" );
            if ( ad !== vd ) {
                rd = ad;
            }
        } );
        return rd.split( "-" ).map( ( v ) => {
            v = v.toString();
            return v.length === 1 ? "0" + v : v;
        } ).join( "-" ); // tired so mental
    },
    showDate = function() {
        output.textContent = date = ( input.value || calcDate() );
    };
showDate();
input.addEventListener( "input", showDate, false );
output {
    margin-left: 1em;
}
<p>To quickly input an invalid date, select the day field and press the down arrow. This will select 31/06/2017 and output the desired invalid date string.</p>
<form><input type="date" value="2017-06-01"><output></output></form>
like image 127
Fred Gandt Avatar answered Oct 21 '22 02:10

Fred Gandt