Allow a user to create a check-in.
Remove a check-in, if the acting user is the owner of the check-in.
${value.text.secondary}
`;
}
async function selectItem({ target }) {
if (target.tagName === "BUTTON") {
const link = target.dataset.object;
const addressDetail = await fetchAddressDetails(link);
const { location = {} } = addressDetail;
const {
address = "",
country = "",
postcode = "",
locality = "",
region = "",
} = location;
addressInput.value = address;
address2Input.value = "";
countryInput.value = country;
postcodeInput.value = postcode;
cityInput.value = locality;
regionInput.value = region;
// generate new session token after a complete search
sessionToken = generateRandomSessionToken();
address2Input && address2Input.focus();
dropDownField.style.display = "none";
}
}
async function fetchAddressDetails(link) {
try {
const results = await fetch(`https://api.foursquare.com${link}`, {
method: "get",
headers: new Headers({
Accept: "application/json",
Authorization: fsqAPIToken,
}),
});
const data = await results.json();
return data;
} catch (err) {
logError(err);
}
}
function highlightedNameElement(textObject) {
if (!textObject) return "";
const { primary, highlight } = textObject;
if (highlight && highlight.length) {
let beginning = 0;
let hightligtedWords = "";
for (let i = 0; i < highlight.length; i++) {
const { start, length } = highlight[i];
hightligtedWords += primary.substr(beginning, start - beginning);
hightligtedWords += "
" + primary.substr(start, length) + "";
beginning = start + length;
}
hightligtedWords += primary.substr(beginning);
return hightligtedWords;
}
return primary;
}
function debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
}
}
localAddressAutoFillJs();
}
if (state && state.params && state.params.slug === "local-search-map") {
function loadLocalMapSearchJs() {
mapboxgl.accessToken =
"pk.eyJ1IjoiZm91cnNxdWFyZSIsImEiOiJjbDNqNXdrN20wN3JtM2JvMWFqZGxoaGljIn0.uSxJ2t7E96TrBFsn3cXT_g";
const fsqAPIToken = "fsq3bgqdcpLAJFkodk8gisc2F+NenA7gK/zI97A9nKQAXIw=";
let userLat = 40.7128;
let userLng = -74.006;
let sessionToken = generateRandomSessionToken();
const inputField = document.getElementById("explorer-search");
const dropDownField = document.getElementById("explorer-dropdown");
const ulField = document.getElementById("explorer-suggestions");
const errorField = document.getElementById("explorer-error");
const notFoundField = document.getElementById("explorer-not-found");
const onChangeAutoComplete = debounce(changeAutoComplete);
inputField.addEventListener("input", onChangeAutoComplete);
ulField.addEventListener("click", selectItem);
function success(pos) {
const { latitude, longitude } = pos.coords;
userLat = latitude;
userLng = longitude;
flyToLocation(userLat, userLng);
}
function logError(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
}
navigator.geolocation.getCurrentPosition(success, logError, {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0,
});
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/light-v10",
center: [userLng, userLat],
zoom: 12,
});
map.addControl(new mapboxgl.GeolocateControl());
map.addControl(new mapboxgl.NavigationControl());
let currentMarker;
/* Generate a random string with 32 characters.
Session Token is a user-generated token to identify a session for billing purposes.
Learn more about session tokens.
https://docs.foursquare.com/reference/session-tokens
*/
function generateRandomSessionToken(length = 32) {
let result = "";
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (let i = 0; i < length; i++) {
result += characters[Math.floor(Math.random() * characters.length)];
}
return result;
}
let isFetching = false;
async function changeAutoComplete({ target }) {
const { value: inputSearch = "" } = target;
ulField.innerHTML = "";
notFoundField.style.display = "none";
errorField.style.display = "none";
if (inputSearch.length && !isFetching) {
try {
isFetching = true;
const results = await autoComplete(inputSearch);
if (results && results.length) {
results.forEach((value) => {
addItem(value);
});
} else {
notFoundField.innerHTML = `Foursquare can't
find ${inputSearch}. Make sure your search is spelled correctly.
Don't see the place you're looking for?.`;
notFoundField.style.display = "block";
}
} catch (err) {
errorField.style.display = "block";
logError(err);
} finally {
isFetching = false;
dropDownField.style.display = "block";
}
} else {
dropDownField.style.display = "none";
}
}
async function autoComplete(query) {
const { lng, lat } = map.getCenter();
userLat = lat;
userLng = lng;
try {
const searchParams = new URLSearchParams({
query,
types: "place",
ll: `${userLat},${userLng}`,
radius: 50000,
session_token: sessionToken,
}).toString();
const searchResults = await fetch(
`https://api.foursquare.com/v3/autocomplete?${searchParams}`,
{
method: "get",
headers: new Headers({
Accept: "application/json",
Authorization: fsqAPIToken,
}),
}
);
const data = await searchResults.json();
return data.results;
} catch (error) {
throw error;
}
}
function addItem(value) {
const placeDetail = value[value.type];
if (!placeDetail || !placeDetail.geocodes || !placeDetail.geocodes.main)
return;
const { latitude, longitude } = placeDetail.geocodes.main;
const fsqId = placeDetail.fsq_id;
const dataObject = JSON.stringify({ latitude, longitude, fsqId });
ulField.innerHTML += `
${highlightedNameElement(value.text)}
${value.text.secondary}
`;
}
async function selectItem({ target }) {
if (target.tagName === "LI") {
const valueObject = JSON.parse(target.dataset.object);
const { latitude, longitude, fsqId } = valueObject;
const placeDetail = await fetchPlacesDetails(fsqId);
addMarkerAndPopup(latitude, longitude, placeDetail);
flyToLocation(latitude, longitude);
// generate new session token after a complete search
sessionToken = generateRandomSessionToken();
const name = target.dataset.name;
inputField.value = target.children[0].textContent;
dropDownField.style.display = "none";
}
}
async function fetchPlacesDetails(fsqId) {
try {
const searchParams = new URLSearchParams({
fields: "fsq_id,name,geocodes,location,photos,rating",
session_token: sessionToken,
}).toString();
const results = await fetch(
`https://api.foursquare.com/v3/places/${fsqId}?${searchParams}`,
{
method: "get",
headers: new Headers({
Accept: "application/json",
Authorization: fsqAPIToken,
}),
}
);
const data = await results.json();
return data;
} catch (err) {
logError(err);
}
}
function createPopup(placeDetail) {
const { location = {}, name = "", photos = [], rating } = placeDetail;
let photoUrl = "https://files.readme.io/c163d6e-placeholder.svg";
if (photos.length && photos[0]) {
photoUrl = `${photos[0].prefix}56${photos[0].suffix}`;
}
const popupHTML = ``;
const markerHeight = 35;
const markerRadius = 14;
const linearOffset = 8;
const verticalOffset = 8;
const popupOffsets = {
top: [0, verticalOffset],
"top-left": [0, verticalOffset],
"top-right": [0, verticalOffset],
bottom: [0, -(markerHeight + verticalOffset)],
"bottom-left": [
0,
(markerHeight + verticalOffset - markerRadius + linearOffset) * -1,
],
"bottom-right": [
0,
(markerHeight + verticalOffset - markerRadius + linearOffset) * -1,
],
left: [
markerRadius + linearOffset,
(markerHeight - markerRadius) * -1,
],
right: [
-(markerRadius + linearOffset),
(markerHeight - markerRadius) * -1,
],
};
return new mapboxgl.Popup({
offset: popupOffsets,
closeButton: false,
}).setHTML(popupHTML);
}
function addMarkerAndPopup(lat, lng, placeDetail) {
if (currentMarker) currentMarker.remove();
currentMarker = new mapboxgl.Marker({
color: "#3333FF",
})
.setLngLat([lng, lat])
.setPopup(createPopup(placeDetail))
.addTo(map);
currentMarker.togglePopup();
}
function flyToLocation(lat, lng) {
map.flyTo({
center: [lng, lat],
});
}
function highlightedNameElement(textObject) {
if (!textObject) return "";
const { primary, highlight } = textObject;
if (highlight && highlight.length) {
let beginning = 0;
let hightligtedWords = "";
for (let i = 0; i < highlight.length; i++) {
const { start, length } = highlight[i];
hightligtedWords += primary.substr(beginning, start - beginning);
hightligtedWords += "
" + primary.substr(start, length) + "";
beginning = start + length;
}
hightligtedWords += primary.substr(beginning);
return hightligtedWords;
}
return primary;
}
function debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
}
}
loadLocalMapSearchJs();
}
});
$(window).on("pageLoad", function (e, state) {
const openSearchPopup = () => {
const headerSearch = document.getElementById("hub-search-results");
if (headerSearch) {
headerSearch.classList.add("hub-search-results-active");
document
.getElementsByClassName("Input Input_md SearchBox-InputUQZAW9QXMe-c")[0]
?.focus();
}
};
const triggerSearchButton = document.getElementsByClassName(
"landing-page-trigger-search-button"
)[0];
if (triggerSearchButton) {
triggerSearchButton.addEventListener("click", openSearchPopup);
}
});
// START: Add Segment Tracking to Try It button
// TODO: Verify this runs on route changes, so button gets instrumented on every page
$(window).on("load", function (e, state) {
var tryItBtn = document.querySelector(".rm-TryIt");
// in case the button has not yet renedered
if (tryItBtn === undefined) return;
tryItBtn.addEventListener("click", () => {
console.log("Try It button Clicked (project)");
analytics.track("Try It button Clicked");
});
});
// END: Add Segment Tracking to Try It button
// CUSTOM LAUNCH TOP NAV CODE - README
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
document.querySelectorAll('.Header-leftADQdGVqx1wqU a[href]').forEach(a => {
a.addEventListener('click', e => {
e.preventDefault();
window.location.href = a.href;
});
});
}, 100);
});
// CUSTOM LAUNCH SIDEBAR CODE (FINAL project: inject Places first, then Users under Welcome)
// Runs after a short delay to avoid flicker or unwanted auto-expansion.
document.addEventListener("DOMContentLoaded", function () {
// Delay entire injection by 1 second
setTimeout(() => {
(async function() {
// Normalize path (strip trailing slash)
const path = window.location.pathname.replace(/\/$/, '');
// Only act on the FINAL project paths
if (!path.startsWith('/developer/reference/')) {
return;
}
// Pages to pull from: Places API overview and Users API overview
// **Places first**, then Users
const toInject = [
{
otherPageUrl: '/fsq-developers-places/reference/places-api-overview',
targetSectionText: 'Places API'
},
{
otherPageUrl: '/fsq-developers-users/reference/users-api-overview',
targetSectionText: 'Users API'
}
];
// If versioned (e.g. /v2023-05-01/ in URL), insert version segment into otherPageUrl
const versionMatch = window.location.pathname.match(/\/(v\d{4}-\d{2}-\d{2})\//);
function applyVersion(url) {
if (versionMatch) {
const version = versionMatch[1];
return url.replace('/reference/', `/${version}/reference/`);
}
return url;
}
// Fetch & cache a single section's HTML from another page
async function fetchSectionHtml(otherPageUrl, targetSectionText) {
const versionedUrl = applyVersion(otherPageUrl);
const cacheKey = `crossSection:${versionedUrl}:${targetSectionText}`;
const cached = sessionStorage.getItem(cacheKey);
if (cached) {
return cached;
}
try {
const resp = await fetch(versionedUrl, { credentials: 'same-origin' });
if (!resp.ok) {
console.warn('[CrossInject] Fetch failed', resp.status, versionedUrl);
return null;
}
const htmlText = await resp.text();
// Parse to DOM
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, 'text/html');
// Find the sidebar section
const sidebarSelector = '.rm-Sidebar';
const otherSidebar = doc.querySelector(sidebarSelector);
if (!otherSidebar) {
console.warn('[CrossInject] Sidebar not found for', versionedUrl);
return null;
}
// Locate the
whose includes targetSectionText
let foundSection = null;
otherSidebar.querySelectorAll('section').forEach(sec => {
if (foundSection) return;
const h2 = sec.querySelector('h2');
if (h2 && h2.textContent.trim().includes(targetSectionText)) {
foundSection = sec;
}
});
if (!foundSection) {
console.warn('[CrossInject] Section not found:', targetSectionText, 'in', versionedUrl);
return null;
}
const sectionHtml = foundSection.outerHTML;
try {
sessionStorage.setItem(cacheKey, sectionHtml);
} catch (_) {}
return sectionHtml;
} catch (err) {
console.error('[CrossInject] Error fetching/parsing:', err);
return null;
}
}
// Wait until the current page's sidebar is present
function whenSidebarReady(fn) {
const interval = setInterval(() => {
const sidebarNav = document.querySelector('.rm-Sidebar');
if (sidebarNav) {
clearInterval(interval);
fn(sidebarNav);
}
}, 100);
// Stop polling after 5s
setTimeout(() => clearInterval(interval), 5000);
}
// Collapse helper for a section element
function collapseSection(sec) {
const h2 = sec.querySelector('h2');
const ul = sec.querySelector('ul');
if (h2) {
h2.classList.remove('section-expanded');
h2.classList.add('section-collapsed');
h2.setAttribute('aria-expanded', 'false');
}
if (ul) {
ul.classList.add('section-collapsed');
ul.classList.remove('section-list-expanded');
}
}
// Expand helper
function expandSection(sec) {
const h2 = sec.querySelector('h2');
const ul = sec.querySelector('ul');
if (h2) {
h2.classList.remove('section-collapsed');
h2.classList.add('section-expanded');
h2.setAttribute('aria-expanded', 'true');
}
if (ul) {
ul.classList.remove('section-collapsed');
ul.classList.add('section-list-expanded');
}
}
// Fetch all needed sections in parallel
const results = await Promise.all(
toInject.map(item =>
fetchSectionHtml(item.otherPageUrl, item.targetSectionText)
.then(html => ({ ...item, sectionHtml: html }))
)
);
const validSections = results.filter(r => r.sectionHtml);
if (validSections.length === 0) {
return;
}
// Once our sidebar is ready, inject under "Welcome"
whenSidebarReady(sidebarNav => {
const contentDiv = sidebarNav.querySelector('.hub-sidebar-content');
if (!contentDiv) {
console.warn('[CrossInject] .hub-sidebar-content not found; abort insertion.');
return;
}
// Find "Welcome" section
const welcomeSection = Array.from(contentDiv.querySelectorAll('section')).find(sec => {
const h2 = sec.querySelector('h2');
return h2 && h2.textContent.trim() === 'Welcome';
});
let insertAfter = welcomeSection;
validSections.forEach(({ targetSectionText, sectionHtml }) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = sectionHtml;
const originalSectionNode = wrapper.firstElementChild;
if (!originalSectionNode) {
console.warn('[CrossInject] No section node for', targetSectionText);
return;
}
const sectionClone = originalSectionNode.cloneNode(true);
// Adapt heading & toggle as needed:
const injectedH2 = sectionClone.querySelector('h2');
const injectedUL = sectionClone.querySelector('ul');
if (injectedH2 && injectedUL) {
// Copy classes from an existing template if present
const templateH2 = sidebarNav.querySelector('.hub-sidebar-content section h2');
if (templateH2) {
injectedH2.className = templateH2.className;
} else {
injectedH2.classList.add('Sidebar-headingTRQyOa2pk0gh', 'rm-Sidebar-heading');
}
injectedH2.setAttribute('tabindex', '0');
// Remove existing chevrons, clone from template or fallback
Array.from(injectedH2.querySelectorAll('.icon-chevron')).forEach(el => el.remove());
if (templateH2) {
templateH2.querySelectorAll('.icon-chevron').forEach(iconSpan => {
injectedH2.appendChild(iconSpan.cloneNode(true));
});
} else {
const fallbackSpan = document.createElement('span');
fallbackSpan.className = 'icon-chevron';
injectedH2.appendChild(fallbackSpan);
}
// Initially collapsed
collapseSection(sectionClone);
// Toggle handler
const toggleFn = () => {
const isCollapsed = injectedUL.classList.contains('section-collapsed');
if (isCollapsed) {
expandSection(sectionClone);
} else {
collapseSection(sectionClone);
}
};
injectedH2.style.cursor = 'pointer';
injectedH2.addEventListener('click', toggleFn);
injectedH2.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
e.preventDefault();
toggleFn();
}
});
}
// Nested toggles
sectionClone.querySelectorAll('a.Sidebar-link_parent').forEach(aParent => {
let btn = aParent.querySelector('button');
if (!btn && aParent.nextElementSibling && aParent.nextElementSibling.tagName === 'BUTTON') {
btn = aParent.nextElementSibling;
}
let subUl = null;
if (btn) {
const maybeUl = btn.parentElement.nextElementSibling;
if (maybeUl && maybeUl.classList.contains('subpages')) subUl = maybeUl;
}
if (!subUl) {
const parentLi = aParent.closest('li');
if (parentLi) {
const maybe = parentLi.querySelector('ul.subpages');
if (maybe) subUl = maybe;
}
}
if (btn && subUl) {
// collapsed by default
btn.setAttribute('aria-expanded', 'false');
subUl.classList.add('section-collapsed');
subUl.classList.remove('section-list-expanded');
btn.style.cursor = 'pointer';
btn.addEventListener('click', e => {
e.preventDefault();
const expanded = btn.getAttribute('aria-expanded') === 'true';
if (expanded) {
btn.setAttribute('aria-expanded', 'false');
subUl.classList.add('section-collapsed');
subUl.classList.remove('section-list-expanded');
} else {
btn.setAttribute('aria-expanded', 'true');
subUl.classList.remove('section-collapsed');
subUl.classList.add('section-list-expanded');
}
});
}
});
// Insert right after Welcome (Places first, then Users)
if (insertAfter && insertAfter.parentElement === contentDiv) {
insertAfter.insertAdjacentElement('afterend', sectionClone);
insertAfter = sectionClone;
} else {
contentDiv.appendChild(sectionClone);
}
console.log(`[CrossInject] Injected "${targetSectionText}" after Welcome.`);
});
// After injecting both, auto-expand whichever matches current path
function normalizeHref(href) {
try {
const url = new URL(href, window.location.origin);
return url.pathname.replace(/\/$/, '');
} catch {
return href.replace(/\/$/, '');
}
}
const normPath = window.location.pathname.replace(/\/$/, '');
// Clear any previous active marks:
contentDiv.querySelectorAll('a[aria-current]').forEach(a => {
a.removeAttribute('aria-current');
a.classList.remove('active');
});
// Find matches among injected links:
const allLinks = Array.from(contentDiv.querySelectorAll('a'));
allLinks.forEach(a => {
const href = a.getAttribute('href');
if (!href) return;
if (normalizeHref(href) === normPath) {
// mark active
a.setAttribute('aria-current', 'page');
a.classList.add('active');
// expand its parent section
let sec = a.closest('section');
if (sec) expandSection(sec);
// expand ancestors
let anc = a.parentElement;
while (anc && anc !== contentDiv) {
if (anc.tagName === 'UL' && anc.classList.contains('subpages')) {
anc.classList.remove('section-collapsed');
anc.classList.add('section-list-expanded');
const parentLi = anc.closest('li');
if (parentLi) {
const btn = parentLi.querySelector('button[aria-expanded]');
if (btn) btn.setAttribute('aria-expanded', 'true');
}
}
if (anc.tagName === 'SECTION') {
expandSection(anc);
}
anc = anc.parentElement;
}
}
});
// If none matched exactly, all remain collapsed.
});
})();
}, 600);
});