Skip to main content

RP Promap Help: Store Locator Module (Plugins System)

Reusable JavaScript system that enhances SCASL store locator behavior using a plugin architecture

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:

  1. Core Engine

    • Handles locations, selectors, and DOM updates

  2. Plugins

    • Small functions that run per location

    • Each plugin adds a specific behavior

  3. 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 ID

  • appointmentOnlyDays → Days to append text

  • noticeDays → 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.id

  • Confirm SCASL markup exists

  • Inspect DOM for selector changes


Script Runs Too Early

Handled automatically with:

  • window.load

  • Retry 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.

Did this answer your question?