Store Locator Widget - Custom Renderer

Customise the rendering of your asset data on the side panel.

  1. Example
  2. Running the Sample Locally

Example

Store Locator Widget - Custom Renderer
        const configLocator = {
  maps: {
    provider: "woosmap",
    localities: {
      types: [],
    },
  },
  datasource: {
    max_responses: 5,
    max_distance: 0,
  },
  theme: {
    primary_color: "#00754a",
  },
  woosmapview: {
    initialZoom: 15,
    breakPoint: 16,
    tileStyle: {
      color: "#00754a",
      size: 13,
      minSize: 5,
    },
    style: {
      default: {
        icon: {
          url: "https://images.woosmap.com/starbucks-marker.svg",
          scaledSize: {
            height: 40,
            width: 34,
          },
        },
        selectedIcon: {
          url: "https://images.woosmap.com/starbucks-marker-selected.svg",
          scaledSize: {
            height: 50,
            width: 43,
          },
        },
      },
    },
  },
};

function getPhone({ properties }: woosmap.map.stores.StoreResponse) {
  const phone = properties.contact?.phone;
  return phone
    ? `<li id='store-phone'><span class='marker-image'></span><p><a class='text-black' href='tel:${phone}'>${phone}</a></p></li>`
    : "";
}

function getWebSite({ properties }: woosmap.map.stores.StoreResponse) {
  const website = properties.contact?.website;
  return website
    ? `<li id='store-website'><span class='marker-image'></span><a class='text-black' href='${website}' target='_blank'>More Details</a></li>`
    : "";
}

function getDistanceAndTime({ properties }) {
  const distanceLabel =
    (properties.distance_text || "") +
    (properties.duration_text ? ` (${properties.duration_text})` : "");
  return `<p class='summary-distance'>${distanceLabel}</p>`;
}

function formatAddress(properties: woosmap.map.stores.Store): string {
  return properties && properties.address
    ? `${properties.address.lines || ""}, ${properties.address.zipcode || ""} ${properties.address.city || ""}`
    : "";
}

function getAddress({ properties }: woosmap.map.stores.StoreResponse): string {
  const address = formatAddress(properties);
  return `
    <li id='store-address'>
      <span class='marker-image'></span>
      <div>
        <p>${address}</p>
        <p>${getDistanceAndTime({ properties })}</p>
      </div>
    </li>`;
}

function getSummaryAddress({
  properties,
}: woosmap.map.stores.StoreResponse): string {
  const address = formatAddress(properties);
  return `<p class='summary-address'>${address}</p>`;
}

function getSummaryPhone({
  properties,
}: woosmap.map.stores.StoreResponse): string {
  const phone =
    properties && properties.contact ? properties.contact.phone || "" : "";
  return `<p class='summary-address'>${phone}</p>`;
}

function getFullSchedule({
  properties,
}: woosmap.map.stores.StoreResponse): string {
  const weeklyOpening = properties.weekly_opening;
  const dayLabels = {
    1: "Monday",
    2: "Tuesday",
    3: "Wednesday",
    4: "Thursday",
    5: "Friday",
    6: "Saturday",
    7: "Sunday",
  };

  if (!weeklyOpening) {
    return "";
  }

  const daysHoursHTMLTable = Object.keys(dayLabels)
    .map((day) => {
      const hours = weeklyOpening[day]?.hours || [];
      let daysHours = "";

      if (hours.length === 0) {
        daysHours = "Closed";
      } else {
        const hoursStrings = hours.map((hour) => {
          if (hour["all-day"]) {
            return "24h/24";
          }
          return `${hour.start}-${hour.end}`;
        });
        daysHours = hoursStrings.join(", ");
      }

      return `<li><span class='day'>${dayLabels[day]}</span><span class="hours">${daysHours}</span></li>`;
    })
    .join("");

  return `<ul class='store-opening-hours-list'>${daysHoursHTMLTable}</ul>`;
}

function getHours(store: woosmap.map.stores.StoreResponse) {
  return `<li id='store-hours'><span class='marker-image'></span>${getFullSchedule(
    store,
  )}</li>`;
}

function getOpeningLabel({
  properties,
}: woosmap.map.stores.StoreResponse): string {
  if (!properties.open) {
    return "";
  }

  let openLabel: string;
  if (properties.open.open_now) {
    openLabel = `Open now until ${properties.open.current_slice?.end}`;
  } else {
    // @ts-ignore - TODO next_opening is wrongly spelled `nextOpening` in @types/woosmap.map
    openLabel = `Closed until ${convertTime(Date.parse(properties.open.next_opening?.day || "") / 1000)} at ${properties.open.next_opening?.start}`;
  }

  return `<p class='summary-hours'>${openLabel}</p>`;
}

function convertTime(UNIX_timestamp: number): string {
  const date = new Date(UNIX_timestamp * 1000);
  return date.toLocaleString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
  });
}

function createDetailedStoreCard(
  store: woosmap.map.stores.StoreResponse,
): HTMLElement {
  const myCustomContent = document.createElement("ul");
  myCustomContent.id = "myCustomContentID";
  myCustomContent.innerHTML = [
    getWebSite(store),
    getAddress(store),
    getHours(store),
    getPhone(store),
  ].join("");
  return myCustomContent;
}

function createSummaryStoreCard(
  store: woosmap.map.stores.StoreResponse,
): HTMLElement {
  const mySummaryContent = document.createElement("div");
  mySummaryContent.className = "store-summary";
  mySummaryContent.innerHTML = [
    `<p class='store-name'>${store.properties.name}</p>`,
    getSummaryAddress(store),
    getSummaryPhone(store),
    getOpeningLabel(store),
    getDistanceAndTime(store),
  ].join("");
  return mySummaryContent;
}

function isMobileDevice(): boolean {
  return window.innerWidth < 500;
}

function initStoreLocator(): void {
  const webapp = new window.WebApp("map", "YOUR_API_KEY");

  webapp.setFullStoreRenderer(createDetailedStoreCard);
  webapp.setSummaryStoreRenderer(createSummaryStoreCard);

  webapp.setConf(configLocator);
  webapp.setInitialStateToSelectedStore("12003");
  webapp.render(isMobileDevice());
}

initStoreLocator();

declare global {
  // currently, the WebApp typings are not exported, so we use `any` here
  interface Window {
    WebApp: new (elementId: string, projectKey: string) => any;
  }
}

    
        const configLocator = {
  maps: {
    provider: "woosmap",
    localities: {
      types: [],
    },
  },
  datasource: {
    max_responses: 5,
    max_distance: 0,
  },
  theme: {
    primary_color: "#00754a",
  },
  woosmapview: {
    initialZoom: 15,
    breakPoint: 16,
    tileStyle: {
      color: "#00754a",
      size: 13,
      minSize: 5,
    },
    style: {
      default: {
        icon: {
          url: "https://images.woosmap.com/starbucks-marker.svg",
          scaledSize: {
            height: 40,
            width: 34,
          },
        },
        selectedIcon: {
          url: "https://images.woosmap.com/starbucks-marker-selected.svg",
          scaledSize: {
            height: 50,
            width: 43,
          },
        },
      },
    },
  },
};

function getPhone({ properties }) {
  let _a;
  const phone =
    (_a = properties.contact) === null || _a === void 0 ? void 0 : _a.phone;
  return phone
    ? `<li id='store-phone'><span class='marker-image'></span><p><a class='text-black' href='tel:${phone}'>${phone}</a></p></li>`
    : "";
}

function getWebSite({ properties }) {
  let _a;
  const website =
    (_a = properties.contact) === null || _a === void 0 ? void 0 : _a.website;
  return website
    ? `<li id='store-website'><span class='marker-image'></span><a class='text-black' href='${website}' target='_blank'>More Details</a></li>`
    : "";
}

function getDistanceAndTime({ properties }) {
  const distanceLabel =
    (properties.distance_text || "") +
    (properties.duration_text ? ` (${properties.duration_text})` : "");
  return `<p class='summary-distance'>${distanceLabel}</p>`;
}

function formatAddress(properties) {
  return properties && properties.address
    ? `${properties.address.lines || ""}, ${properties.address.zipcode || ""} ${properties.address.city || ""}`
    : "";
}

function getAddress({ properties }) {
  const address = formatAddress(properties);
  return `
    <li id='store-address'>
      <span class='marker-image'></span>
      <div>
        <p>${address}</p>
        <p>${getDistanceAndTime({ properties })}</p>
      </div>
    </li>`;
}

function getSummaryAddress({ properties }) {
  const address = formatAddress(properties);
  return `<p class='summary-address'>${address}</p>`;
}

function getSummaryPhone({ properties }) {
  const phone =
    properties && properties.contact ? properties.contact.phone || "" : "";
  return `<p class='summary-address'>${phone}</p>`;
}

function getFullSchedule({ properties }) {
  const weeklyOpening = properties.weekly_opening;
  const dayLabels = {
    1: "Monday",
    2: "Tuesday",
    3: "Wednesday",
    4: "Thursday",
    5: "Friday",
    6: "Saturday",
    7: "Sunday",
  };

  if (!weeklyOpening) {
    return "";
  }

  const daysHoursHTMLTable = Object.keys(dayLabels)
    .map((day) => {
      let _a;
      const hours =
        ((_a = weeklyOpening[day]) === null || _a === void 0
          ? void 0
          : _a.hours) || [];
      let daysHours = "";

      if (hours.length === 0) {
        daysHours = "Closed";
      } else {
        const hoursStrings = hours.map((hour) => {
          if (hour["all-day"]) {
            return "24h/24";
          }
          return `${hour.start}-${hour.end}`;
        });

        daysHours = hoursStrings.join(", ");
      }
      return `<li><span class='day'>${dayLabels[day]}</span><span class="hours">${daysHours}</span></li>`;
    })
    .join("");
  return `<ul class='store-opening-hours-list'>${daysHoursHTMLTable}</ul>`;
}

function getHours(store) {
  return `<li id='store-hours'><span class='marker-image'></span>${getFullSchedule(store)}</li>`;
}

function getOpeningLabel({ properties }) {
  let _a, _b, _c;

  if (!properties.open) {
    return "";
  }

  let openLabel;

  if (properties.open.open_now) {
    openLabel = `Open now until ${(_a = properties.open.current_slice) === null || _a === void 0 ? void 0 : _a.end}`;
  } else {
    // @ts-ignore - TODO next_opening is wrongly spelled `nextOpening` in @types/woosmap.map
    openLabel = `Closed until ${convertTime(Date.parse(((_b = properties.open.next_opening) === null || _b === void 0 ? void 0 : _b.day) || "") / 1000)} at ${(_c = properties.open.next_opening) === null || _c === void 0 ? void 0 : _c.start}`;
  }
  return `<p class='summary-hours'>${openLabel}</p>`;
}

function convertTime(UNIX_timestamp) {
  const date = new Date(UNIX_timestamp * 1000);
  return date.toLocaleString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
  });
}

function createDetailedStoreCard(store) {
  const myCustomContent = document.createElement("ul");

  myCustomContent.id = "myCustomContentID";
  myCustomContent.innerHTML = [
    getWebSite(store),
    getAddress(store),
    getHours(store),
    getPhone(store),
  ].join("");
  return myCustomContent;
}

function createSummaryStoreCard(store) {
  const mySummaryContent = document.createElement("div");

  mySummaryContent.className = "store-summary";
  mySummaryContent.innerHTML = [
    `<p class='store-name'>${store.properties.name}</p>`,
    getSummaryAddress(store),
    getSummaryPhone(store),
    getOpeningLabel(store),
    getDistanceAndTime(store),
  ].join("");
  return mySummaryContent;
}

function isMobileDevice() {
  return window.innerWidth < 500;
}

function initStoreLocator() {
  const webapp = new window.WebApp("map", "YOUR_API_KEY");

  webapp.setFullStoreRenderer(createDetailedStoreCard);
  webapp.setSummaryStoreRenderer(createSummaryStoreCard);
  webapp.setConf(configLocator);
  webapp.setInitialStateToSelectedStore("12003");
  webapp.render(isMobileDevice());
}

initStoreLocator();

    
        /*
 * Always set the map height explicitly to define the size of the div element
 * that contains the map.
 */
#map {
  height: 100%;
}

/*
 * Optional: Makes the sample page fill the window.
 */
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
}

#myCustomContentID {
  bottom: 0;
  top: 0;
  left: 0;
  right: 0;
  position: absolute;
  padding: 0;
}
#myCustomContentID > li {
  display: flex;
  align-items: flex-start;
  list-style: none;
  min-height: 32px;
  line-height: 24px;
  padding-bottom: 10px;
  padding-left: 5px;
}
#myCustomContentID p {
  margin: 0;
}

.marker-image {
  height: 24px;
  width: 24px;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  flex: 0 0 20px;
  margin-right: 10px;
}

#store-hours .marker-image {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 -960 960 960' width='24'%3E%3Cpath d='m612-292 56-56-148-148v-184h-80v216l172 172ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Z'/%3E%3C/svg%3E");
}

#store-address .marker-image {
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 height=%2724%27 viewBox=%270 0 24 24%27 width=%2724%27%3E%3Cpath d=%27M0 0h24v24H0V0z%27 fill=%27none%27/%3E%3Cpath d=%27M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zM7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 2.88-2.88 7.19-5 9.88C9.92 16.21 7 11.85 7 9z%27/%3E%3Ccircle cx=%2712%27 cy=%279%27 r=%272.5%27/%3E%3C/svg%3E");
}

#store-website .marker-image {
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 height=%2724%27 viewBox=%270 0 24 24%27 width=%2724%27%3E%3Cpath d=%27M0 0h24v24H0V0z%27 fill=%27none%27/%3E%3Cpath d=%27M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z%27/%3E%3C/svg%3E");
}

#store-phone .marker-image {
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 height=%2724%27 viewBox=%270 0 24 24%27 width=%2724%27%3E%3Cpath d=%27M0 0h24v24H0V0z%27 fill=%27none%27/%3E%3Cpath d=%27M6.54 5c.06.89.21 1.76.45 2.59l-1.2 1.2c-.41-1.2-.67-2.47-.76-3.79h1.51m9.86 12.02c.85.24 1.72.39 2.6.45v1.49c-1.32-.09-2.59-.35-3.8-.75l1.2-1.19M7.5 3H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.49c0-.55-.45-1-1-1-1.24 0-2.45-.2-3.57-.57-.1-.04-.21-.05-.31-.05-.26 0-.51.1-.71.29l-2.2 2.2c-2.83-1.45-5.15-3.76-6.59-6.59l2.2-2.2c.28-.28.36-.67.25-1.02C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1z%27/%3E%3C/svg%3E");
}

.summary-address {
  opacity: 0.6;
}

.store-summary p {
  margin: 0;
}

.store-name {
  font-size: 1rem !important;
  margin-right: 20px;
}

.text-black {
  color: black;
  text-decoration: none;
}

.text-black:hover {
  color: black;
  text-decoration: none;
  cursor: pointer;
}

.store-opening-hours-list {
  display: flex;
  flex-direction: column;
  padding: 0;
  list-style: none;
  width: 100%;
}
.store-opening-hours-list > li {
  flex: 1;
  line-height: 24px;
  display: flex;
  padding-bottom: 5px;
  justify-content: space-between;
  max-width: 245px;
}

.padding-right {
  padding-right: 15px !important;
}


    
        <html>
  <head>
    <title>Store Locator Widget - Custom Renderer</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta charset="utf-8" />

    <script src="https://webapp.woosmap.com/webapp.js"></script>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>

    

Running the Sample Locally

Before you can run this sample on your local machine, you need to have Git and Node.js installed. If they’re not already installed, follow these instructions to get them set up.

Once you have Git and Node.js installed, you can run the sample by following these steps:

  1. Clone the repository and navigate to the directory of the sample.

  2. Install the necessary dependencies.

  3. Start running the sample.

Here are the commands you can use in your terminal to do this:

Shell
        git clone -b sample/store-locator-widget-custom-renderer https://github.com/woosmap/js-samples.git
cd js-samples
npm i
npm start

    

You can experiment with other samples by switching to any branch that starts with the pattern sample/SAMPLE_NAME.

Shell
        git checkout sample/SAMPLE_NAME
npm i
npm start

    
Was this article helpful?
Have more questions? Submit a request