Mappedin Logo

Developer

Mappedin Documentation

Web SDK: Creating a list of locations sorted by category

Web SDK: Creating a list of locations sorted by category

Basic Example

A common user interface element of a Mappedin Web SDK powered app is a list of a venue's locations sorted by their associated categories.

For venues with simple category structures, it may be sufficient to display each category with their associated locations as a flat list.

We start by adding categories to our things object in the venue configuration object so that the category data is fetched when the map loads. For now, we only require the category names. We also define a utility function for sorting objects alphabetically by their name property. Following that we can sort the list of categories by name, sort each categories location by name, and add those categories and locations to a list in our app's sidebar.

Sample Code

<html>
<head>
<script src="https://d1p5cqqchvbqmy.cloudfront.net/websdk/v1.71.12/mappedin.js"></script>
</head>
<body>
<div class="container">
<div id="sidebar">
<h2>Locations:</h2>
</div>
<div class="map-container">
<div id="mapView" />
</div>
</div>
</body>
</html>
body {
margin: 0;
padding: 0;
}
.container {
display: grid;
grid-template-columns: minmax(200px, 25%) 1fr;
}
.map-container {
position: relative;
}
#sidebar {
max-height: 100vh;
margin: 0;
padding: 0 12px;
overflow-y: scroll;
border-right: 1px dashed grey;
}
const div = document.getElementById("mapView");
let mapview, venue;
const options = {
mapview: {
antialias: "AUTO",
mode: Mappedin.modes.TEST,
onFirstMapLoaded: () => {
populateLocationsList();
},
},
venue: {
clientId: "<MAPPEDIN_CLIENT_ID>",
clientSecret: "<MAPPEDIN_CLIENT_SECRET>",
perspective: "Website", //pick the perspective you would like to load
things: {
//fetch some data
venue: ["slug", "name"],
categories: ["name"],
maps: ["name", "elevation", "shortName"],
},
venue: "mappedin-demo-mall",
},
};
function sortByName(arr) {
return arr.sort((a, b) =>
a.name.toUpperCase() > b.name.toUpperCase() ? 1 : -1
);
}
function populateLocationsList() {
const sortedCategories = sortByName(venue.categories);
sortedCategories.forEach((category) => {
const heading = document.createElement("h3");
heading.appendChild(document.createTextNode(category.name));
const sidebarElement = document.getElementById("sidebar");
sidebarElement.appendChild(heading);
const ulElement = document.createElement("ul");
sortByName(category.locations).forEach((location) => {
const listItem = document.createElement("li");
listItem.appendChild(document.createTextNode(location.name));
ulElement.appendChild(listItem);
});
sidebarElement.appendChild(ulElement);
});
}
Mappedin.initialize(options, div).then((data) => {
mapview = data.mapview;
venue = data.venue;
});

The Result

Advanced Category Structures

Mappedin's CMS allows clients to create complex data structures involving categories. Locations may belong to multiple categories, and categories have a parents field that support defining one or more parent categories that the respective category is also a member of. This allows for a very flexible way to define your data, but also introduces some additional considerations when determining how to display a list of locations.

Since a venue may have dozens of categories and subcategories containing the same locations in a nested structure, one way to simplify this data is to show top level category headings only, while listing both their direct child locations and the locations associated with any subcategories. Note in the following example that the Footwear and Accessories categories are not shown, and that all locations that were present in either category are now visible beneath the Clothing category.

Sample Code

const div = document.getElementById("mapView");
let mapview, venue;
const options = {
mapview: {
antialias: "AUTO",
mode: Mappedin.modes.TEST,
onFirstMapLoaded: () => {
populateLocationsList();
},
},
venue: {
clientId: "<MAPPEDIN_CLIENT_ID>",
clientSecret: "<MAPPEDIN_CLIENT_SECRET>",
perspective: "Website", //pick the perspective you would like to load
things: {
//fetch some data
venue: ["slug", "name"],
categories: ["name"],
maps: ["name", "elevation", "shortName"],
},
venue: "mappedin-demo-mall",
},
};
function sortByName(arr) {
return arr.sort((a, b) =>
a.name.toUpperCase() > b.name.toUpperCase() ? 1 : -1
);
}
function getCategoricalLocations(coalesceWithParents = true) {
// coalesceWithParents =>
// if true, return the top-level categories sorted by name with their locations array
// containing the sorted locations of all subcategories.
// if false, return all categories sorted by name with their associated sorted locations
const { locations: allLocations, categories: allCategories } = venue;
if (!coalesceWithParents) {
//Filter locations to ones with polygons and sort categories and locations alphabetically
const result = sortByName(
allCategories
.filter(
(item) => Array.isArray(item.locations) && item.locations.length > 0
)
.map((category) => {
return {
...category,
locations: category.locations.filter(
(location) => location.polygons?.length > 0
),
};
})
);
return result;
} else if (Array.isArray(allLocations) && Array.isArray(allCategories)) {
//Create an object map of categoryId: category
const categoryMap = allCategories.reduce((acc, category) => {
acc[category.id] = {
...category,
locations: [
...category.locations.filter(
(location) => location?.polygons?.length > 0
),
],
};
return acc;
}, {});
//Find the IDs of all child categories
const childCategoryList = allCategories
.filter((category) => category?.parents?.length > 0)
.map((category) => category.id);
const getParentCategories = (categoryID) => {
//Given a category ID, return a list of the top level parents the category relates to
const flatten = (list, accumulator = []) => {
Array.from(list).forEach((item) => {
if (Array.isArray(item)) {
flatten(item, accumulator);
} else if (item.id) {
accumulator.push(item);
}
});
return accumulator;
};
if (categoryMap[categoryID]?.parents?.length == 0) {
return [categoryMap[categoryID]];
} else {
return flatten(
categoryMap[categoryID].parents.map((id) => getParentCategories(id))
);
}
};
//Add the child categories' locations to the parent category locations, avoid adding duplicate locations
childCategoryList.forEach((categoryID) => {
console.log("start", categoryID);
const childCategory = categoryMap[categoryID];
const parentCategories = getParentCategories(categoryID);
parentCategories.forEach((parentCategory) => {
parentCategory.locations = sortByName([
...parentCategory.locations,
...childCategory.locations.filter(
(location) =>
!parentCategory.locations.some(
(existingLocation) =>
existingLocation &&
existingLocation?.id === location.id &&
existingLocation?.polygons?.length > 0
)
),
]);
});
});
//Filter for exclusively parent categories from our object map
const parents = Object.keys(categoryMap)
.filter((categoryID) => {
return !categoryMap[categoryID]?.parents?.length > 0;
})
.map((categoryID) => categoryMap[categoryID]);
//Return sorted list of categories
return sortByName(parents);
} else {
console.error("Something went wrong generating categorical locations");
return {};
}
}
function populateLocationsList() {
const sortedCategories = getCategoricalLocations();
sortedCategories.forEach((category) => {
const heading = document.createElement("h3");
heading.appendChild(document.createTextNode(category.name));
const sidebarElement = document.getElementById("sidebar");
sidebarElement.appendChild(heading);
const ulElement = document.createElement("ul");
sortByName(category.locations).forEach((location) => {
const listItem = document.createElement("li");
listItem.appendChild(document.createTextNode(location.name));
ulElement.appendChild(listItem);
});
sidebarElement.appendChild(ulElement);
});
}
Mappedin.initialize(options, div).then((data) => {
mapview = data.mapview;
venue = data.venue;
});

The Result

Search Our Docs

Sign Up

© 2020 Copyright Mappedin, All Rights Reserved. View our Privacy Policy