RP Promap Help: Store Locator Module (Plugins System)
Overview
The RP Promap Store Locator Module is a reusable JavaScript system that enhances SCASL store locator behavior using a plugin architecture.
It allows you to:
Add appointment notices dynamically
Modify operating hours text
Inject UI elements (headers, alerts, indicators)
Extend functionality without rewriting the core script
How It Works
The module has three parts:
Core Engine
Handles locations, selectors, and DOM updates
Plugins
Small functions that run per location
Each plugin adds a specific behavior
Configuration
Location IDs, rules, and messages
Installation
Step 1: Add the Script
Paste the full module script into your site (before </body> or in your custom JS area).
<!-- RP Promap Store Locator Module -->
<script>
/* FULL SCRIPT HERE (use your latest version) */
<script>
/*
RP Promap Store Locator Module
Reusable plugin-based script
Updated: 4-27-26
*/
(function () {
try {
var RPPromapStoreLocator = {
selectors: {
locationById: function (id) {
return '[data-location-id="' + id + '"]';
},
operatingHourDayCell: 'td.oh-item',
operatingHoursBlock: '.scasl-operating-hour.scasl-field',
operatingHoursHeader: '.scasl-operating-hours-label',
expandedNotice: '.rp-appointment-notice',
headerNotice: '.rp-appointment-header-notice'
},
classes: {
expandedNotice: 'rp-appointment-notice',
headerNotice: 'rp-appointment-header-notice'
},
messages: {
appointmentOnlyDay: 'solo con turno previo',
todayAppointmentOnly: '⚠️ Hoy solo con turno previo'
},
styles: {
expandedNotice: 'color:#b00;font-weight:bold;font-size:13px;margin:6px 0;',
headerNotice: 'color:#b00;font-weight:bold;font-size:12px;margin-top:2px;'
},
locations: [
{
name: 'Acassuso',
id: '18535034',
appointmentOnlyDays: ['Lunes', 'Sabado', 'Sábado'],
noticeDays: [1, 6]
},
{
name: 'Belgrano',
id: '18535037',
appointmentOnlyDays: ['Sabado', 'Sábado'],
noticeDays: [6]
}
],
plugins: [],
registerPlugin: function (plugin) {
if (typeof plugin === 'function') {
this.plugins.push(plugin);
}
},
getLocationContainer: function (locationId) {
return document.querySelector(this.selectors.locationById(locationId));
},
createNoticeElement: function (className, styleText) {
var notice = document.createElement('div');
notice.className = className;
notice.style.cssText = styleText;
return notice;
},
runPlugins: function () {
var module = this;
module.locations.forEach(function (location) {
var container = module.getLocationContainer(location.id);
if (!container) return;
module.plugins.forEach(function (plugin) {
plugin({
module: module,
location: location,
container: container,
today: new Date().getDay()
});
});
});
},
debounce: function (fn, delay) {
var timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(fn, delay);
};
},
limitedRetry: function (fn, interval, maxAttempts) {
var attempts = 0;
var timer = setInterval(function () {
attempts += 1;
fn();
if (attempts >= maxAttempts) {
clearInterval(timer);
}
}, interval);
},
startObserver: function () {
var module = this;
var debouncedRun = module.debounce(function () {
module.runPlugins();
}, 300);
var observer = new MutationObserver(function (mutations) {
var shouldRun = mutations.some(function (mutation) {
return mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0;
});
if (shouldRun) {
debouncedRun();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
},
init: function () {
var module = this;
window.addEventListener('load', function () {
module.runPlugins();
module.limitedRetry(function () {
module.runPlugins();
}, 1000, 5);
module.startObserver();
});
}
};
/* ==============================
Plugin: Appointment Text on Days
============================== */
RPPromapStoreLocator.registerPlugin(function (context) {
var module = context.module;
var location = context.location;
var container = context.container;
container.querySelectorAll(module.selectors.operatingHourDayCell).forEach(function (cell) {
var text = cell.textContent.trim();
if (
location.appointmentOnlyDays.indexOf(text) !== -1 &&
text.indexOf(module.messages.appointmentOnlyDay) === -1
) {
cell.textContent = text + ' (' + module.messages.appointmentOnlyDay + ')';
}
});
});
/* ==============================
Plugin: Always-Visible Appointment Notice
Shows above .scasl-operating-hour.scasl-field
============================== */
RPPromapStoreLocator.registerPlugin(function (context) {
var module = context.module;
var location = context.location;
var container = context.container;
var today = context.today;
var message = location.noticeDays.indexOf(today) !== -1
? module.messages.todayAppointmentOnly
: '';
var notice = container.querySelector(module.selectors.expandedNotice);
if (!notice) {
notice = module.createNoticeElement(
module.classes.expandedNotice,
module.styles.expandedNotice
);
var hoursBlock = container.querySelector(module.selectors.operatingHoursBlock);
if (hoursBlock && hoursBlock.parentNode) {
hoursBlock.parentNode.insertBefore(notice, hoursBlock);
} else {
container.insertBefore(notice, container.firstChild);
}
}
notice.textContent = message || '';
notice.style.display = message ? 'block' : 'none';
});
/* ==============================
Plugin: Header Appointment Notice
============================== */
RPPromapStoreLocator.registerPlugin(function (context) {
var module = context.module;
var location = context.location;
var container = context.container;
var today = context.today;
var message = location.noticeDays.indexOf(today) !== -1
? module.messages.todayAppointmentOnly
: '';
var header = container.querySelector(module.selectors.operatingHoursHeader);
if (!header) return;
var notice = header.querySelector(module.selectors.headerNotice);
if (!notice) {
notice = module.createNoticeElement(
module.classes.headerNotice,
module.styles.headerNotice
);
header.insertBefore(notice, header.firstChild);
}
notice.textContent = message || '';
notice.style.display = message ? 'block' : 'none';
});
RPPromapStoreLocator.init();
window.RPPromapStoreLocator = RPPromapStoreLocator;
} catch (error) {
console.warn('RP Promap Store Locator Module:', error);
}
})();
</script>
</script>
Step 2: Update Locations
Edit the locations array:
locations: [
{
name: 'Acassuso',
id: '18535034',
appointmentOnlyDays: ['Lunes', 'Sabado', 'Sábado'],
noticeDays: [1, 6]
}
]
Fields:
id→ SCASL location IDappointmentOnlyDays→ Days to append textnoticeDays→ Days to show alert (0 = Sunday)
Default Features (Included Plugins)
1. Appointment Text on Days
Adds text like:
Lunes (solo con turno previo)
2. Always-Visible Notice
Displays:
⚠️ Hoy solo con turno previo
✔ Appears above:
.scasl-operating-hour.scasl-field
✔ Works in:
Collapsed view
Expanded view
3. Header Notice
Adds the same message inside:
.scasl-operating-hours-label
Plugin System
What is a Plugin?
A plugin is a function that runs for each location:
RPPromapStoreLocator.registerPlugin(function (context) {
// your logic here
});Plugin Context
Each plugin receives:
{
module, // main system
location, // location config
container, // DOM element
today // current day (0–6)
}Creating a Custom Plugin
Example: Add a Badge
RPPromapStoreLocator.registerPlugin(function (context) {
var container = context.container;
var badge = document.createElement('div');
badge.textContent = 'Premium Location';
badge.style.cssText = 'background:#A4118D;color:#fff;padding:4px 8px;font-size:11px;margin-bottom:6px;';
container.insertBefore(badge, container.firstChild);
});Example: Highlight Open Days
RPPromapStoreLocator.registerPlugin(function (context) {
var module = context.module;
context.container
.querySelectorAll(module.selectors.operatingHourDayCell)
.forEach(function (cell) {
if (cell.textContent.includes('Lunes')) {
cell.style.fontWeight = 'bold';
}
});
});Best Practices
1. Keep Plugins Focused
Each plugin should do one thing only
✔ Good:
Add notice
Modify text
Inject UI
❌ Avoid:
Large multi-purpose plugins
2. Use Module Selectors
Always reference selectors from the module:
module.selectors.operatingHourDayCell
3. Prevent Duplicates
Always check before inserting elements:
if (!container.querySelector('.my-element')) {
// create it
}4. Use Safe Anchors
Best placement targets:
.scasl-operating-hour.scasl-field.scasl-operating-hours-label
Avoid:
.maximize-oh-status(unstable)
Customization
Change Messages
messages: {
appointmentOnlyDay: 'appointment only',
todayAppointmentOnly: '⚠️ Today by appointment only'
}Change Styles
styles: {
expandedNotice: 'color:#A4118D;font-weight:bold;'
}Troubleshooting
Notice Not Showing
Check correct
location.idConfirm SCASL markup exists
Inspect DOM for selector changes
Script Runs Too Early
Handled automatically with:
window.loadRetry system
MutationObserver
Duplicate Elements
Ensure plugin checks before inserting elements.
Extending the Module
You can build reusable features like:
Status badges (Open / Closed)
Holiday overrides
Geo-based messaging
Dynamic promotions
Summary
The RP Promap Store Locator Module gives you:
✔ Modular architecture
✔ Easy customization
✔ Safe DOM handling
✔ Scalable plugin system
Need More Advanced Features?
If you're building:
Multi-language support
Dynamic API-driven hours
Advanced UI overlays
You can extend this module with additional plugins.
