Call Javascript function from hit result anchor tag

Hi

I have an instantSearch.js project with a Leaflet map and hits listing

I want to create a hyperlink in a hit item so that I can click that and it will call Leaflet’s map.setView function

How do I do that? I tried calling referring the JS “map” object in the mustache template where I declare the anchor tag but that doesn’t work

Thanks

Hi @sami.jan, welcome to the forum.

If I understand correctly, you’d like to modify the hits template so that it includes a link in it. You should be able to do that directly in the hits item template as you suggest. When you say it doesn’t work – is there some error being thrown? Would you be able to share a live reproduction of the issue in a codesandbox? Just the minimal amount of code necessary to show what you have tried would be very helpful. This InstantSearch.js template should be a good start. Looking forward to hearing back from you!

Hi Maria

Thanks for the tips

I am using instantSearch.js and adapted the Leaflet widget here: https://www.algolia.com/doc/api-reference/widgets/geo-search/js/#full-example

So in my html I have

<div id="maps"></div>
<div id="hits"></div>

And in my JS I have code that looks like this:

// Create the custom widget
const customGeoSearch = instantsearch.connectors.connectGeoSearch(
    renderGeoSearch
);

// Instantiate the custom widget
search.addWidgets([
    customGeoSearch({
        container: document.querySelector('#maps'),
        initialZoom: 12,
        initialPosition: {
            lat: 33,
            lng: 73,
        },
    })
]);

renderGeoSearch function is defined just like the boilerplate from the example above

// Create the render function
let map = null;
let markers = [];
let isUserInteraction = true;
let userClickedOnPopup = true;

function centerMap(_geoloc, zoom) {
    map.setView(_geoloc, zoom);
}

const renderGeoSearch = (renderOptions, isFirstRendering) => {
    const {
        items,
        currentRefinement,
        refine,
        clearMapRefinement,
        widgetParams,
    } = renderOptions;

    const {
        initialZoom,
        initialPosition,
        container,
    } = widgetParams;

    if (isFirstRendering) {
        console.log("isFirstRendering = " + isFirstRendering);
        const element = document.createElement('div');
        element.style.height = '100%';

        const button = document.createElement('button');
        button.textContent = 'Clear the map refinement';

        map = L.map(element);

        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
        }).addTo(map);

        map.on('moveend', () => {
            console.log("moveend called");
            if (isUserInteraction && !userClickedOnPopup) {
                const ne = map.getBounds().getNorthEast();
                const sw = map.getBounds().getSouthWest();

                refine({
                    northEast: { lat: ne.lat, lng: ne.lng },
                    southWest: { lat: sw.lat, lng: sw.lng },
                });
            }
            userClickedOnPopup = false;
        });

        button.addEventListener('click', () => {
            clearMapRefinement();
        });

        container.appendChild(element);
        container.appendChild(button);
    }

    container.querySelector('button').hidden = !currentRefinement;

    markers.forEach(marker => marker.remove());

    markers = items.map((item) =>
        L.marker([item._geoloc.lat, item._geoloc.lng])
        .addTo(map)
        .bindPopup(infoWindowContent(item))
        .on('click', () => userClickedOnPopup = true)
    );

    isUserInteraction = false;
    if (!currentRefinement && markers.length) {
        console.log("going to fitbounds");
        map.fitBounds(L.featureGroup(markers).getBounds(), {
            animate: false,
        });
    } else if (!currentRefinement) {
        console.log("going to setView");
        map.setView(initialPosition, initialZoom, {
            animate: false,
        });
    }
    isUserInteraction = true;
};

Now I can see all my points on the map, which is great

But the issue is that I want the user to be able to click a “Show me this on the map” link on a card in the hits result and when the user clicks that, the correct marker comes into focus

I tried doing that with:

instantsearch.widgets.hits({
        container: '#hits',
        templates: {
            item: `
        <div class="card-info-box">
            <div class="hit-name">
                {{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}
            </div>
            {{#_geoloc}}
                <div class="hit-price">
                  <a href='#' onclick='map.setCenter([_geoloc.lat,_geoloc.lng], 12)'>
                    Find on map
                  </a>
                </div>
            {{/_geoloc}}
        </div>
        `
        },
    }),

But the “map” object in JS is not visible to this mustache “context”…

When I click the anchor link, it returns:

(index):1 Uncaught ReferenceError: map is not defined
    at HTMLAnchorElement.onclick ((index):1)

Hi @sami.jan,

Thanks for the response and it looks like you’ve put some great thought into your implementation. For us to best help you, we’d need to see a live example, even if in a failing state.

Could you please update this sandbox to fit your needs: https://codesandbox.io/s/github/algolia/create-instantsearch-app/tree/templates/instantsearch.js

And then give us steps to replicate the issue? Thanks in advance.

Thank you Ajay - please see this https://codesandbox.io/s/strange-waterfall-mw1vu?fontsize=14&hidenavigation=1&theme=dark

I tried to pull my full leaflet code in here but it won’t detect its objects in codesandbox so a map is not rendered here but the main problem I will highlight

Essentially, this is a list of geographical locations - the #hits div shows the name and details of each location

I want a user to click the “Click here” anchor on one of these cards, which will zoom in on the map (which you can’t see here)

The JS code has a Javascript map attribute like I defined (mapObject) and I need to call mapObject.setView on it

I am assuming I would call a JS method from the template and this would call the mapObject.setView method but the panToLocationOnMap method itself is not found by the Mustache “renderer”

In a nutshell, how do I call panToLocationOnMap from the mustache template

Thanks :slight_smile:

Hi @sami.jan you can’t call a function from Hogan (we don’t use mustache, but Hogan is practically the same as mustache).

But you can add a listener on this #hits DOM element, expose some data attributes, and call your function from there.

I changed your example to show you how: https://codesandbox.io/s/icy-firefly-bqp5x
I hope this helps!

wow! thanks so much Youcef - the solution looks pretty manageable and it works!

And kids, if you every run a software company, this is how you support your customers - Algolia is by far the best in providing the kind of support that makes customers (future ones too) actually happy

2 Likes