Hi,
So here is the process, hope you will understand, if not fell free to ask.
- (optional)
Action hook to set default 30min slot intervals on all booking listings
// SET DEFOULT 30min BOOKING SLOT INTERVAL
add_action('hivepress/v1/models/listing/create', function($listing_id) {
if(hivepress()->get_version( 'bookings' )){
update_post_meta($listing_id, 'hp_booking_slot_duration', 30);
update_post_meta($listing_id, 'hp_booking_min_time', 3600);
}
});
- (optional)
Deactivate the fields to set the time spans
add_filter(
'hivepress/v1/models/listing/attributes',
function( $attributes ) {
if ( isset($attributes['booking_slot_duration']) ) {
$attributes['booking_slot_duration']['editable'] = false;
}
if ( isset($attributes['booking_slot_interval']) ) {
$attributes['booking_slot_interval']['editable'] = false;
}
if ( isset($attributes['booking_moderated']) ) {
$attributes['booking_moderated']['editable'] = false;
}
return $attributes;
},
1000,
2
);
- Add two select fields for the time start and time end (same as the time select of hivepress)
// ADD START AND END TIME IN BOOKING FORM
add_filter('hivepress/v1/forms/booking_make', 'form_mod1', 1000);
function form_mod1( $form ) {
$form['fields']['_start_time'] = [
'label' => esc_html__( 'Start Time', 'hivepress-bookings' ),
'type' => 'select',
'options' => [],
'source' => $form['fields']['_time']['source'],
'required' => true,
'_separate' => true,
'_order' => 5,
'attributes' => [
'data-parent' => '_dates',
'class' => [ 'booking-time-start' ],
],
];
$form['fields']['_end_time'] = [
'label' => esc_html__( 'End Time', 'hivepress-bookings' ),
'type' => 'select',
'options' => [],
'source' => $form['fields']['_time']['source'],
'required' => false,
'_separate' => true,
'_order' => 5,
'attributes' => [
'data-parent' => '_dates',
'class' => [ 'booking-time-end' ],
],
];
// $form['fields']['_end_time'] = $form['fields']['_time']
return $form;
}
- Add css snippers to the new fields
#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(3){
display: inline-block;
}
#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(4){
float: right;
}
.hp-form__field--select{
display: block;
}
- Add javascript code to handle:
- vendor minimum service time, so if min. serv. time of that listing is 2:30h and the working time is 9:00 - 20:00 on start time you will only be able to select up to 17:30 so you can order 2:30h booking.
- end time will change options based on start value selected, so if user selects start time 16:00 the end time will have only available selects from 18:30.
- edit send booking button, so it will prevent default action and it will manually change the link to the make-booking page with costum start and and and date params
NOTE: here I hardcoded “2:30” min. serv. time (see “getMinimumServiceTime” function), in future i should get this parameter from listing attributes. If you dont need min. serv. time put this “0:00”.
// HANDE BOOKING TIME ORDER HOURS
add_action('wp_footer', 'booking_time_handler');
function booking_time_handler()
{
?>
<script>
requestButtonClick();
// waitForElm("remove_dash_li", "body > span > span > span.select2-results", "");
// waitForElm("remove_dash_span", "#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(3) > span > span.selection > span", "");
var all_li_elements = [];
waitForElm("handle_span_change", getStartSpanOuterSelector());
waitForElm("remove_li_after_maximum", getUlOuterSelector());
function waitForElm(operation, selector, costum_values) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
switch (operation) {
case "remove_dash_li":
// remove_li_after_dash(selector, observer);
break;
case "remove_dash_span":
// remove_span_after_dash(selector, observer, time_remove_executions);
break;
case "handle_span_change":
handle_span_change(observer);
update_multi_time_select(observer);
addBtnClick();
break;
case "remove_li_before_minimum":
remove_li_before_minimum(selector, observer);
break;
case "remove_li_after_maximum":
remove_li_after_maximum(selector, observer);
break;
default:
break;
}
}
});
observer_connect(observer);
});
function observer_connect(observer){
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// function remove_li_after_dash(selector, observer){
// var ul_el = document.querySelector(selector).getElementsByTagName("ul")[0];
// if(!ul_el.getElementsByTagName("li")[0].innerHTML.includes('Searching')
// && !ul_el.classList.contains('already_edited')){
// ul_el.classList.add("already_edited");
// observer.disconnect();
// var li_elements = ul_el.getElementsByTagName("li");
// for (let i = 0; i < li_elements.length; i++) {
// var li_text = li_elements[i].innerHTML;
// li_elements[i].innerHTML = li_text.split("-")[0];
// }
// observer_connect(observer);
// }
// }
// function remove_span_after_dash(selector, observer, time_remove_executions){
// var span_outer_el = document.querySelector(selector);
// var span_inner_el = span_outer_el.getElementsByTagName("span")[0];
// span_inner_el.get
// if(!span_inner_el.innerHTML.includes("—")){
// observer.disconnect();
// span_text = span_inner_el.innerHTML;
// span_inner_el.innerHTML = span_text.split("-")[0];
// time_remove_executions++;
// if(time_remove_executions < 2){
// observer_connect(observer);
// }
// }
// }
function handle_span_change(observer){
// START TIME SPAN
var span_inner_el_start = getStartSpanInner();
// END TIME SPAN
var span_inner_el_end = getEndSpanInner();
if(span_inner_el_start.innerHTML != "—"){
var minimum_end = getMinimumEnd();
update_end_time_if_less_then_min();
waitForElm("remove_li_before_minimum", getUlOuterSelector());
}
else if(span_inner_el_start.innerHTML == "—"
&& span_inner_el_end.innerHTML != "—"){
span_inner_el_start.innerHTML = "—";
span_inner_el_end.innerHTML = "—";
}
}
function remove_li_before_minimum(selector, observer){
var ul_el = getUlInner()
if(!ul_el.getElementsByTagName("li")[0].innerHTML.includes('Searching')){
observer.disconnect();
var li_elements = ul_el.getElementsByTagName("li");
// remove only if is end ul
if(!is_start_ul(li_elements)){
for (let i = 0; i < li_elements.length; i++) {
var li_text = li_elements[i].innerHTML;
end_ul_set_selected();
// when minimum end time is reached, stop removing li elements
if(compareTimes(li_text, ">=", getMinimumEnd())){
break;
}
else{
ul_el.removeChild(li_elements[i]);
i--;
}
// li_elements[i].innerHTML = li_text.split("-")[0];
}
}
observer_connect(observer);
}
}
function remove_li_after_maximum(selector, observer){
var ul_el = getUlInner()
if(!ul_el.getElementsByTagName("li")[0].innerHTML.includes('Searching')){
var li_elements = ul_el.getElementsByTagName("li");
var maximum_end = getMaximumEnd();
// save all li elements with also removed ones (save before removing)
if(all_li_elements.length == 0){
all_li_elements = li_elements;
}
observer.disconnect();
// remove only if is end ul
if(is_start_ul(li_elements)){
for (let i = 0; i < li_elements.length; i++) {
var li_text = li_elements[i].innerHTML;
// when macimum end time is reached, remove all next li elements
if(compareTimes(li_text, ">", maximum_end)){
ul_el.removeChild(li_elements[i]);
i--;
}
}
}
observer_connect(observer);
}
}
function is_start_ul(li_elements){
for (let i = 0; i < li_elements.length; i++) {
// get start time from start span
var start_time = getCurrentStartValue();
// if no time is set, say is start ul also if is not
if(start_time == "—")
return true;
// if the li is equal to the start selected time is the start ul
if(li_elements[i].innerHTML == start_time
&& li_elements[i].getAttribute("aria-selected") == "true"){
return true;
}
}
// otherwise is end ul
return false;
}
function compareTimes(time1, operator, time2){
// Extract hours and minutes from time strings
const [hours1, minutes1] = time1.split(':').map(Number);
const [hours2, minutes2] = time2.split(':').map(Number);
// Calculate total minutes since midnight for each time
const totalMinutes1 = hours1 * 60 + minutes1;
const totalMinutes2 = hours2 * 60 + minutes2;
switch (operator) {
case ">":
return totalMinutes1 > totalMinutes2;
break;
case "<":
return totalMinutes1 < totalMinutes2;
break;
case ">=":
return totalMinutes1 >= totalMinutes2;
break;
case "<=":
return totalMinutes1 <= totalMinutes2;
break;
case "==":
return totalMinutes1 == totalMinutes2;
break;
default:
return false;
break;
}
}
function update_end_time_if_less_then_min(){
var start_span_inner = getStartSpanInner();
var end_span_inner = getEndSpanInner();
var minimumEnd = getMinimumEnd();
if(end_span_inner.innerHTML == "—"
|| compareTimes(end_span_inner.innerHTML, "<", minimumEnd)){
end_span_inner.innerHTML = minimumEnd;
}
}
function end_ul_set_selected(){
var selector = "body > span > span > span.select2-results";
var ul_el = getUlInner();
if(!ul_el.getElementsByTagName("li")[0].innerHTML.includes('Searching')){
var li_elements = ul_el.getElementsByTagName("li");
// remove only if is end ul
if(!is_start_ul(li_elements)){
for (let i = 0; i < li_elements.length; i++) {
var li_text = li_elements[i].innerHTML;
// when minimum end time is reached, stop removing li elements
if(compareTimes(li_text, "==", getCurrentEndValue())){
li_elements[i].setAttribute("aria-selected", "true");
}
else{
li_elements[i].setAttribute("aria-selected", "false");
}
}
}
}
}
function update_multi_time_select(observer){
// if start and end are selected
if(getCurrentStartValue() != "—"
&& getCurrentEndValue() != "—"){
observer.disconnect();
var multi_select_ul = getMultiSelectUl();
// var insert_li_template = '<li class="select2-selection__choice" title="08:00" data-select2-id="145"><span class="select2-selection__choice__remove" role="presentation">×</span>08:00</li>';
// multi_select_ul.click();
var select_text_input = document.querySelector("#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(5) > span > span.selection > span > ul > li > input");
// Create a new keyboard event
var event = new Event("input", {
bubbles: true,
cancelable: true,
});
// Set the value of the input field
select_text_input.value = "a";
// Dispatch the keyboard event
select_text_input.dispatchEvent(event);
// var multi_select_ul_outer = getMultiSelectUlOuter();
// var insert_li_template_outer = '<option value="32400" data-select2-id="52">09:00</option>';
// multi_select_ul.innerHTML = insert_li_template + multi_select_ul.innerHTML;
// multi_select_ul_outer.innerHTML = insert_li_template_outer + multi_select_ul_outer.innerHTML;
var ul_el = getUlInner();
if(ul_el){
var li_elements = ul_el.getElementsByTagName("li");
console.log(" FOUND "+li_elements.length);
performActionWhenArrayHasMoreThanOneElement(li_elements);
}
else{
console.log("NOT FOUND");
}
// observer_connect(observer);
}
function waitForMultipleElements(arr, minimumElements) {
return new Promise(resolve => {
const checkArrayLength = () => {
if (arr.length >= minimumElements) {
resolve();
} else {
setTimeout(checkArrayLength, 100); // Check again after 100 milliseconds
}
};
checkArrayLength(); // Start the checking process
});
}
async function performActionWhenArrayHasMoreThanOneElement(arr) {
await waitForMultipleElements(arr, 2); // Wait for at least 2 elements in the array
// Perform your action here
for (let i = 0; i < li_elements.length; i++) {
console.log(li_elements[i].innerHTML);
if(i == 3){
li_elements[i].click();
console.log("CLICKED");
li_elements[i].style.backgroundColor = "red";
}
}
}
}
function addBtnClick(){
var button =document.querySelector('#content > div > div > div > div > aside > div > div > form > div.hp-form__footer > button');
if(!button.hasClickListener){
button.addEventListener('click', function() {
// Code to be executed when the button is clicked
button.hasClickListener = true;
var form = document.querySelector("#content > div > div > div > div > aside > div > div > form");
form.addEventListener("submit", function(event) {
console.log("SUBMIT");
event.preventDefault(); // Prevent the form from submitting automatically
var actionLink = form.getAttribute("action");
var listing_id =
<?php
echo $GLOBALS["listing_id"];
?>
var start_time =getCurrentStartValue();
var end_time =getCurrentEndValue();
var date = getCurrentDateValue();
var updatedActionLink = actionLink + "?listing="+listing_id+"&start_time="+start_time+"&end_time="+end_time+"&date="+date;
// Manually redirect to the updated URL
window.location.href = updatedActionLink;
});
});
}
}
}
function getCurrentStartValue(){
var start_span_inner = document.querySelector(getStartSpanOuterSelector()).getElementsByTagName("span")[0];
return start_span_inner.innerHTML;
}
function getCurrentEndValue(){
var end_span_inner = getEndSpanInner();
return end_span_inner.innerHTML;
}
function getCurrentDateValue(){
var date_input = document.querySelector("#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div.hp-form__field.hp-form__field--date > div > input.hp-field.hp-field--text.flatpickr-input");
return date_input.value;
}
function getMinimumServiceTime(){
return "2:30"; // -> GET FROM PARAMS WORDPRESS
}
function getUlOuterSelector(){
return "body > span > span > span.select2-results";
}
function getStartSpanOuterSelector(){
return "#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(3) > span > span.selection > span";
}
function getEndSpanOuterSelector(){
return "#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(4) > span > span.selection > span";
}
function getUlInner(){
return document.querySelector(getUlOuterSelector()).getElementsByTagName("ul")[0];
}
function getMultiSelectUl(){
return document.querySelector("#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(5) > span > span.selection > span > ul");
}
function getMultiSelectUlOuter(){
return document.querySelector("#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(5) > select");
}
function getStartSpanInner(){
return document.querySelector(getStartSpanOuterSelector()).getElementsByTagName("span")[0];
}
function getEndSpanInner(){
return document.querySelector(getEndSpanOuterSelector()).getElementsByTagName("span")[0];
}
function getMinimumEnd(){
var start_time = getCurrentStartValue();
var minimum_service_time = getMinimumServiceTime();
// Parse the start_time string to create a Date object
var startTimeParts = start_time.split(":");
var startDate = new Date();
startDate.setHours(parseInt(startTimeParts[0], 10));
startDate.setMinutes(parseInt(startTimeParts[1], 10));
// Parse the minimum_service_time string to create a Date object
var [minHours, minMinutes] = minimum_service_time.split(':');
minHours = parseInt(minHours);
minMinutes = parseInt(minMinutes);
// Add 2 hours and 30 minutes to the start_time
var endTime = new Date(startDate.getTime() + minHours * 60 * 60 * 1000 + minMinutes * 60 * 1000);
// Format the endTime as "HH:mm"
var endHours = endTime.getHours().toString().padStart(2, '0');
var endMinutes = endTime.getMinutes().toString().padStart(2, '0');
var min_end = endHours + ":" + endMinutes;
return min_end;
}
function getMaximumEnd(){
var minimum_service_time = getMinimumServiceTime();
var ul_el = getUlInner();
var li_elements = ul_el.getElementsByTagName("li");
var last_hour = li_elements[li_elements.length - 1].innerHTML;
// Parse the start_time string to create a Date object
var lastTimeParts = last_hour.split(":");
var lastDate = new Date();
lastDate.setHours(parseInt(lastTimeParts[0], 10));
lastDate.setMinutes(parseInt(lastTimeParts[1], 10));
// Parse the last_hour string to create a Date object
var [minHours, minMinutes] = minimum_service_time.split(':');
minHours = parseInt(minHours);
minMinutes = parseInt(minMinutes);
// Add 2 hours and 30 minutes to the last_hour
var endTime = new Date(lastDate.getTime() - (minHours * 60 * 60 * 1000 + minMinutes * 60 * 1000));
// Format the endTime as "HH:mm"
var endHours = endTime.getHours().toString().padStart(2, '0');
var endMinutes = endTime.getMinutes().toString().padStart(2, '0');
var max_end = endHours + ":" + endMinutes;
return max_end;
}
function requestButtonClick(){
}
</script>
<?php
}
- Save listing id in $GLOBALS php variable to use it after to redirect to the correct listing page (see point 5 third “-”)
// ADD LISTING ID ON GLOBALS
add_filter(
'hivepress/v1/templates/listing_view_page/blocks',
function ($blocks, $template) {
$listing = $template->get_context('listing');
if (!$listing) {
return $blocks;
}
$listing_id = $listing->get_id();
// set listing id in globals
$GLOBALS["listing_id"] = "$listing_id";
return $blocks;
},
1000,
2
);
- Change the start and end time in the database after the booking is created with the “wrong” (default) time. The $_GET parameters were passed in the link url parameters before.
// SET COSTUM DATE AND TIME
add_action('hivepress/v1/models/booking/update', function($booking_id ) {
if(hivepress()->get_version( 'bookings' )){
$start_time = $_GET['start_time'];
$end_time = $_GET['end_time'];
$date = $_GET['date'];
update_post_meta( $booking_id, 'hp_start_time', strtotime("$date $start_time"));
update_post_meta( $booking_id, 'hp_end_time', strtotime("$date $end_time"));
}
});
- Set date on first place (order) of the form and unset the hivepress time select
add_filter(
'hivepress/v1/forms/booking_make',
function( $form ) {
$form['fields']['_dates']['_order'] = 1;
unset($form['fields']['_time']);
return $form;
},
1000,
2
);
- Add CSS Style snippet to hive “(optional)” in end time (the end time will be automaticly set so there is no way it will be empty)
#content > div > div > div > div > aside > div > div > form > div.hp-form__fields > div:nth-child(4) > label > small{
display: none;
}
- To remove the second half of booking time listing slots (after the dash (“-”)), thas is essential for my script, check this topic and follow my technique (not raccomended) or try to make what yevhen explained in the post.
That’s it!
NOTE 1: that as I said before there is only hardcoded minimum service time
NOTE 2: the price calculation is still work in progress so for now it stays as default, if you need i will send you also this when i will finish.
NOTE 3: Im not sure I used the raccomended procedures everywere, I think I made too much hardcoding. If you have some suggestions to make the code better tell me please.
Hope I will help you!