Ah Woocommerce Bookings from Woothemes, how long did we wait for a really good bookings extension for Woocommerce, and when it finally came it didn’t disappoint - robust, stable, versatile and of course tying in with all the other great Woothemes extensions.

But alas, there is no way that any WordPress plugin developer could ever encapsulate every use-case from the thousands (is it millions now?) of e-commerce stores powered by Woocommerce, and so when the plugin came out so did hundreds of requests for slight tweaks or customisations to its core functionality.

Thankfully as with every Woothemes plugin there are plenty of hooks to play with and everything else is incorporated in the 100% WP way making it very easy to extend without having to ever touch a core file. I will cover o

DatePicker to Available Dates Dropdown

The date/time picker you get straight out of the box with the Woocommerce Bookings plugin is great, but for some stores having a list of available dates in a dropown is a more user-friendly interface. Here is how you can do it:

As I will be sharing this code in a Gist link at the end of the article I better be honest. While the first main objective in this plugin is to change out the datepicker field for a dropown, the first line of code is actually $wswp_dates_built = false; - this sets a global variable (Google PHP variable scope if you don’t know what I mean by global) to record if we have built the options for the dropdown. If I remember correctly this prevented getting multiple sets of the same dates in the dropdown, because the actual date-picker field is split into two/three actual fields in the bookings form.

So… on to the first objective - turning our date-picker field from type=date-picker to a dropown and complete with options in the form of available dates.

As I said the date-picker is actually split accross multiple fields, clicking on any of them sprouts the picker, and the values of all of them go into the cost calculations. A dropdown is only 1 field, so what we need to do is keep the original fields, hide them and show our new dropdown in their place. We can then take the selection from the dropdown and put it into the original fields on the fly to give Woo Bookings what it needs to give a price and allow a purchase of the given block(s). Thankfully we can do all of that using only one wonderful filter.


add_filter('booking_form_fields','wswp_booking_form_fields');

function wswp_booking_form_fields($fields) {
    global $wswp_dates_built;
    $i = 0;
    foreach($fields as $field) {
        $new_fields[$i] = $field;
        if ($field['type'] == "date-picker" && $wswp_dates_built === false)
        {
            $s = $i;
            $new_fields[$s]['class'] = array('picker-hidden');
            $i++;
            $new_fields[$i] = $field;
            $new_fields[$i]['type'] = "select";
            $new_fields[$i]['options'] = wswp_build_options($field['availability_rules'][0]);
            $new_fields[$i]['class'] = array('picker-chooser');
        }
        $i++;
    }
    return $new_fields;
}

So… first we bring in the global $wswp_dates_built as we need it here. Then (as the original array is associative) we initiate a numeric counting variable that we can use to keep track of the array items as we make changes to its values.

Then we go into the actual array of form fields passed into the filter as $fields and build a new array of fields to pass back out - $new_fields. Firstly as we are keeping all fields we store every field into the $new_fields array. We check if it is a date-picker field and if we haven’t built the dates yet we go into action. We add the class “picker-hidden” to the original field stored in the array at the current value of $i, and then we add 1 to $i with $i++ to add a clone of that same field into the array at the new value of $i. We then make the modifications to that field to turn it into a dropdown.

We change the type to “select”, add the class picker-chooser for targetting it within the DOM (Document Object Model), and we also add an item ‘options’ to the field, in here we store our dates, which come from a function and make up the options for the dropdown -- I will show you that function next. The rest of that code simply iterates on $i and repeats the cycle for the remaining fields and returns $new_fields as the set of fields now making up your bookings form.

So now the function that is the hingepin of this code wswp_built_options, into which we have passed the availability rules for the original picker field. In case you are wondering the availability rules is a very complex array of rules created from the settings used when creating the bookable product. In our function we parse into that array to get the available dates.


function wswp_build_options($rules,$building = false) {
    global $wswp_dates_built;
    $dates = array();
    foreach($rules as $dateset) {
        if ($dateset[0] == "custom") {
            $year = reset(array_keys($dateset[1]));
            $month = reset(array_keys($dateset[1][$year]));
            $day = reset(array_keys($dateset[1][$year][$month]));
            $dtime = strtotime($year."-".$month."-".$day);
            $dates[$dtime] = date("d/m/Y",$dtime);
        }

    }
    ksort($dates);
    foreach($dates as $key => $date) {
        $dates[date("Y-m-d",$key)] = $date;
        unset($dates[$key]);
    }
    $wswp_dates_built = true;
    return $dates;
}

I won’t bore you with a line by line on that function because I would then have to go into the structure of the array. For those that want to know more Google the functions, ask in the comments, and play around with print_r on the $rules variable.

Now finally we just have to move our dropdown to the correct place in the DOM and attach an event handler to the change event on the dropdown so that when a date is chosen it populates the original picker fields with the needed values. As it is a fairly short piece of javascript I decided to simply place it in the footer using the wp_footer hook:


add_action('wp_footer','wswp_css_js');

function wswp_css_js() {
    //adding in footer as not enough to justify new stylesheet and js file
    ?><style type="text/css">
        .picker-hidden .picker,.picker-hidden legend {
            display:none;
        }
        </style>
        <script type='text/javascript'>
            jQuery(function($) {
//Move .picker chooser up in the DOM
                $(".picker-chooser").insertBefore('.wc-bookings-date-picker-date-fields');
//Attach handler to insert chosen date into picker fields
                $("select#wc_bookings_field_start_date").on('change', function() {
                var selectedDate = $(this).val()
                var selectedDateBreakdown = selectedDate.split("-");

                $( "input[name*='wc_bookings_field_start_date_year']" ).val( selectedDateBreakdown[0] );
                $( "input[name*='wc_bookings_field_start_date_month']" ).val( selectedDateBreakdown[1] );
                $( "input[name*='wc_bookings_field_start_date_day']" ).val( selectedDateBreakdown[2] );
            });
            });

            </script>
            <?php
}

And voila - now load up the page of any bookable product and where you once saw the picker fields you will now see a nice dropdown.

As promised here is the Gist for this: https://gist.github.com/LiamBailey/417e8bf233c7cd7c0fb8 - and if you use Resources i.e. to have different locations for courses etc, I made an update to this script to handle that you can find that here: https://gist.github.com/LiamBailey/a33dcf9fedac2229fcdb.

Note: To anyone expecting the next post from me to be a guide on importing Yahoo Miva (store platform) products from its xlsx export files into Woocommerce using WP All Import, there have been some problems with that and if I can resolve them it will be covered at a later date.Quality: The Codeable Differene

  • @Liam Once again, great advice! Translating the customer’s experience into code is a work of art which you consistently do INCREDIBLY!

    • Liam Bailey

      Thanks @brad_griffin:disqus – just saw this – awesome. Where I would be without your support is unclear but I sure do love it :-) – thanks again mate

  • Zohar

    Hi, thanks for this. It works like a charm!

    I have two questions:

    1. What do we need to do in order to hide past or non-bookable dates?
    2. Would it be possible to show a table of dates with button to select rather than drop-down list?

    Cheers!

    P.S. To actually hide the legend of the picker I had to add !important to the code.

    • Liam Bailey

      Hi Zohar,

      I really apologise for the delay I must find out if I am receiving notifications of comments :-)

      1 – non-bookable dates should not be appearing in the dropdown – and neither should past ones. Maybe I can take a look at your code etc – if you want me to have a peek you can email me details to liam[at]codeable[dot]io

      2 – Interesting idea… Of course it can. All my code does is build an array of available dates, one you have that the world is your oyster in terms of how you display the dates. In your case the first thing you need to sort out is making it only show available dates as in point 1.

      Hope this helps

      Liam

      • Kobus

        Hi Liam,

        I am not familiar with Disqus, and I don’t know how to tag you. I hope you receive this notification. Can you please have a look at my comments?

    • Richard Chamberlain

      I have the same issue 1. here.

  • aguerram

    Can I rearrange the order of the booking form elements? If so could you please point me in the right direction?

    • Liam Bailey

      Absolutely. I will look into this and get back to you shortly.

      Ps sorry for delay in responding.

  • Mark Lloyd Biñar

    Hello, may I ask how can I change the text inputs to dropdown menu. Please see below link for the screenshots. I want to change those text inputs to Dropdown. THanks for your help in advance.

    https://i.stack.imgur.com/zr5PN.png

    • Sakshi Grover

      Hi Mark,

      Did you ever get solution to this.? Please let me know if you could fix this by any means.

      Thanks

  • Steven Held

    The newest update to the Woocommerce Bookings plugin (1.9.14) has made your code not function anymore. Any idea on how to fix? I have a pretty angry client.

  • Liam Bailey

    @disqus_dZrcuO817C:disqus If you send me a login to liam@codeable.io I will take a look at this for you.

  • Jonathon

    Thanks so much for sharing this code. I forked your Gist to fix two minor issues I had when trying to use this code on a current project. It seems an array in the plugin is now associative when it wasn’t before.

  • Beth perrou

    Thanks for this Liam. I was working on a very similar approach to what you did, but didn’t want to apply it to every product on my site. I have kind of merged your work with mine.

    There was one issue too, that you didn’t catch on your code. If the $rules returns a NO for Bookable date, then the associative array is empty (not bookable) your’s still has the dates showing up. You need to add this test
    if($dateset[‘range’][$year][$month][$day]) {

    $dtime = strtotime($year.”-“.$month.”-“.$day);
    $dates[$dtime] = date(“d/m/Y”,$dtime);
    }

    I also added a product category to allow users to only specify this use on individual products. My version is here,https://github.com/baperrou/WooBooking-Dropdown-coursedates

    Thanks again for your work.

  • Hi Liam, thank you so much for this code!

    The only problem I have is that I only have one date for the event I wish to book, so I need the plugin to fill in the date without the user having to click on the field.

    I see that woocommerce’s date field is only populate when there is a change detected on the dropdown. I’ve changed this to “click” in the javascript, but non-tech savvy users might not click at all, seeing that the date is correct and then become confused when they can’t book. Anyway to change it so that the date is populated on the load of the page with the first date or something similar? Would love hearing back from you.