Skip to main content

RP Local Pickup Widget: Disable Unavailable Pickup and Delivery Time Slots

This helps prevent customers from selecting pickup or delivery times that have already reached capacity.

RP Local Pickup Widget: Disable Unavailable Pickup and Delivery Time Slots

The RP Local Pickup Widget is a Promap-style JavaScript widget for RP Local Pickup and Delivery stores. It checks visible pickup or delivery time slots against the Store Pickup API and disables any slot that is no longer available.

This helps prevent customers from selecting pickup or delivery times that have already reached capacity.

What this widget does

The widget runs on the storefront and:

  1. Detects whether the customer is using Pickup or Delivery.

  2. Reads the selected date.

  3. Reads the selected pickup or delivery location.

  4. Checks each visible time slot against the availability API.

  5. Disables unavailable time slots in the time picker.

Unavailable slots are given the disabled picker class:

picker__list-item--disabled

When to use this widget

Use this widget when you want to improve checkout accuracy by hiding or disabling unavailable pickup or delivery times before the customer completes checkout.

Common use cases include:

  • Preventing customers from choosing fully booked pickup times.

  • Reducing support requests caused by unavailable delivery windows.

  • Improving the customer experience during high-volume order periods.

  • Adding frontend availability checks without editing the core Store Pickup app files.

  • Supporting themes where pickup UI is re-rendered dynamically.

Requirements

Before installing the widget, confirm that your storefront includes:

  • SCASPSetting.app_url

  • Shopify.shop

  • jQuerySCASP or jQuery

  • Time picker options using .picker__list-item

  • Time picker entries with a data-pick attribute

The widget should be loaded after the main Store Pickup scripts.

Installation

Upload the file:

rp-local-pickup-widget.js

to your Shopify theme assets.

Then add the following code after the Store Pickup scripts, usually in theme.liquid, the cart template, or the page where the pickup widget appears:

<script src="{{ 'rp-local-pickup-widget.js' | asset_url }}"></script> <script>   window.rpPickupWidget = window.RPLocalPickupWidget.init({     appUrl: window.SCASPSetting && window.SCASPSetting.app_url,     shopName: window.Shopify && window.Shopify.shop,     method: 'pickup',     debounceMs: 250,     debug: false   }); </script>

Implementation notes for the .js file as a Promap widget

The .js file is built as a standalone runtime widget. It does not require editing the base Store Pickup app files.

The widget exposes a global object:

window.RPLocalPickupWidget

You initialize it with:

window.RPLocalPickupWidget.init({...});

The initialized widget can be stored globally:

window.rpPickupWidget = window.RPLocalPickupWidget.init({...});

This allows the widget to be refreshed manually later if needed.

Optional selector overrides

If your theme uses custom pickup or delivery selectors, pass custom selectors during initialization:

window.RPLocalPickupWidget.init({   appUrl: window.SCASPSetting.app_url,   shopName: window.Shopify.shop,   selectors: {     pickupDateInput: '#date-pickup',     deliveryDateInput: '#date-delivery',     pickupTimeInput: '#time-pickup',     deliveryTimeInput: '#time-delivery',     pickupLocationChecked: 'input[name="location_id"]:checked',     deliveryLocationFirst: '.location_id',     timeItem: '.picker__list-item'   },   classes: {     disabled: 'picker__list-item--disabled'   } });

Manual refresh

If your theme updates the pickup or delivery widget through AJAX, call:

window.rpPickupWidget && window.rpPickupWidget.refresh();

This forces the widget to re-check the currently visible time slots.

Debugging

To enable console logging, set:

debug: true

Example:

window.rpPickupWidget = window.RPLocalPickupWidget.init({   appUrl: window.SCASPSetting && window.SCASPSetting.app_url,   shopName: window.Shopify && window.Shopify.shop,   debug: true });

Troubleshooting

No time slots are disabled

Check that:

  • SCASPSetting.app_url is available.

  • Shopify.shop is available.

  • Time options contain data-pick.

  • The script is loaded after the Store Pickup app scripts.

  • The pickup or delivery date has been selected.

Wrong location is being checked

Override the location selectors:

selectors: {   pickupLocationChecked: 'input[name="location_id"]:checked',   deliveryLocationFirst: '.location_id' }

Script loads but does nothing

Confirm that the pickup UI exists before the widget initializes. If the UI loads later through AJAX, call:

window.rpPickupWidget.refresh();

after the pickup form renders.

Important notes

This widget improves the frontend customer experience, but backend validation should remain the source of truth.

For delivery, some Store Pickup API logic may check availability at the day level rather than the individual time-slot level. In that case, multiple delivery times may enable or disable together.

The widget may make one API call per visible time option, so test performance on mobile and low-bandwidth connections when many time slots are shown.

(function (window) {  
'use strict';
var defaultConfig = { appUrl: null,
shopName: null,
method: 'pickup',
selectors: {
pickupTab: '.sca-storepickup-pickup-tab',
deliveryTab: '.sca-storepickup-delivery-tab',
pickupDateInput: '#date-pickup',
deliveryDateInput: '#date-delivery',
pickupTimeInput: '#time-pickup',
deliveryTimeInput: '#time-delivery',
pickupLocationChecked: 'input[name="location_id"]:checked',
deliveryLocationFirst: '.location_id',
timeItem: '.picker__list-item'
},
classes: {
disabled: 'picker__list-item--disabled'
},
debounceMs: 250,
debug: false
};
function log(config) {
if (!config.debug || !window.console) return;
var args = Array.prototype.slice.call(arguments, 1);
args.unshift('[RP Local Pickup Widget]');
console.log.apply(console, args);
}
function mergeDeep(target, source) {
Object.keys(source || {}).forEach(function (key) {
var srcVal = source[key];
var tgtVal = target[key];
if (srcVal && typeof srcVal === 'object' && !Array.isArray(srcVal)) {
target[key] = mergeDeep(tgtVal && typeof tgtVal === 'object' ? tgtVal : {}, srcVal);
} else {
target[key] = srcVal;
}
});
return target;
}
function parseDateValue(value) {
if (!value) return null;
var date = new Date(value);
if (!isNaN(date.getTime())) {
return { day: date.getDate(), month: date.getMonth() + 1, year: date.getFullYear() };
}
var match = value.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/);
if (!match) return null;
return {
day: parseInt(match[1], 10),
month: parseInt(match[2], 10),
year: parseInt(match[3], 10)
};
}
function pickMethod($, config) {
if ($(config.selectors.deliveryTab).hasClass('active')) return 'delivery';
if ($(config.selectors.pickupTab).hasClass('active')) return 'pickup';
return config.method;
}
function getDateParts($, config, method) {
var selector = method === 'pickup' ? config.selectors.pickupDateInput : config.selectors.deliveryDateInput;
return parseDateValue($(selector).val());
}
function getLocationId($, config, method) {
if (method === 'pickup') {
return parseInt($(config.selectors.pickupLocationChecked).val(), 10);
}
return parseInt($(config.selectors.deliveryLocationFirst).first().val(), 10);
}
function getTimeItems($, config) {
return $(config.selectors.timeItem)
.not('.' + config.classes.disabled)
.filter(function () { return typeof $(this).attr('data-pick') !== 'undefined'; });
}
function disableItem($item, config) {
$item.addClass(config.classes.disabled);
$item.attr('aria-disabled', 'true');
}
function checkSlot($, config, payload) {
return $.ajax({
url: config.appUrl + '/api/check-available-time-slot',
method: 'post',
dataType: 'json',
data: payload
});
}
function buildPayload(config, method, dateParts, locationId, pick) {
var payload = {
method: method,
day: dateParts.day,
month: dateParts.month,
year: dateParts.year,
shop_name: config.shopName,
location_id: locationId
};
if (method === 'pickup') {
payload.hour = Math.floor(pick / 60);
payload.minute = pick % 60;
}
return payload;
}
function disableUnavailableTimes($, config) {
var method = pickMethod($, config);
var dateParts = getDateParts($, config, method);
var locationId = getLocationId($, config, method);
if (!dateParts || !locationId || !config.shopName || !config.appUrl) {
log(config, 'Skipped check due to missing input', {
method: method,
dateParts: dateParts,
locationId: locationId,
shopName: config.shopName,
appUrl: config.appUrl
});
return;
}
var $items = getTimeItems($, config);
if (!$items.length) {
log(config, 'No eligible time items found');
return;
}
$items.each(function () {
var $item = $(this);
var pick = parseInt($item.attr('data-pick'), 10);
if (isNaN(pick)) return;
var payload = buildPayload(config, method, dateParts, locationId, pick);
checkSlot($, config, payload)
.done(function (res) {
if (parseInt(res.code, 10) !== 2) {
disableItem($item, config);
}
})
.fail(function () {
log(config, 'Slot check request failed', payload);
});
});
}
function bindEvents($, config) {
var debounceId = null;
function schedule() {
if (debounceId) clearTimeout(debounceId);
debounceId = setTimeout(function () {
disableUnavailableTimes($, config);
}, config.debounceMs);
}
$(document).on('focus click', [
config.selectors.pickupTimeInput,
config.selectors.deliveryTimeInput,
config.selectors.pickupDateInput,
config.selectors.deliveryDateInput
].join(','), schedule);
$(document).on('click', '.picker__day, .picker__nav--next, .picker__nav--prev', schedule);
}
function createWidget(userConfig) {
var config = mergeDeep(mergeDeep({}, defaultConfig), userConfig || {});
var $ = window.jQuerySCASP || window.jQuery;
if (!$) {
throw new Error('RP Local Pickup Widget requires jQuerySCASP or jQuery.');
}
if (!config.shopName && window.Shopify && window.Shopify.shop) {
config.shopName = window.Shopify.shop;
}
bindEvents($, config);
log(config, 'Initialized', config);
return {
refresh: function () {
disableUnavailableTimes($, config);
}
};
}
window.RPLocalPickupWidget = {
init: createWidget,
defaults: defaultConfig
};
})(window);

Did this answer your question?