Back to Developer Home

Storepoint Locator Widget JavaScript API

Customize and extend your embedded store locator with our Locator Widget JavaScript API. Listen for events, trigger actions, and create seamless integrations between your application and the Storepoint locator widget.

Get Started →

Installation

Get Your Embed Code

Find your unique embed code (and public widget ID) on the 'Embed Locator' page in your dashboard.

Get Embed Code

Basic implementation example:

HTML
<!-- Place the widget container wherever you want the store locator to appear -->
<div id="storepoint-widget"></div>

<!-- Place these scripts before the closing body tag -->
<script src="https://widget.storepoint.co/embed.js"></script>
<script>
var storepoint = new StorepointWidget('YOUR_PUBLIC_WIDGET_ID', '#storepoint-widget', {
    // Optional configuration options
});
</script>

Remember to replace 'YOUR_PUBLIC_WIDGET_ID' with your public widget ID, or use the embed code directly from your dashboard embed locator page.

This shows a simplified synchronous loading version. The default embed code from your dashboard uses async loading for better performance. You can implement your own async loading too - just remember to initialize the widget after the embed.js script has loaded.

Available Events

Search Events

search

Triggered when a search is performed.

JavaScript
storepoint.on('search', function(data) {
    // data contains:
    // {
    //   lat: number|null,        // Latitude of search point
    //   lng: number|null,        // Longitude of search point
    //   query: string,           // Search text
    //   type: string,            // Search type (e.g., 'initial_search', 'filter_change')
    //   storepoint_location_id: number|null,  // ID of nearest location
    //   distance: number|null,   // Distance to nearest location
    //   tags: string[]|null      // Active filter tags
    // }
});

Location Events

location-result-item-click

Triggered when a location in the results list is clicked.

JavaScript
storepoint.on('location-result-item-click', function(data) {
    // data contains location details
});

map-marker-click

Triggered when a location marker on the map is clicked.

JavaScript
storepoint.on('map-marker-click', function(data) {
    // data contains location details
});

Online Store Events

online-store-click

Triggered when an online store link is clicked.

JavaScript
storepoint.on('online-store-click', function(data) {
    // data contains:
    // {
    //   store_name: string,  // Name of the online store
    //   store_link: string   // URL of the online store
    // }
});

Widget Events

load

Triggered when the widget initially loads.

JavaScript
storepoint.on('load', function(data) {
    // data contains:
    // {
    //   url: string,      // Current page URL
    //   referrer: string  // Referrer URL
    // }
});

no-results

Triggered when a search returns no results.

JavaScript
storepoint.on('no-results', function(data) {
    // Useful for implementing fallback behavior
    window.location.href = '/store-not-found';
});

API Methods

on(event: string, callback: Function)

Subscribes to widget events. The callback function receives event-specific data.

JavaScript
storepoint.on('search', function(data) {
    console.log('Search performed:', data);
});

setLanguage(language: ISO2Code)

Sets the locator widget's display language using a two-letter ISO language code (e.g. 'en', 'es', 'fr'). The language must be configured first in your Language Settings.

Only languages that have been configured in your dashboard settings will work. Make sure to set up your desired languages and translations in the Language Settings page before using this method.

JavaScript
// Example: Change to French (if configured in dashboard)
storepoint.setLanguage('fr');  // ISO code for French

// Example: Change to Spanish (if configured in dashboard)
storepoint.setLanguage('es');  // ISO code for Spanish

setFilters(filters: Array<string>)

Programmatically sets active filters on the widget.

JavaScript
storepoint.setFilters(['featured', 'premium']); // Activates these filter tags

getCurrentLocations()

Returns an array of location objects currently in view or in the search results. Useful for accessing location data for custom integrations.

JavaScript
// Get all locations currently in the widget view
const locations = storepoint.getCurrentLocations();

// Example: Log the names of all visible locations
locations.forEach(location => {
    console.log(location.name);
});

This method returns the current set of locations after any filtering or searching has been applied. The returned array contains the full location objects with all properties.

addCustomLayer(layerId: string, layerOptions: Object)

Adds a custom GeoJSON layer to the map. This allows you to overlay custom shapes, markers, or other geographic data on the map. See an advanced example that combines the search events and getCurrentLocations method to dynamically add a custom layer based on the search results.

JavaScript
// Add a custom polygon layer to the map
storepoint.addCustomLayer('my-custom-layer', {
    type: 'fill',
    fillColor: '#FF5733',
    fillOpacity: 0.2,
    strokeWidth: 2,
    strokeColor: '#FF5733',
    strokeOpacity: 0.8,
    data: {
        type: 'FeatureCollection',
        features: [{
            type: 'Feature',
            geometry: {
                type: 'Polygon',
                coordinates: [[
                    [-74.006, 40.713],
                    [-74.006, 40.717],
                    [-73.998, 40.717],
                    [-73.998, 40.713],
                    [-74.006, 40.713]
                ]]
            }
        }]
    }
});

The layerOptions object supports various styling properties including type (fill, line, symbol), fillColor, fillOpacity, strokeWidth, strokeColor, and strokeOpacity. The data property should contain valid GeoJSON.

removeCustomLayer(layerId: string)

Removes a previously added custom layer from the map by its ID.

JavaScript
// Remove a custom layer
storepoint.removeCustomLayer('my-custom-layer');

addCustomFilter(filterConfig: Object)

Adds a custom filter UI component to the widget with custom filtering logic. This allows for advanced filtering beyond the standard tag-based filters. The filterConfig object defines how your custom filter should be structured, including its ID, filtering function, UI template, and event handling. Below, we demonstrate the expected structure of the filterConfig object, along with a simple example of a price range filter that filters locations based on their price property.

JavaScript
// Example of a custom range filter for price
storepoint.addCustomFilter({
    // Unique identifier for this filter
    id: 'price_range',
    
    // Filter type (always 'custom' for custom filters)
    type: 'custom',
    
    /**
     * Filtering function that determines which locations to display
     * @param {Array} locations - Array of locations matching the current query before custom filters
     * @param {any} filterValue - Current value of the filter (from defaultValue or updateValue)
     * @return {Array} - Filtered array of locations that should be displayed
     */
    filter: function(locations, filterValue) {
        if (!filterValue) return locations;
        
        const min = filterValue[0];
        const max = filterValue[1];
        
        return locations.filter(location => {
            // Custom logic to filter locations based on a property
            const price = parseFloat(location.custom_fields.text.find(
                field => field.name === 'price'
            ).value);
            return !isNaN(price) && price >= min && price <= max;
        });
    },
    
    /**
     * HTML template for the filter UI
     * Can be provided as a string or as a getter function: get template() { return '...'; }
     * This defines the visual structure of your custom filter component
     */
    template: `
        <div class="price-filter">
            <div class="filter-header">Price Range</div>
            <div class="range-inputs">
                <input type="number" class="min-input" placeholder="Min">
                <span>to</span>
                <input type="number" class="max-input" placeholder="Max">
            </div>
        </div>
    `,
    
    /**
     * Setup function to initialize the filter UI and handle events
     * @param {HTMLElement} container - The DOM element where your filter HTML is mounted
     * @param {Function} updateValue - Callback function that must be called with the new filter value. This new value will be passed to the filter function to filter locations
     */
    setup: function(container, updateValue) {
        const minInput = container.querySelector('.min-input');
        const maxInput = container.querySelector('.max-input');
        
        // Handle input changes
        function handleChange() {
            const min = parseFloat(minInput.value) || 0;
            const max = parseFloat(maxInput.value) || 1000;
            updateValue([min, max]);
        }
        
        minInput.addEventListener('input', handleChange);
        maxInput.addEventListener('input', handleChange);
        
        // Initialize with default values
        minInput.value = 0;
        maxInput.value = 1000;
        updateValue([0, 1000]);
    },
    
    // Default filter value
    defaultValue: [0, 1000],
    
    // Optional CSS styles for the filter
    styles: `
        .price-filter {
            padding: 10px;
            background: #f5f5f5;
            border-radius: 4px;
        }
        .filter-header {
            font-weight: bold;
            margin-bottom: 8px;
        }
        .range-inputs {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .min-input, .max-input {
            width: 80px;
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
    `
});

The addCustomFilter method allows for highly customizable filters with your own UI and filtering logic. The filter function receives the current locations and the filter value, and should return the filtered locations. The setup function is responsible for initializing the UI and handling user interactions.

Advanced Examples

Adding Radius Circles Around Locations

This example demonstrates how to add radius circles around all locations currently in view. It combines getCurrentLocations(), addCustomLayer(), and event listening to create dynamic visualizations.

JavaScript
/**
 * Helper function to generate our radius circles
 */
function addLocationRadiusCircles(storepoint, locations, radiusInMiles, layerId) {
    // Remove existing layer if it exists
    storepoint.removeCustomLayer(layerId);
    
    // Create GeoJSON feature collection
    var featureCollection = {
        type: 'FeatureCollection',
        features: []
    };
    
    // Add circle features for each location
    locations.forEach(location => {
        // Create circle as GeoJSON polygon
        var circleFeature = createCircleFeature(
            [location.loc_long, location.loc_lat], 
            radiusInMiles
        );
        featureCollection.features.push(circleFeature);
    });
    
    // Add the layer to the map
    storepoint.addCustomLayer(layerId, {
        type: 'fill',
        fillColor: '#000000',
        fillOpacity: 0.15,
        strokeWidth: 1,
        strokeColor: '#000000',
        strokeOpacity: 0.3,
        data: featureCollection
    });
    
    /**
     * Helper function to create a GeoJSON Feature representing a circle
     */
    function createCircleFeature(center, radiusInMiles) {
        // Number of points to use for the circle
        points = 128;
        
        // Extract coordinates
        var coords = {
            longitude: center[0],
            latitude: center[1]
        };
        
        var coordinates = [];
        
        // Convert miles to approximate degrees
        var distanceX = radiusInMiles/(111.320*Math.cos(coords.latitude*Math.PI/180)*0.621371);
        var distanceY = radiusInMiles/(110.574*0.621371);
        
        // Generate points around the circle
        for(var i = 0; i < points; i++) {
            var angle = (i/points)*(2*Math.PI);
            var x = distanceX * Math.cos(angle);
            var y = distanceY * Math.sin(angle);
            
            coordinates.push([
                coords.longitude + x,
                coords.latitude + y
            ]);
        }
        
        // Close the polygon by repeating the first point
        coordinates.push(coordinates[0]);
        
        // Return GeoJSON feature
        return {
            type: "Feature",
            geometry: {
                type: "Polygon",
                coordinates: [coordinates]
            }
        };
    }
}

// Usage example: Add radius circles when search is performed
storepoint.on('search', function(data) {
    // Add 50-mile radius circles around all current locations
    addLocationRadiusCircles(
        storepoint,
        storepoint.getCurrentLocations(),
        50,
        'location-radius-50'
    );
});

This example creates radius circles around each location to visualize coverage areas. The circles are added as a custom layer using GeoJSON polygons. The radius calculation uses a simplified approximation that works well for most use cases.

Creating a Dual-Range Filter

This example shows how to create a sophisticated dual-range filter for filtering locations by a numeric range (such as square footage or price).

This is a more advanced example that demonstrates the flexibility of custom filters.

JavaScript
// Add a custom dual range filter for square footage
storepoint.addCustomFilter({
    type: 'custom',
    id: 'sq_ft_range',
    
    // Filtering function
    filter: function(locations, filterValue) {
        if (!filterValue) return locations;
        
        const min = filterValue[0];
        const max = filterValue[1];
        
        return locations.filter(location => {
            // Find square footage values in custom fields
            let sqFt = null;
            
            // Check if custom_fields exist
            if (location.custom_fields && location.custom_fields.text) {
                // Find the square footage field by its token
                const sqFtField = location.custom_fields.text.find(
                    field => field.token === "square_footage_token"
                );
                
                if (sqFtField) {
                    sqFt = parseFloat(sqFtField.value);
                }
            }
            
            // If square footage exists and is in range, include the location
            if (!isNaN(sqFt)) {
                return sqFt >= min && sqFt <= max;
            }
            
            // If no square footage data, don't filter out
            return true;
        });
    },
    
    // Configuration for the filter
    rangeConfig: {
        minValue: 0,
        maxValue: 10000,
        defaultMin: 0,
        defaultMax: 10000,
        step: 100
    },
    
    // HTML template for the filter UI
    template: `
        <div class="range-filter">
            <div class="filter-header">Square Footage</div>
            <div class="range-inputs">
                <input type="number" class="min-input" placeholder="Min">
                <span>to</span>
                <input type="number" class="max-input" placeholder="Max">
                <span>sq ft</span>
            </div>
            <div class="range-slider">
                <input type="range" class="min-slider" min="0" max="10000" step="100" value="0">
                <input type="range" class="max-slider" min="0" max="10000" step="100" value="10000">
            </div>
        </div>
    `,
    
    // Setup function (simplified version)
    setup: function(container, updateValue) {
        const minInput = container.querySelector('.min-input');
        const maxInput = container.querySelector('.max-input');
        const minSlider = container.querySelector('.min-slider');
        const maxSlider = container.querySelector('.max-slider');
        
        // Handle input changes and update the filter value
        function handleChange() {
            const min = parseInt(minInput.value) || 0;
            const max = parseInt(maxInput.value) || 10000;
            updateValue([min, max]);
        }
        
        // Set up event listeners
        minInput.addEventListener('input', handleChange);
        maxInput.addEventListener('input', handleChange);
        minSlider.addEventListener('input', function() {
            minInput.value = this.value;
            handleChange();
        });
        maxSlider.addEventListener('input', function() {
            maxInput.value = this.value;
            handleChange();
        });
        
        // Initialize with default values
        minInput.value = 0;
        maxInput.value = 10000;
        updateValue([0, 10000]);
    },
    
    // Default filter value
    defaultValue: [0, 10000],
    
    // CSS styles for the filter (simplified)
    styles: `
        .range-filter {
            padding: 10px;
            background: #f5f5f5;
            border-radius: 4px;
        }
        .filter-header {
            font-weight: bold;
            margin-bottom: 8px;
        }
        .range-inputs {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
        }
        .min-input, .max-input {
            width: 80px;
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .range-slider {
            position: relative;
            height: 30px;
        }
        .min-slider, .max-slider {
            position: absolute;
            width: 100%;
            pointer-events: none;
        }
        .min-slider::-webkit-slider-thumb, .max-slider::-webkit-slider-thumb {
            pointer-events: auto;
        }
    `
});

Interested in other events or functions?

We're constantly expanding our API capabilities. Contact us to discuss your specific needs or request new features.

Contact us at [email protected]