V5 is live

UP TO

60% OFF
Hot December Sale
Launch Sale
0 DAYS
:
0 HOURS
:
0 MINUTES
:
0 SECONDS
GET NOW percent icon

Front-end Guide

Extending Booknetic’s booking and payment flows using JavaScript hooks, the ‎object API, and addon architecture.

Version:
Categories

Booknetic’s frontend layer gives developers a flexible way to extend the booking experience, integrate custom UI, modify request data, and build advanced addons without changing the core plugin directly.

This guide covers the full frontend extension layer, including the booking panel your customers use, the admin panel JavaScript structure, the client-side hook system, frontend architecture, payment flow integration, styling rules, and file structure references.

Technology Stack

Booknetic’s frontend is built with a lightweight and familiar stack:

TechnologyUsage
jQuery 3.3.1+Core JavaScript framework
Vanilla JavaScript (ES6)Supplementary logic
Bootstrap 4.xCSS framework
Select2Enhanced dropdowns
FlatpickrDate picker
intlTelInputInternational phone input
NiceScrollCustom scrollbar

Booknetic does not use a build tool. There is no webpack, no gulp, and no compilation step. JavaScript and CSS files are loaded directly as they are, which means you can add or edit files without any asset build process.

JavaScript Hook System

Booknetic includes a client-side hook system inspired by WordPress hooks. This is the main extension mechanism for frontend addons and custom booking panel behavior.

The bookneticHooks Object

The global window.bookneticHooks object provides four core methods:

// Register a filter (modifies and returns a value)
bookneticHooks.addFilter(key, callback, uniqueId);

// Execute a filter (passes value through all registered callbacks)
let result = bookneticHooks.doFilter(key, initialValue, ...extraArgs);

// Register an action (side-effect, no return value needed)
bookneticHooks.addAction(key, callback, uniqueId);

// Execute an action
bookneticHooks.doAction(key, ...params);

Actions and filters have different purposes:

  • Actions run your callback for side effects, such as updating the DOM, attaching events, or triggering AJAX requests. Their return value is ignored.
  • Filters pass a value through your callback. You must return the modified value, otherwise the next part of the code will receive undefined.

The optional uniqueId argument makes it possible to later replace or remove a specific hook by calling addFilter(key, null, uniqueId).

Hook Key Casing

All hook keys are automatically converted to lowercase internally. That means booking_panel_loaded, Booking_Panel_Loaded, and BOOKING_PANEL_LOADED all point to the same hook.

For consistency, use snake_case naming.

Actions Reference

Actions let you react to important lifecycle events inside the booking panel, AJAX system, payment flow, and customer panel.

Booking Panel Lifecycle

booking_panel_loaded

Fired once after the booking panel is fully initialized. This is the main entry point for your addon JavaScript. Use it to bind event listeners and prepare custom UI.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
bookneticHooks.addAction('booking_panel_loaded', function (booknetic) {
    let panel = booknetic.panel_js; // jQuery element for this panel

    // Bind event listeners using delegation
    panel.on('click', '.my_addon_button', function () {
        // Handle click
    });
});

A single page can contain multiple booking panels. Each panel fires its own booking_panel_loaded event and has its own booknetic instance. Always use booknetic.panel_js for DOM queries instead of global selectors.

before_step_loading

Fired just before a step transition starts, before the AJAX request is sent.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
next_stepstringThe step ID about to be loaded
current_stepstringThe step ID being left
bookneticHooks.addAction('before_step_loading', function (booknetic, nextStep, currentStep) {
    console.log(`Navigating from ${currentStep} to ${nextStep}`);
});

start_step_loading

Fired when the AJAX request for the next step starts.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
next_stepstringThe step being loaded
current_stepstringThe previous step

loaded_step

Fired after a step’s content is loaded and rendered. Use this to enhance or extend the UI of a specific step.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
step_idstringThe ID of the loaded step
current_stepstringThe previous step ID
resultobjectThe AJAX response data
bookneticHooks.addAction('loaded_step', function (booknetic, stepId, currentStep, result) {
    if (stepId !== 'confirm_details') return;

    // Inject a coupon input into the confirmation step
    let html = '<div class="my_coupon_section">'
        + '<input type="text" placeholder="' + booknetic.__('enter_coupon') + '">'
        + '<button class="my_apply_btn">' + booknetic.__('apply') + '</button>'
        + '</div>';

    booknetic.panel_js.find('.booknetic_confirm_details').append(html);
});

loaded_step_{step_id}

Dynamic version of loaded_step. This hook only fires for the specified step, so you do not need a manual if check.

Available step IDs:

location, staff, service, service_extras, date_time, recurring_info, information, cart, confirm_details

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
resultobjectThe AJAX response data
bookneticHooks.addAction('loaded_step_date_time', function (booknetic, result) {
    // Runs only when the Date & Time step is loaded
    // Customize calendar behavior here
});

loaded_cached_step

Fired when a previously visited step is loaded from cache.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
step_idstringThe step loaded from cache

step_end_{step_id}

Fired after a step passes validation and the user is about to move forward. This is useful when you want to react to a completed step before the next one loads.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
bookneticHooks.addAction('step_end_service', function (booknetic) {
    // The user has selected a service and is moving forward
    let serviceId = booknetic.getSelected.service();
    console.log('Selected service:', serviceId);
});

step_loaded_with_error

Fired when a step fails to load because the server returned an error.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
next_stepstringThe step that failed to load
current_stepstringThe step being returned to
resultobjectThe error response

AJAX Lifecycle

ajax_before_{action}

Fired before an AJAX request is sent. {action} is the AJAX action name, such as get_data, confirm, or check_customer_exist.

Parameters:

ParameterTypeDescription
paramsobjectThe request parameters
bookneticobjectThe booking panel instance
bookneticHooks.addAction('ajax_before_confirm', function (params, booknetic) {
    // Add a loading indicator, disable buttons, etc.
    console.log('About to confirm appointment...');
});

ajax_after_{action}_success

Fired after a successful AJAX response.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
paramsobjectThe original request parameters
resultobjectThe server response
bookneticHooks.addAction('ajax_after_get_data_success', function (booknetic, params, result) {
    // Step data loaded successfully
});

ajax_after_{action}_error

Fired after a failed AJAX response.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
paramsobjectThe original request parameters
resultobjectThe error response, which may be undefined on network errors

Payment Lifecycle

before_processing_payment

Fired before the payment process starts. Payment gateway addons use this hook to initialize their flow, such as opening a popup window.

Parameters:

ParameterTypeDescription
payment_methodstringThe selected payment method, for example 'local', 'stripe', or 'paypal'
dataobjectPayment data returned from the server, including tokens, URLs, and related values
bookneticHooks.addAction('before_processing_payment', function (paymentMethod, data) {
    if (paymentMethod !== 'my_gateway') return;

    // Open a popup window for the payment gateway
    myPaymentWindow = window.open('', 'booknetic_payment_window', 'width=1000,height=700');
});

after_processing_payment

Fired after the payment process has been initiated. This is typically used to redirect the popup to the payment gateway page.

Parameters:

ParameterTypeDescription
payment_methodstringThe payment method
statusbooleanWhether payment initiation succeeded
dataobjectPayment response data
bookneticHooks.addAction('after_processing_payment', function (paymentMethod, status, data) {
    if (paymentMethod !== 'my_gateway') return;

    if (!status) {
        myPaymentWindow.close();
        return;
    }

    // Redirect the popup to the gateway's checkout page
    myPaymentWindow.location.href = data['url'];
});

payment_completed

Fired when the payment process finishes successfully.

Parameters:

ParameterTypeDescription
statusbooleanPayment success status
paymentDataobjectPayment result data

payment_completed_deprecated

Legacy hook kept for backward compatibility. Use payment_completed instead.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance

payment_error

Fired when a payment attempt fails.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance

booking_finished_successfully

Fired when the full booking flow is completed successfully and the finish screen is shown.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance
bookneticHooks.addAction('booking_finished_successfully', function (booknetic) {
    // Trigger conversion tracking, analytics event, etc.
    if (typeof gtag !== 'undefined') {
        gtag('event', 'booking_completed', {
            appointment_id: booknetic.appointmentId
        });
    }
});

Customer Panel

customer_panel_loaded

Fired when the customer panel is loaded. This is the interface where existing customers manage their bookings.

Parameters: None

bookneticHooks.addAction('customer_panel_loaded', function () {
    // Add a custom tab to the customer panel
    let tabHtml = '<li class="my_addon_tab" data-tab="my_tab">'
        + '<span>My Custom Tab</span></li>';
    $('.booknetic_cp_tabs').append(tabHtml);

    $(document).on('click', '.my_addon_tab', function () {
        booknetic.ajax('my_addon_get_tab_content', {}, function (result) {
            $('.booknetic_cp_content').html(result.html);
        });
    });
});

Filters Reference

Filters allow you to modify outgoing request data, step validation results, cart items, and rendered frontend HTML before Booknetic uses them.

AJAX Filters

ajax

Filters all outgoing AJAX request parameters. This hook runs for every AJAX request made by the booking panel.

Parameters:

ParameterTypeDescription
paramsobjectThe request parameters
bookneticobjectThe booking panel instance

Returns: object — The modified parameters

bookneticHooks.addFilter('ajax', function (params, booknetic) {
    // Add a custom parameter to every AJAX request
    params.my_tracking_id = 'abc123';
    return params;
});

ajax_{action}

Filters parameters for one specific AJAX action only. For example, ajax_confirm affects only the confirmation request.

Parameters:

ParameterTypeDescription
paramsobjectThe request parameters
bookneticobjectThe booking panel instance

Returns: object

bookneticHooks.addFilter('ajax_confirm', function (params, booknetic) {
    // Add extra data only to the confirmation request
    params.special_note = document.getElementById('my_note_field').value;
    return params;
});

appointment_ajax_data

Filters the final FormData sent when the appointment is confirmed. This is the last point where you can change submission data before it reaches the backend.

Parameters:

ParameterTypeDescription
dataFormDataThe appointment submission data
bookneticobjectThe booking panel instance

Returns: FormData

bookneticHooks.addFilter('appointment_ajax_data', function (formData, booknetic) {
    // Append custom form field data
    formData.append('coupon_code', document.getElementById('my_coupon_input').value);
    formData.append('gift_card_code', document.getElementById('my_gift_card_input').value);
    return formData;
});

Step Filters

step_validation_{step_id}

Validates a step before the user can continue. Return { status: false, errorMsg: '...' } to block navigation.

Available step IDs:

location, staff, service, service_extras, date_time, recurring_info, information, cart, confirm_details

Parameters:

ParameterTypeDescription
resultobject{ status: true, errorMsg: '' }
bookneticobjectThe booking panel instance

Returns: object{ status: boolean, errorMsg: string }

bookneticHooks.addFilter('step_validation_information', function (result, booknetic) {
    let customField = booknetic.panel_js.find('#my_required_field').val();
    if (!customField) {
        return {
            status: false,
            errorMsg: booknetic.__('my_field_required')
        };
    }
    return result;
});

load_step_{step_id}

Controls whether a step should load. Return false to prevent the step from loading. This is useful when you need to show a modal or collect extra input first.

Parameters:

ParameterTypeDescription
bookneticobjectThe booking panel instance

Returns: boolean

bookneticHooks.addFilter('load_step_date_time', function (booknetic) {
    // Show a custom duration picker modal before proceeding to date/time
    if (!durationSelected) {
        showDurationModal(booknetic);
        return false; // Block step loading
    }
    return true; // Allow step loading (default)
});

Cart Filter

bkntc_cart

Filters the cart item data before it is stored in the cart array.

Parameters:

ParameterTypeDescription
cart_itemobjectThe cart item data object
bookneticobjectThe booking panel instance

Returns: object

bookneticHooks.addFilter('bkntc_cart', function (cartItem, booknetic) {
    // Add custom data to the cart item
    cartItem.my_addon_data = {
        coupon_code: mySelectedCoupon,
        discount: myCalculatedDiscount
    };
    return cartItem;
});

Time Slot Filter

bkntc_date_time_load

Filters the HTML of each time slot before it is rendered in the Date & Time step.

Parameters:

ParameterTypeDescription
htmlstringThe time slot HTML element
time_objobjectThe time slot data, such as start_time, end_time, and other slot-related fields
bookneticobjectThe booking panel instance

Returns: string

bookneticHooks.addFilter('bkntc_date_time_load', function (html, timeObj, booknetic) {
    // Add a "Popular" badge to slots with high demand
    if (timeObj.booking_count > 5) {
        html = html.replace('</div>', '<span class="popular-badge">Popular</span></div>');
    }
    return html;
});

The booknetic Object

Each booking panel instance creates its own booknetic object. This acts as the panel’s main API and state container.

Properties

PropertyTypeDescription
panel_jsjQueryjQuery reference to the root element of this panel
cartArrarrayCart items array
cartCurrentIndexnumberIndex of the cart item currently being edited
calendarDateTimesobjectAvailable dates and time slots
calendarYearnumberCurrently displayed calendar year
calendarMonthnumberCurrently displayed calendar month
paymentWindowWindowReference to the payment popup window
paymentStatusbooleanPayment success or failure state
appointmentIdnumberCreated appointment ID after confirmation
paymentIdnumberPayment record ID

Methods

booknetic.ajax(action, params, successCallback, loading, errorCallback, async)

Sends an AJAX request to the Booknetic backend.

ParameterTypeDefaultDescription
actionstring|objectAction name. If it is a string, Booknetic prefixes it with bkntc_. If it is an object, it should be in {backend_action, frontend_action} format
paramsobject|FormDataRequest parameters
successCallbackfunctionCallback triggered on success with (result)
loadingbooleantrueWhether to show the loading spinner
errorCallbackfunctionCallback triggered on error with (result)
asyncbooleantrueWhether the request is asynchronous
booknetic.ajax('my_addon_action', {
    param1: 'value1',
    param2: 'value2'
}, function (result) {
    // Success
    console.log(result.data);
}, true, function (error) {
    // Error
    booknetic.toast(error.error_msg, 'error');
});

The action name is automatically prefixed with bkntc_. For example, 'my_addon_action' becomes bkntc_my_addon_action on the server.

booknetic.ajaxAsync(action, params, loading, async)

Promise-based version of ajax(). It resolves with the result or rejects with the error.

try {
    let result = await booknetic.ajaxAsync('my_action', { id: 123 }, true);
    console.log(result.data);
} catch (error) {
    booknetic.toast(error.error_msg, 'error');
}

booknetic.toast(message, type, duration)

Displays a toast notification.

ParameterTypeDescription
messagestringMessage text
typestring'success', 'error', or 'warning'
durationnumberAuto-dismiss time in milliseconds

booknetic.__(key)

Returns a localized string by key.

booknetic.__('select_service');  // → "Select a service" (or translated equivalent)

booknetic.getSelected

An object containing getter methods for the current booking selections:

booknetic.getSelected.location();           // Selected location ID
booknetic.getSelected.service();            // Selected service ID
booknetic.getSelected.staff();              // Selected staff ID
booknetic.getSelected.serviceExtras();      // [{extra, quantity}, ...]
booknetic.getSelected.date();               // 'YYYY-MM-DD'
booknetic.getSelected.time();               // 'HH:MM'
booknetic.getSelected.formData();           // {email, name, surname, phone}
booknetic.getSelected.customerId();         // Customer ID (if existing customer)
booknetic.getSelected.paymentMethod();      // Payment method key
booknetic.getSelected.paymentDepositFullAmount(); // 'deposit' or 'full_amount'

// Recurring appointments
booknetic.getSelected.recurringStartDate();
booknetic.getSelected.recurringEndDate();
booknetic.getSelected.recurringTimesArr();

booknetic.stepManager

The step navigation controller:

booknetic.stepManager.goForward();          // Navigate to the next step
booknetic.stepManager.goBack();             // Navigate to the previous step
booknetic.stepManager.loadStep(stepId);     // Load a specific step
booknetic.stepManager.getNextStep();        // Get the next step ID
booknetic.stepManager.getPrevStep();        // Get the previous step ID
booknetic.stepManager.saveData();           // Save current selections to cart

Utility Methods

booknetic.throttle(fn, delay);              // Returns throttled function (default 500ms)
booknetic.debounce(fn, delay);              // Returns debounced function (default 300ms)
booknetic.htmlspecialchars(string);         // HTML-encode a string
booknetic.htmlspecialchars_decode(string);  // HTML-decode a string
booknetic.sanitizeHTML(node);               // Remove <script> tags from a DOM node
booknetic.isMobileView();                   // true if viewport ≤ 1000px
booknetic.zeroPad(number, length);          // Zero-pad a number (e.g. zeroPad(42, 4) → "0042")
booknetic.loading(state);                   // Show (1) or hide (0) the loading spinner

Booking Panel Architecture

Understanding the booking panel architecture makes addon development much easier, especially when you need to hook into steps, cache behavior, or AJAX flow.

Booking Flow

The booking panel follows a step-based wizard pattern:

Location → Staff → Service → Service Extras → Date/Time → [Recurring Info] → Information → Cart → Confirmation → Finish

Steps can be hidden or reordered from the admin settings. Navigation, caching, and validation are handled automatically by the step manager.

Initialization

When a page containing the [booknetic] shortcode loads, the following happens:

  1. The PHP view renders the HTML skeleton of the booking panel.
  2. An inline script calls bookneticInitBookingPage() for each panel on the page.
  3. The first step is loaded through AJAX using bkntc_get_data.
  4. The booking_panel_loaded action is fired.
// Simplified initialization flow
jQuery(".booknetic_appointment").each((i, element) => {
    window.bookneticInitBookingPage(element);
});

HTML Structure

<div class="booknetic_appointment" id="booknetic_theme_{themeId}">
    <div class="booknetic_appointment_steps">
        <!-- Step sidebar / progress indicator -->
    </div>
    <div class="booknetic_appointment_container">
        <div class="booknetic_appointment_container_header">
            <!-- Current step title + cart icon -->
        </div>
        <div class="booknetic_appointment_container_body">
            <!-- Dynamic step content (loaded via AJAX) -->
        </div>
        <div class="booknetic_appointment_container_footer">
            <!-- Back, Next, Confirm buttons -->
        </div>
    </div>
    <div class="booknetic_appointment_finished">
        <!-- Success / error screen -->
    </div>
</div>

Step Content Loading

Each step is loaded through AJAX via the bkntc_get_data endpoint. The backend returns HTML, which is inserted into .booknetic_appointment_container_body.

To improve performance, step content is cached in booknetic.cartHTMLBody, so going back to a previous step does not trigger another request.

AJAX Request Flow

Every AJAX request follows this pipeline:

1. Action is prefixed: 'bkntc_' + action
2. tenant_id is appended
3. Hook: bookneticHooks.doAction('ajax_before_' + action, params, booknetic)
4. Hook: params = bookneticHooks.doFilter('ajax', params, booknetic)
5. Hook: params = bookneticHooks.doFilter('ajax_' + action, params, booknetic)
6. $.ajax() POST → admin-ajax.php
7. On success: callback(result) + doAction('ajax_after_{action}_success', ...)
8. On error:   callback(result) + doAction('ajax_after_{action}_error', ...)

AJAX Response Format

All Booknetic AJAX responses use the following structure:

{
    status: 'ok' | 'error',
    error_msg: 'Error message',         // Present on errors
    errors: [{ field, message }],       // Field-level validation errors
    html: '<div>...</div>',             // Step HTML content
    data: { ... }                       // Additional data
}

Payment Gateway Integration

A payment gateway addon must implement three key hooks to integrate smoothly into the booking flow.

Complete Gateway Pattern

(function () {
    let paymentWindow;
    let paymentData;

    // Step 1: Open the payment window before processing starts
    bookneticHooks.addAction('before_processing_payment', function (paymentMethod, data) {
        if (paymentMethod !== 'my_gateway') return;

        paymentData = data;
        paymentWindow = window.open('', 'booknetic_payment_window', 'width=1000,height=700');
    });

    // Step 2: Redirect the popup to the gateway after the server creates the payment
    bookneticHooks.addAction('after_processing_payment', function (paymentMethod, status, data) {
        if (paymentMethod !== 'my_gateway') return;

        if (!status) {
            paymentWindow.close();
            return;
        }

        // Redirect to the gateway's checkout page
        paymentWindow.location.href = data['url'];
    });
})();

On the gateway callback page, after the customer completes payment, the popup should call one of the following:

// In the callback view loaded inside the popup:
window.opener.bookneticPaymentStatus(true);   // Payment successful
// or
window.opener.bookneticPaymentStatus(false);  // Payment failed

The main booking panel watches the popup. When it closes, it checks the payment result and then displays either the success screen or an error state.

Payment Flow Summary

1. User clicks "Confirm"
2. confirmAppointment() → AJAX: bkntc_confirm
3. Server creates appointment, returns payment URL
4. doAction('before_processing_payment') → Gateway opens popup
5. doAction('after_processing_payment') → Popup redirected to gateway URL
6. Customer completes payment in popup
7. Gateway callback calls window.opener.bookneticPaymentStatus(true)
8. Popup closes, main window detects it
9. doAction('payment_completed')
10. doAction('booking_finished_successfully') → Success screen shown

Admin Panel JavaScript

Booknetic’s admin side has its own JavaScript architecture and its own booknetic object.

Core Admin Object

The main admin object is defined in app/Backend/Base/assets/js/booknetic.js:

var booknetic = {
    // Localization
    __(key) — Get a translated string

    // UI
    loadModal(modal, parameters, options) — Load a modal via AJAX
    modalHide($modal) — Close a modal
    toast(title, type, duration) — Show a toast notification
    confirm(text, bg, icon, okCallback, okText, cancelText) — Show confirmation dialog

    // AJAX
    ajax(action, params, callback) — Make an AJAX request

    // DataTable
    dataTable.init(container) — Initialize a data table
    dataTable.reload() — Reload table data
    dataTable.doAction(action, ids, params, callback) — Execute a table action
};

Module JavaScript Pattern

Each admin module usually has its own JavaScript file and follows a consistent interaction pattern:

$(document).ready(function () {
    // Add button
    $(document).on('click', '#addBtn', function () {
        booknetic.loadModal('add_new', {});
    });

    // DataTable edit action
    booknetic.dataTable.actionCallbacks['edit'] = function (ids) {
        booknetic.loadModal('add_new', { id: ids[0] });
    };

    // DataTable delete action
    booknetic.dataTable.actionCallbacks['delete'] = function (ids) {
        booknetic.confirm(
            booknetic.__('are_you_sure_want_to_delete'),
            'danger', 'trash',
            function () {
                booknetic.dataTable.doAction('delete', ids, {}, function () {
                    booknetic.toast(booknetic.__('deleted'), 'success', 2000);
                });
            }
        );
    };
});

Modal Form Submit Pattern

$(document).on('click', '#saveBtnModal', function () {
    let name = $('#input_name').val();

    if (!name) {
        booknetic.toast(booknetic.__('name_required'), 'error');
        return;
    }

    booknetic.ajax('save', {
        id: $('#input_id').val(),
        name: name
    }, function (result) {
        booknetic.toast(booknetic.__('saved'), 'success', 2000);
        booknetic.modalHide($('.modal'));
        booknetic.dataTable.reload();
    });
});

Addon Development

A frontend addon usually needs to load assets, register localized strings, hook into lifecycle events, inject UI, send custom data, and sometimes validate steps.

Loading Frontend Assets

Register your addon scripts and styles through PHP hooks:

// In your AddonLoader's initFrontend() method:
public function initFrontend()
{
    // Load assets on the booking panel
    add_filter('bkntc_booking_panel_assets', function ($assets) {
        $assets[] = ['type' => 'js',  'url' => self::loadAsset('assets/js/init.js')];
        $assets[] = ['type' => 'css', 'url' => self::loadAsset('assets/css/style.css')];
        return $assets;
    });
}

// In your AddonLoader's initBackend() method:
public function initBackend()
{
    // Load assets on admin pages
    add_action('bkntc_enqueue_assets', function ($module, $action, $viewPath) {
        if ($module === 'appointments') {
            wp_enqueue_script('my-addon-admin', self::loadAsset('assets/js/admin.js'), ['jquery']);
        }
    }, 10, 3);
}

Adding Localized Strings

You can pass translatable strings from PHP into JavaScript:

// PHP: Register strings
add_filter('bkntc_frontend_localization', function ($strings) {
    $strings['my_addon_label'] = bkntc__('My Label');
    $strings['my_addon_error'] = bkntc__('Something went wrong');
    return $strings;
});
// JavaScript: Use the strings
booknetic.__('my_addon_label');  // → "My Label"
booknetic.__('my_addon_error');  // → "Something went wrong"

Complete Addon Frontend Pattern

A typical addon init.js can follow this structure:

// Panel loaded — bind events
bookneticHooks.addAction('booking_panel_loaded', function (booknetic) {
    let panel = booknetic.panel_js;

    // Bind DOM events using delegation
    panel.on('click', '.my_addon_apply_btn', function () {
        let code = panel.find('#my_addon_input').val();

        booknetic.ajax('my_addon_validate', { code: code }, function (result) {
            // Update UI with result
            panel.find('.my_addon_status').text(result.data.message);
        }, true, function (error) {
            booknetic.toast(error.error_msg, 'error');
        });
    });
});

// Inject UI into a specific step
bookneticHooks.addAction('loaded_step_confirm_details', function (booknetic, result) {
    let html = '<div class="my_addon_section">'
        + '<input type="text" id="my_addon_input" placeholder="' + booknetic.__('my_placeholder') + '">'
        + '<button class="my_addon_apply_btn">' + booknetic.__('apply') + '</button>'
        + '</div>';

    booknetic.panel_js.find('.booknetic_confirm_details').append(html);
});

// Append custom data to the appointment submission
bookneticHooks.addFilter('appointment_ajax_data', function (formData, booknetic) {
    let myValue = booknetic.panel_js.find('#my_addon_input').val();
    if (myValue) {
        formData.append('my_addon_code', myValue);
    }
    return formData;
});

// Custom step validation
bookneticHooks.addFilter('step_validation_information', function (result, booknetic) {
    let field = booknetic.panel_js.find('#my_required_field').val();
    if (!field) {
        return { status: false, errorMsg: booknetic.__('field_required') };
    }
    return result;
});

CSS Guidelines

Booknetic frontend styles are scoped to reduce conflicts with WordPress themes and third-party styles.

Isolation

Booking panel styles are isolated under .booknetic_appointment:

.booknetic_appointment * {
    all: unset;
    /* Properties are then explicitly re-set */
}

When writing custom styles, always keep them inside the booking panel scope:

.booknetic_appointment .my_addon_section {
    padding: 15px;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    margin-top: 10px;
}

CSS Class Naming

PrefixScopeExample
booknetic_Core booking panel elements.booknetic_service_card
bkntc_Internal shorthand classes.bkntc_service_list
bkntcsaas_SaaS module elements.bkntcsaas_signup_btn

State modifiers use the _{state} suffix, such as .booknetic_card_selected and .booknetic_category_accordion_hidden.

Key CSS Classes

ClassDescription
.booknetic_appointmentRoot container of the full booking panel
.booknetic_appointment_stepsLeft sidebar with step indicators
.booknetic_appointment_containerMain content wrapper
.booknetic_appointment_container_bodyDynamic step content area
.booknetic_cardSelectable card element for location, staff, and similar items
.booknetic_card_selectedCurrently selected card
.booknetic_service_cardService selection card
.booknetic_service_card_selectedCurrently selected service
.booknetic_calendar_rowsCalendar day grid
.booknetic_calendar_daysSingle calendar day
.booknetic_time_elementTime slot element
.booknetic_selected_timeCurrently selected time slot
.booknetic_appointment_finishedSuccess or completion screen

Responsive Behavior

The booking panel switches to mobile layout at 1000px viewport width.

booknetic.isMobileView(); // returns true when viewport ≤ 1000px

In mobile view:

  • The step sidebar is hidden
  • The content area becomes full width
  • Scroll behavior changes

RTL Support

RTL is detected automatically:

if ($('html').attr('dir') === 'rtl') {
    $('body').addClass('rtl');
}

File Structure Reference

app/Frontend/assets/
├── js/
│   ├── booknetic.js              # Core booking panel logic (~2400 lines)
│   ├── booknetic-popup.js        # Modal/popup handler
│   ├── booknetic-signin.js       # Customer sign-in
│   ├── booknetic-signup.js       # Customer sign-up
│   ├── booknetic-forgot-password.js
│   └── steps/                    # Step-specific JS files
│       ├── step_locations.js
│       ├── step_services.js
│       ├── step_staff.js
│       ├── step_service_extras.js
│       ├── step_date_time.js
│       ├── step_information.js
│       ├── step_cart.js
│       ├── step_confirm_details.js
│       └── step_recurring_info.js
├── css/
│   ├── booknetic.css             # Main booking panel styles
│   └── bootstrap-booknetic.css   # Bootstrap customization
└── icons/                        # SVG icons (40+)

app/Frontend/view/booking_panel/  # Step PHP templates
├── locations.php
├── services.php
├── staff.php
├── service_extras.php
├── date_time.php
├── information.php
├── confirm.php
├── cart.php
└── booknetic.php                 # Main panel template

app/Backend/Base/assets/js/
├── booknetic.js                  # Admin panel core JS
└── generic-table.js              # GenericTable class

app/Backend/[Module]/assets/      # Module-specific assets
├── js/
└── css/

Summary

Booknetic’s frontend developer layer gives you a practical extension system for the booking panel, customer panel, admin JavaScript, payment gateways, and UI rendering behavior.

Hook TypeCountPurpose
JS Actions18+React to lifecycle events such as step loading, payment completion, and booking success
JS Filters7+Modify request data, validation results, cart items, and rendered frontend output

Quick Reference: Most Used Hooks

HookTypeCommon Use Case
booking_panel_loadedActionInitialize addon and bind event listeners
loaded_step / loaded_step_{id}ActionInject custom UI into a specific step
step_end_{step_id}ActionReact when a step is completed
before_processing_paymentActionOpen a payment gateway popup
after_processing_paymentActionRedirect the popup to the gateway checkout page
booking_finished_successfullyActionConversion tracking and analytics
step_validation_{step_id}FilterAdd custom step validation
appointment_ajax_dataFilterAppend custom data to the booking submission
ajax / ajax_{action}FilterModify AJAX request parameters
bkntc_date_time_loadFilterCustomize time slot rendering
load_step_{step_id}FilterConditionally block step loading
bkntc_cartFilterModify cart item data