Nepali Date Integration

Complete documentation for the two-way AD/BS synchronization engine in PHPRunner 11.2.

1 Global Setup

Before the JavaScript functions can work, Sajan Maharjan's library must be loaded. Loading it globally ensures the calendar works across your entire application without redundant code.

Method A: The Visual Editor Header (Recommended)

The cleanest way to apply this project-wide is by adding the HTML assets directly to PHPRunner's shared Header snippet.

  1. Open PHPRunner and go to the Visual Editor (Page Designer).
  2. Locate and click on the Header snippet (usually at the top of the page layout).
  3. Paste the following standard HTML into the editor:
<link href="https://cdn.jsdelivr.net/npm/@sajanm/nepali-date-picker@5.0.6/dist/nepali.datepicker.v5.0.6.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/@sajanm/nepali-date-picker@5.0.6/dist/nepali.datepicker.v5.0.6.min.js"></script>
Method B: The Global Event (Alternative PHP approach)

If you prefer keeping everything in the Events screen, navigate to Events > Global > Before display and add:

$pageObject->AddCSSFile("https://cdn.jsdelivr.net/npm/@sajanm/nepali-date-picker@5.0.6/dist/nepali.datepicker.v5.0.6.min.css");
$pageObject->AddJSFile("https://cdn.jsdelivr.net/npm/@sajanm/nepali-date-picker@5.0.6/dist/nepali.datepicker.v5.0.6.min.js");
Final Configuration Step
CRITICAL: In PHPRunner's visual designer, ensure both your English Date (`date_ad`) and Nepali Date (`date_bs`) fields are set to "Text Field" under the "Edit as" properties. Do not use the "Date" type, or PHPRunner will reject the formatted strings.
2 Core JavaScript Integration

To make the utility available globally across your application, copy the following engine code and paste it into your custom_functions.js file (or your project's global JavaScript file).

/**
 * Smart Nepali Date Picker Integration for PHPRunner
 * Features:
 * - Scoped securely via PHPRunner's pageid
 * - Auto AD-to-BS and BS-to-AD synchronization
 * - Safely handles PHPRunner's internal state via .setValue()
 * * @param {number} pageid - The current PHPRunner page ID
 * @param {string} bsFieldName - Name of the Bikram Sambat field
 * @param {string} adFieldName - (Optional) Name of the AD field to sync with
 * @param {Object} options - (Optional) Sajan's v5 configuration object
 */
function initNepaliDatePicker(pageid, bsFieldName, adFieldName, options) {
    console.log("Nepali Date Picker: Initializing for page " + pageid);

    // Wait for the library to exist
    var checkExist = setInterval(function() {
        var bsControl = Runner.getControl(pageid, bsFieldName);
        if (!bsControl) return;

        var bsInput = $("#value_" + bsFieldName + "_" + bsControl.id)[0];
        if (!bsInput) return;

        if (typeof bsInput.nepaliDatePicker === 'function' && typeof NepaliFunctions !== 'undefined') {
            clearInterval(checkExist); 
            console.log("Nepali Date Picker: Loaded! Attaching to -> " + bsFieldName);
            attachCalendar(bsControl, bsInput, adFieldName, options);
        }
    }, 100);

    setTimeout(function() { clearInterval(checkExist); }, 3000);

    // --- The Core Engine ---
    function attachCalendar(bsControl, bsInput, adFieldName, options) {
        var adControl = adFieldName ? Runner.getControl(pageid, adFieldName) : null;
        var adInput = adControl ? $("#value_" + adFieldName + "_" + adControl.id)[0] : null;

        // ANTI-INFINITE LOOP FLAG
        var isSyncing = false; 

        function formatDate(dateObj) {
            var m = dateObj.month < 10 ? "0" + dateObj.month : dateObj.month;
            var d = dateObj.day < 10 ? "0" + dateObj.day : dateObj.day;
            return dateObj.year + "-" + m + "-" + d;
        }

        // Centralized Synchronization Logic
        function doSync(source, value) {
            if (isSyncing) return; 
            
            if (!value) {
                isSyncing = true;
                if (source === "BS" && adControl) { 
                    adControl.setValue(""); 
                    adInput.value = ""; 
                }
                if (source === "AD") { 
                    bsControl.setValue(""); 
                    bsInput.value = ""; 
                }
                isSyncing = false;
                return;
            }

            try {
                if (source === "BS" && adControl) {
                    var cleanVal = value.replace(/\//g, "-").replace(/[^\d-]/g, "");
                    var parts = cleanVal.split("-");
                    if (parts.length !== 3) return;
                    
                    var dateObj = { year: Number(parts[0]), month: Number(parts[1]), day: Number(parts[2]) };
                    var adObj = NepaliFunctions.BS2AD(dateObj);
                    var adString = formatDate(adObj);
                    
                    isSyncing = true; 
                    
                    // 1. Update PHPRunner API & Visual DOM
                    adControl.setValue(adString);
                    adInput.value = adString; 
                    
                    // 2. Force Native Browser Events so PHPRunner's serializer catches the data
                    adInput.dispatchEvent(new Event('input', { bubbles: true }));
                    adInput.dispatchEvent(new Event('change', { bubbles: true }));
                    adInput.dispatchEvent(new Event('blur', { bubbles: true }));
                    
                    isSyncing = false; 
                } 
                else if (source === "AD") {
                    var parsedDate = new Date(value);
                    if (isNaN(parsedDate.getTime())) return;

                    var adDateObj = { 
                        year: parsedDate.getFullYear(), 
                        month: parsedDate.getMonth() + 1, 
                        day: parsedDate.getDate() 
                    };
                    
                    var bsObj = NepaliFunctions.AD2BS(adDateObj);
                    var bsString = formatDate(bsObj);
                    
                    isSyncing = true; 
                    
                    // 1. Update PHPRunner API & Visual DOM
                    bsControl.setValue(bsString);
                    bsInput.value = bsString; 
                    
                    // 2. Force Native Browser Events
                    bsInput.dispatchEvent(new Event('input', { bubbles: true }));
                    bsInput.dispatchEvent(new Event('change', { bubbles: true }));
                    bsInput.dispatchEvent(new Event('blur', { bubbles: true }));
                    
                    isSyncing = false; 
                }
            } catch (error) {
                console.error("Nepali Date Picker: Math Conversion Error ->", error);
                isSyncing = false;
            }
        }

        // Initialize Plugin
        var config = Object.assign({
            ndpYear: true,
            ndpMonth: true,
            ndpYearCount: 100,
            ndpEnglishInput: true,
            onChange: function() {
                doSync("BS", bsInput.value);
            }
        }, options || {});

        bsInput.nepaliDatePicker(config);

        // --- Event Listeners ---
        
        // Listen to Nepali Field
        $(bsInput).on("blur", function() { doSync("BS", this.value); });
        
        // Listen to English Field
        if (adControl) {
            // Catches typing
            $(adInput).on("blur change input", function() { 
                doSync("AD", adControl.getValue() || this.value); 
            });
            
            // Catches PHPRunner's built-in calendar popup selections
            adControl.on("change", function() {
                doSync("AD", adControl.getValue());
            });
        }
    }
}

// ========================================================================
// GLOBAL STANDALONE DATE CONVERTERS
// ========================================================================

/**
 * Converts an AD date string (e.g., "2023-08-29") to a BS date string ("2080-05-12")
 * @param {string} adDateString - The English date string
 * @returns {string} - The formatted Nepali date string, or empty string if invalid
 */
function ad2bs(adDateString) {
    if (!adDateString || typeof NepaliFunctions === 'undefined') return "";
    
    var parsedDate = new Date(adDateString);
    if (isNaN(parsedDate.getTime())) return "";
    
    var adDateObj = {
        year: parsedDate.getFullYear(),
        month: parsedDate.getMonth() + 1,
        day: parsedDate.getDate()
    };
    
    try {
        var bsObj = NepaliFunctions.AD2BS(adDateObj);
        var m = bsObj.month < 10 ? "0" + bsObj.month : bsObj.month;
        var d = bsObj.day < 10 ? "0" + bsObj.day : bsObj.day;
        return bsObj.year + "-" + m + "-" + d;
    } catch (e) {
        console.error("ad2bs conversion error:", e);
        return "";
    }
}

/**
 * Converts a BS date string (e.g., "2080-05-12") to an AD date string ("2023-08-29")
 * @param {string} bsDateString - The Nepali date string
 * @returns {string} - The formatted English date string, or empty string if invalid
 */
function bs2ad(bsDateString) {
    if (!bsDateString || typeof NepaliFunctions === 'undefined') return "";
    
    var cleanVal = bsDateString.replace(/\//g, "-").replace(/[^\d-]/g, "");
    var parts = cleanVal.split("-");
    if (parts.length !== 3) return "";
    
    var bsDateObj = { 
        year: Number(parts[0]), 
        month: Number(parts[1]), 
        day: Number(parts[2]) 
    };
    
    try {
        var adObj = NepaliFunctions.BS2AD(bsDateObj);
        var m = adObj.month < 10 ? "0" + adObj.month : adObj.month;
        var d = adObj.day < 10 ? "0" + adObj.day : adObj.day;
        return adObj.year + "-" + m + "-" + d;
    } catch (e) {
        console.error("bs2ad conversion error:", e);
        return "";
    }
}
3 Add / Edit Pages (Two-Way Sync)

Use the initNepaliDatePicker function to attach the interactive calendar and bind the two fields together. This provides real-time, bidirectional conversion.

Navigate to the Javascript OnLoad event of your Add or Edit page and add:

// Basic Two-Way Sync
initNepaliDatePicker(pageid, "date_bs", "date_ad");

// Advanced Usage with Custom Plugin Options
initNepaliDatePicker(pageid, "date_bs", "date_ad", {
    ndpYearCount: 200,      // Show 200 years in dropdown
    disableDaysBefore: 1,   // Disable past dates
    ndpEnglishInput: true   // Allow typing in English digits
});
Pro Tip: Both fields remain fully editable. If a user types into the AD field, the BS field updates instantly. If they click the BS calendar, the AD field updates instantly.
4 List & View Pages (Auto-Conversion)

Databases should always store English (AD) dates for accurate sorting and filtering. To display these AD dates as Nepali (BS) dates on List, View, or Print pages, use CSS class targeting.

Step 1: Add the CSS Class

In the PHPRunner Page Designer, click on your date column. In the properties panel on the right, enter format-ad-to-bs into the Custom CSS Class box.

Step 2: Trigger the Converter

In the Javascript OnLoad event for the List or View page, call the auto-converter (if you implemented the list display function):

// Scans the page and converts all targeted CSS classes
autoConvertDisplayDates();

Available Classes:

  • format-ad-to-bs : Translates "2023-08-29" into "2080-05-12"
  • format-bs-to-ad : Translates "2080-05-12" into "2023-08-29"
5 Standalone Converters (API)

If you need to perform translations manually in your own custom JavaScript logic (e.g., inside custom buttons or validation routines), you can use the globally exposed helper functions.

// Convert English to Nepali
var myNepaliDate = ad2bs("2026-04-04");
console.log(myNepaliDate); // Outputs: "2082-12-21"

// Convert Nepali to English
var myEnglishDate = bs2ad("2080-05-12");
console.log(myEnglishDate); // Outputs: "2023-08-29"

Note: These functions return an empty string "" if an invalid date format is provided or if the core library has not finished loading.