Webformulieren en een datumprikker

Ben de laatste tijd bezig met Javascript. Meer dan ik had gehoopt. Ik vind het heerlijk om zelf met Javascript te knutselen. Tegelijkertijd probeer ik wel te waken om niet overal te pas en te onpas JS in te zetten, als het ook anders kan.

Ik gebruik graag beschikbaar gestelde snippets of libraries, die ik dan aanpas op mijn wensen. Zo wilde ik laatst een datumprikker plaatsen in een formulier, en dan kom je al snel op een Javascript uit. Helemaal wanneer je beperkte invloed hebt op de manier waarop het formulier wordt opgebouwd (3e partij en geen toegang tot hun source).

Nou ja, je zou het gehele formulier kunnen herschrijven met behulp van XLST, en er dan een HTML5 verhaal van maken (wat zeker nog op mijn to-do lijst staat, met Bootstrap smaakje), maar dat is een project an sich. Tevens zijn de HTML5 input features niet geschikt voor alle devices en browsers op het moment, dus je zit toch al snel met nog wat Javascript te kijken.

In dit geval heb ik daar nu de tijd niet voor en moet het snel opgeleverd worden.

Zoeken

Dus op zoek naar dat script. Tijdens mijn zoektocht kom je dan wel een aantal scripts tegen die behoorlijk wat mogelijkheden lijken te bieden, maar dan toch weer her en der, bij implementatie, in de soep draaien (uitsluiten van datums en tijdsmomenten, mogelijkheden tot opmaak gelijk trekken met de huisstijl).

Dan kun je drie dingen doen;

  • Troubleshooten en uiteindelijk bugfixen in het hoofdscript (waar jij niet aan de basis hebt gestaan dus aardig wat extra tijd kwijt bent om het te doorgronden)
  • Zelf van de grond af aan opbouwen (want alles naar eigen wens)
  • Weggooien en op zoek naar een alternatief

Ik heb eerst getroubleshoot, maar al snel de bijl begraven en ik ben na een paar snelle simpele fixes die niet het gewenste resultaat hadden, verder gaan zoeken. Zelf bouwen was al snel vergeten door mij, want tijd en onderhoud. Zeker als je nog op tijdelijk contract staat is dit niet de keuze die je wilt maken.

Uiteindelijk ben ik bij pickadate.js van Amsul terecht gekomen. Belangrijkste criteria kon aan voldaan worden;

  1. hoog configureerbaar - Kantooruren + speciale dagen met afwijkende tijden.
  2. in de basis responsive design.
  3. goed gedocumenteerd en dus snel(ler) manipuleerbare source.

Top, maar toch...

Alsnog heb ik een volledig custom aanroep erboven op geschreven om bijvoorbeeld een extra knop te maken die de eerstvolgende speciale datum invult zodra deze aangeklikt wordt. Daarna was het van belang om het beheerbaar te houden en heb ik een setje datums aan het begin van het bestand in gegeven, waarna de een functie zelf gaat evalueren (loop met splice en sort door een array heen) of de datum nog wel geldig is. Dit voorkomt dat ik de bestanden maandelijks mag gaan updaten. Ik heb wel wat beters te doen. Zeker als de datums meestal voor een heel jaar bekend zijn...

Onderaan het blog zal ik de custom initialisatie code plaatsen zodat anderen er eventueel gebruik van kunnen maken.

Tevens wilde ik een eenvoudige knop bovenaan in de datumkiezer zetten. Er staat namelijk standaard 'Sluiten', 'Wissen' en 'Vandaag' beschikbaar. Handige functionaliteit. Ik wilde gewoon de eerstvolgende door mij aangegeven speciale datum selecteert.

Op zich vrij eenvoudig, omdat ik een Array heb die ik splice op basis van het moment waarop een gebruiker deze bekijkt. Dus ik vergelijk op het moment van het openen van de datumkiezer het huidige moment (new Date();) met de speciale datums en splice deze uit de array en sort deze daarna van laag naar hoog. Dit betekent dat de dichtst bijzijnde datum ten opzichte van vandaag altijd op index 0 in de array zit.

Dit kan ik vervolgens gebruiken om gewoon de functionaliteit van de 'Vandaag' knop te kopiëren en in de source van pickadate.js toevoegen.

Daarna wilde ik dat deze knoppen boven het kalendergedeelte kwamen te staan. Onderin stonden ze zo verstopt.

Ook de source code aangepast en het gedeelte dat de html rendert aangepast en verplaatst.

Erg leuk maar voor je het weet zit je echt knie-diep in custom code, met risico's dat het weer moet bijgehouden worden bij updates etc.

Dat was voor mij ook het moment om te stoppen met aanpassingen. Alles natuurlijk goed gedocumenteerd met comments, maar ja... we weten allemaal hoe nuttig die soms zijn...

Ik maak tevens gebruik van jQuery om de DOM her en der te manipuleren en te tweaken, maar ook om pickadate.js te binden bijvoorbeeld. Deze jQuery selectors kun je natuurlijk vervangen met document.getElementById("id").pickadate(); bijvoorbeeld. Wel eerst even zorgen dat jQuery wordt ingeladen op je webpagina natuurlijk.

Een demo:

See the Pen pickadate.js example by Tim Huesken (@TimHuesken) on CodePen.

Zoals beloofd (lap code):

    /*Custom aanroep van pickadate.js door Tim Huesken - tim.huesken[at]outlook.com*/

    /*-------------------------------------------------------------------*/
    /*Alle speciale datums.*/
    /*Format waarin je een datum opvoert: new Date(Jaar, Maandnummer-1,Dag,Uur,Minuut,Seconde,Milliseconde).getTime(),*/
    /*Oude datums kun je laten staan, die worden netjes eruit gefilterd*/
    var zondagen = [ new Date( 2016, 1, 21, 0, 0, 0, 0 ).getTime(), new Date( 2016, 3, 3, 0, 0, 0, 0 ).getTime(), new Date( 2016, 4, 16, 0, 0, 0, 0 ).getTime(),
                    new Date( 2016, 5, 26, 0, 0, 0, 0 ).getTime(), new Date( 2016, 8, 11, 0, 0, 0, 0 ).getTime(), new Date( 2016, 10, 6, 0, 0, 0, 0 ).getTime(),
                    new Date( 2016, 11, 18, 0, 0, 0, 0 ) ];
    /*------------------------------------------------------------------*/

    //Kies hier de tekst van de knop en de popup bij mouseover speciale datums.
    var textButton = "Speciale Zondag";
    var zondag = '';
    var format = 'yyyy-dd-mm';

    /*Hiermee wordt de Speciale zondag button gedefinieerd.*/
    /*Functionaliteiten en stijl kun je hier manipuleren. Haal bijvoorbeeld de regel weg en vervang met: var appendButton = ''; om de button in zijn geheel weg te halen.*/
    var appendButton = '<button id="zondag" class="picker__button--zondag" type="button">' + textButton + '</button>';

    //Helper functie voor het toevoegen van de juiste data-pick waarde voor de Speciale zondag button. Hier hoef je niets aan te wijzigen.
    function dataPickZondag(){
        for ( var i = zondagen.length -1; i >= 0; i-- ) {
            if ( new Date().getTime() >= zondagen[ i ] )  {
                zondagen.splice( i, 1 );
            }
        }
        for ( var j = 0; j < zondagen.length -1; j++ ) {
          if ( $( '[data-pick=' + zondagen[ j ] + ']' ) !== null ) {
                  $( '[data-pick=' + zondagen[ j ] + ']' ).addClass( 'pickadate--zondag' );
                  $( '[data-pick=' + zondagen[ j ] + ']' ).attr( "title" , "Speciale zondag" );
                  $( '[data-pick=' + zondagen[ j ] + ']' ).removeClass( 'picker__day--disabled' );
              }
          }
        zondagen.sort();
        if ( $( '[data-pick=' + zondagen[ 0 ] + ']' ) !== null ) {
                    $( '#zondag' ).attr( "data-pick", zondagen[0] );
            }
            else {
            $( 'button' ).remove( ".picker__button--zondag" );    
            }
    }

    /*Hier worden de datumprikker en tijdprikker aangeroepen en opgemaakt. Basis instellingen vind je hier.*/
    var datepicker = $( "input[placeholder='Datum placeholder']" ).pickadate({
            firstDay: 1,
            min: new Date(),
            max: +45,
            //formatSubmit: format,
            hiddenName: true,
            //closeOnSelect: false,
            closeOnClear: false,
            disable: [
            true,
            1,2,3,4,5,
            [2016, 1, 21],
            [2016, 3, 3],
            [2016, 3, 27, 'inverted'],
            [2016, 4, 5, 'inverted'],
            [2016, 4, 16],
            [2016, 5, 26],
            [2016, 8, 11],
            [2016, 10, 6],
            [2016, 11, 18],
            [2016, 11, 25, 'inverted'],
            [2016, 11, 26, 'inverted'],
            [2017, 1, 1, 'inverted']
            ],
            onRender: function (){
                //format = format + ' ' + zondag,
                nu = new Date().getTime();
                teLaat = ( new Date().setHours( 0, 0, 0, 0 ) + 56700000 );
                $( '.picker__buttons' ).append( appendButton );
                dataPickZondag();
                $( '.picker__button--clear' ).click( function() {
                    tpicker.clear();
                });
                    if ( nu > teLaat ) {
                        $( 'button' ).remove( ".picker__button--today" );
                    }
                }
        }),
        dpicker = datepicker.pickadate( 'picker' );

    var timepicker = $( "input[placeholder='Tijd placeholder']" ).pickatime({
            format: 'HH:i uur',
            formatSubmit: 'HH:i',
            hiddenName: true,
            min: [9,0],
            max: [16,30],
            disable: [
            [12,0],
            [12,30]
            ]
        }),
        tpicker = timepicker.pickatime('picker');

        if (dpicker === undefined) {} else { 
        dpicker.on('open', function (event) {
            var nu = new Date().getTime(),
            teLaat = (new Date().setHours(0,0,0,0) + 56700000);
            $('.picker__button--clear').click(function(){
                    tpicker.clear();
                });
            if(document.getElementById("zondag")){
                dataPickZondag();
            }
            
            if (nu > teLaat) {
                dpicker.set('min', 1);
                $( 'picker__button--today').addClass( 'picker__day--outfocus picker__day--disabled' );
                $( '[data-pick=' + new Date().setHours( 0, 0, 0, 0 ) + ']' ).removeClass( 'picker__day--selected picker__day--highlighted' );
                $( 'button' ).remove( ".picker__button--today" );
            }
        });
    }

        if (dpicker === undefined) {} else {
            dpicker.on( 'set', function ( event ) {
                if (( event.select ) && ( zondagen.indexOf(dpicker.get( 'select' ).pick) != -1 )) {
                    //zondag = 'JASNO op Zondag',
                    $( "input[data-name='Eventtype']" ).val("JASNO op Zondag"),
                    $( "input[data-name='Evenement']" ).val("Ja"),
                    tpicker.clear(),
                    tpicker.set('enable', true),
                    tpicker.set({
                    interval: 60,
                    min: [11,0],
                    max: [15,0],
                    }),
                    tpicker.on( 'open', function ( remove ) {
                        $( 'p' ).remove( ".text-center" );
                        }),
                    setTimeout( tpicker.open, 0 );
                }
                else if (( event.select ) && ( dpicker.get( 'select' ).pick !== new Date().setHours( 0, 0, 0, 0 ) )) {
                    //zondag = '',
                    $( "input[data-name='Eventtype']" ).val("Bezoek"),
                    $( "input[data-name='Evenement']" ).val("Ja"),
                    tpicker.clear(),
                    tpicker.set( 'enable' , true),
                    tpicker.set({
                        interval: 30,
                        min: [9,0],
                        max: [16,30],
                        disable: [
                        {
                            from:[12,0], to:[12,30]
                        }
                        ]
                    }),
                    tpicker.on( 'open', function ( remove ) {
                            $( 'p' ).remove( ".text-center" );
                        }),
                    setTimeout( tpicker.open, 0 );
                }
                else if (( event.select ) && ( dpicker.get( 'select' ).pick === new Date().setHours( 0, 0, 0, 0 ) )) {
                    var tijd = (new Date().getTime() + ( 60 * 45 * 1000 )),
                    min = new Date(tijd),
                    uur = min.getHours(),
                    afgerond = ( 30 * ( Math.ceil( min.getMinutes()/30 )));
                    //zondag = '',
                    $( "input[data-name='Eventtype']" ).val("Bezoek"),
                    $( "input[data-name='Evenement']" ).val("Ja"),
                    tpicker.clear(),
                    tpicker.set( 'enable', true ),
                    tpicker.set({
                        interval: 30,
                        min: [uur,afgerond],
                        max: [16,30],
                        disable: [
                        {
                            from:[12,0], to:[12,30]
                        }
                        ]
                    }),
                    tpicker.on( 'open', function ( remove ) {
                            $( 'p' ).remove( ".text-center" );
                        }),
                    setTimeout( tpicker.open, 0 );
                }
            });
        }

Aangepaste Source

Mocht je hier verder gebruik van willen maken, kun je hier de verdere source files downloaden. Ik verwacht wel van je dat je weet hoe je deze .js en .css bestanden moet aanroepen in je webpagina en dat je weet de input elementen in een form aan te roepen en succesvol bovenstaande code kunt aanpassen zodat de boel netjes aan de elementen wordt gebonden. Ook neem ik aan dat je de css kunt instellen naar wens. Mochten er onverhoopt toch vragen ontstaan, laat ze hieronder achter en ik zal ze proberen te beantwoorden.

Ook weet ik dat mijn code efficiënter en beter kan. Als je iets ziet, mag je het natuurlijk melden. Graag zelfs. :-)