Skip to content

Instantly share code, notes, and snippets.

@rhoover
Created April 4, 2025 15:43
Show Gist options
  • Save rhoover/313d97bb719655370c256ecdc2325e88 to your computer and use it in GitHub Desktop.
Save rhoover/313d97bb719655370c256ecdc2325e88 to your computer and use it in GitHub Desktop.
web-component-first-effort
<!DOCTYPE html>
<html>
<head>
<title>Toggle Buttons</title>
<style type="text/css">
body {
margin: 1em auto;
max-width: 60em;
width: 88%;
}
</style>
<link rel="stylesheet" type="text/css" href="toggle-test.css">
</head>
<body>
<h1>Toggle Buttons</h1>
<main>
<buttons-row>
<div class="detail-tabs-row" artisan-latitude="42.853184" artisan-longitude="-72.793594">
<button class="detail-tabs-item" source-button="dining">Local Dining</button>
<button class="detail-tabs-item" source-button="shopping">Local Shopping</button>
<button class="detail-tabs-item" source-button="weather">Local Weather</button>
</div>
<!-- </section> -->
</buttons-row>
<panels-row>
<section class="detail-panel" target-panel="dining">
<div class="detail-panel-header">
<p>Local Dining By:</p>
<a href="https://www.yelp.com" target="_blank" rel="noreferrer noopener"><img src="/img/yelp_fullcolor.png" alt="yelp icon" class="detail-panel-header-image" width="100" height="40"></a>
</div>
</section>
<section class="detail-panel" target-panel="shopping">
<div class="detail-panel-header">
<p>Local Shopping By:</p>
<a href="https://www.yelp.com" target="_blank" rel="noreferrer noopener"><img src="/img/yelp_fullcolor.png" alt="yelp icon" class="detail-panel-header-image" width="100" height="40"></a>
</div>
</section>
<section class="detail-panel" target-panel="weather">
<div class="detail-panel-header">
<p>Local Weather By:</p>
<a href="https://openweathermap.org/" target="_blank" rel="noreferrer noopener"><img src="/img/OpenWeather-Logo-Small.png" alt="open weather map icon" class="detail-panel-header-image" width="75" height="33"></a>
</div>
<div class="detail-panel-weather-current"></div>
<div class="detail-panel-weather-fiveday"></div>
</section>
</panels-row>
</main>
<script src="toggle-test.js"></script>
</body>
</html>
(() => {
'use strict';
customElements.define('buttons-row', class extends HTMLElement {
constructor() {
super();
// initialize the things
this.buttonRow = this.querySelector('.detail-tabs-row');
this.lat = this.buttonRow.getAttribute('artisan-latitude');
this.lon = this.buttonRow.getAttribute('artisan-longitude');
this.buttonRowItems = this.buttonRow.querySelectorAll('.detail-tabs-item');
}; // end constructor()
// deal with any kind of 'on...' event
// referenced below
handleEvent(event) {
this[`on${event.type}`](event);
}; // end handleEvent()
connectedCallback() {
this.addEventListener('click', this);
// create data about what was clicked
this.addEventListener('click', (event) => {
const sourceButtonClicked = {
sourceData: event.target.getAttribute('source-button'),
latitude: this.lat,
longitude: this.lon
};
// communicate with panels web component
this.dispatchEvent(new CustomEvent('toggle-panels', {
detail: sourceButtonClicked,
bubbles: true,
composed: true
}));
});
}; // end connectedCallback()
// different 'on...' events, let's go with 'onclick' as the most common
onclick(event) {
// ignore currently active tab
if (event.target.matches('.detail-tabs-item-active')) return;
this.toggleButtonClasses(event.target);
}; // end onclick()
// self explanatory
toggleButtonClasses (buttonClicked) {
this.buttonRowItems.forEach((button) => {
if (button.classList.contains('detail-tabs-item-active')) {
button.classList.remove('detail-tabs-item-active');
};
}); // end forEach
// then add new active class to clicked tab
buttonClicked.classList.add('detail-tabs-item-active');
}; // end toggleButtonRowItems
}); // end customElements.define('buttons-row')
customElements.define('panels-row', class extends HTMLElement {
constructor() {
super();
this.panels = this.querySelectorAll('.detail-panel');
}; // end constructor()
connectedCallback() {
this.addEventListener('click', this);
// have to listen on the whole document to pick up this custom event
document.addEventListener('toggle-panels', (event) => {
// build formdata to be sent to server for fetching weather and yelp data
let geoData = new FormData();
geoData.append('latitude', event.detail.latitude);
geoData.append('longitude', event.detail.longitude);
// send appropriate data for appropriate panel
switch (event.detail.sourceData) {
case 'weather':
this.weatherPanelInit(geoData);
break;
case 'dining':
this.yelpPanelsInit(geoData, 'restaurants');
break;
case 'shopping':
this.yelpPanelsInit(geoData, 'shopping');
break;
default:
};
// self-explanatory
this.togglePanelClasses(event.detail);
});
}; // end connectedCallback()
// self-explanatory
togglePanelClasses (targetPanelData) {
this.panels.forEach((panel) =>{
if (panel.classList.contains('detail-panel-active')) {
panel.classList.remove('detail-panel-active');
};
if (panel.getAttribute('target-panel') === targetPanelData.sourceData) {
panel.classList.add('detail-panel-active');
};
}); // end forEach
}; // end togglePanelClasses
weatherPanelInit (dataForServer) {
// fetch OpenWeather data
// it wasn't until I *stopped* using the headers{} option did the fetch() start to work
fetch('https://mydroplet/server/openweather.php', {
method: 'POST',
body: dataForServer
})
.then(res => {
return res.text();
})
.then(res => {
let weatherResults = JSON.parse(res);
this.weatherPanelRender(weatherResults);
}); // end .then(res)
}; // end weatherPanelInit()
weatherPanelRender (weatherResults) {
let currentContentTarget = this.querySelector('.detail-panel-weather-current');
let fivedayContentTarget = this.querySelector('.detail-panel-weather-fiveday');
let currentContent = "";
let fivedayContent = "";
//get next five days data, all of it
let daily = weatherResults.daily.slice(1, 6);
//initialize days of week, as .dt returns an integer
let dayOfWeek = {0:"Sun",1:"Mon",2:"Tue",3:"Wed",4:"Thu",5: "Fri",6: "Sat"};
//initialize five-day forecast array
let fiveDayForecast = [];
// let'er rip to create the five-day forecast array
for (let i = 0; i < daily.length; i++) {
let dailyWeatherObj = {};
let dayNum = new Date(daily[i].dt * 1000).getDay();
dailyWeatherObj['minTemp'] = Math.round(daily[i].temp.min);
dailyWeatherObj['maxTemp'] = Math.round(daily[i].temp.max);
dailyWeatherObj['icon'] = daily[i].weather[0].icon;
dailyWeatherObj['desc'] = daily[i].weather[0].main;
dailyWeatherObj['day'] = dayOfWeek[dayNum];
fiveDayForecast.push(dailyWeatherObj);
};
// current weather render from data build
currentContent = `
<h5 class="detail-panel-weather-current-header">Current Weather:</h5>
<img src="/img/openWeather/${weatherResults.current.weather[0].icon}.png" width="50" height="50">
<p class="desc">${weatherResults.current.weather[0].description.replace(/\b\w/g, (c) => c.toUpperCase())}</p>
<p class="current">${Math.round(weatherResults.daily[0].temp.day) + '\u00B0'}</p>
<p class="min">Min. Temp:<br />${Math.round(weatherResults.daily[0].temp.min) + '\u00B0'}</p>
<p class="max">Max. Temp:<br />${Math.round(weatherResults.daily[0].temp.max) + '\u00B0'}</p>
<p class="precip">Precip Chance:<br />${weatherResults.daily[0].pop * 100 + '\u0025'}</p>
`; // end current content
currentContentTarget.innerHTML = currentContent;
// because the five day forcast is an array, to get the data into a template literal
// I must run a function for each div in the card: days, icons, descriptions, temps
// each output is called in the literal
function fiveDays() {
let days = "";
fiveDayForecast.forEach((i) => {
days += `<p>${i.day}</p>`;
});
return days;
};
function fiveIcons() {
let icons = "";
fiveDayForecast.forEach((i) => {
icons += `<img src="/img/openWeather/${i.icon}.png" width="50" height="50">`;
});
return icons;
};
function fiveDescriptions() {
let desc = "";
fiveDayForecast.forEach((i) => {
desc += `<p>${i.desc}</p>`;
});
return desc;
};
function fiveTemps() {
let temps = "";
fiveDayForecast.forEach((i) => {
temps += `<p>Low: ${i.minTemp + '\u00B0'} Hi: ${i.maxTemp + '\u00B0'}</p>`;
});
return temps;
};
// five-day forecast weather display from data build
// super simple as all the legwork has been done just above
fivedayContent = `
<h5 class="detail-panel-weather-fiveday-header">5 Day Forecast</h5>
<div class="detail-panel-weather-fiveday-days">${fiveDays()}</div>
<div class="detail-panel-weather-fiveday-icons">${fiveIcons()}</div>
<div class="detail-panel-weather-fiveday-desc">${fiveDescriptions()}</div>
<div class="detail-panel-weather-fiveday-temps">${fiveTemps()}</div>
`;
fivedayContentTarget.innerHTML = fivedayContent;
}; // end weatherPanelRender()
yelpPanelsInit (dataForServer, searchTerm) {
// add searchterm to formdata because I have to for request to yelp api from server
dataForServer.append('term', searchTerm);
// fetch Yelp data
// it wasn't until I *stopped* using the headers{} option did the fetch() start to work
fetch('https://mydroplet/server/yelp.php', {
method: 'POST',
body: dataForServer
})
.then(res => {
return res.text();
})
.then(res => {
let yelpResults = JSON.parse(res).businesses;
this.yelpPanelsRender(yelpResults, searchTerm);
});
}; // end yelpPanelsInit
yelpPanelsRender (yelpResults, searchTerm) {
// where to stuff incoming data into the DOM
if (searchTerm === 'restaurants') {
var yelpDataTarget = this.querySelector('[target-panel="dining"]');
} else { // else it's shopping
var yelpDataTarget = this.querySelector('[target-panel="shopping"]');
};
// initialize
let yelpContent = "";
// the response is a single array, each object is a business to display, thus the loop encompassing everything
yelpResults.forEach((yelpItem) => {
//if we're to show the review stars, must first assign a string to the number rating
// yelp is now doing ratings like "3.25", so I'm just taking the low whole number
let ratingNumber = yelpItem.rating;
let ratingString = "";
switch (true) {
case ratingNumber >= 5:
ratingString = "five";
break;
case ratingNumber >= 4:
ratingString = "four";
break;
case ratingNumber >= 3:
ratingString = "three";
break;
case ratingNumber >= 2:
ratingString = "two";
break;
case ratingNumber >= 1:
ratingString = "one";
break;
case ratingNumber >= 0:
ratingString = "zero";
break;
};
// yelp render from data build
yelpContent = `
<div class="detail-panel-yelpcard">
<img src="${yelpItem.image_url}" class="detail-panel-yelpcard-image">
<div class="detail-panel-yelpcard-meta">
<p class="detail-panel-yelpcard-meta-name">${yelpItem.name}</p>
<p class="detail-panel-yelpcard-meta-address">${yelpItem.location.address1}</p>
<p class="detail-panel-yelpcard-meta-location">${yelpItem.location.city}, ${yelpItem.location.state} ${yelpItem.location.zip_code}</p>
</div>
<div class="detail-panel-yelpcard-yelp">
<span class="detail-panel-yelpcard-yelp-stars ${ratingString}"></span>
<p class="detail-panel-yelpcard-yelp-reviews">Reviews: ${yelpItem.review_count}</p>
<a href="${yelpItem.phone}" class="detail-panel-yelpcard-yelp-phone">${yelpItem.display_phone}</a>
<a class="detail-panel-yelpcard-yelp-url" href="" target="_blank" rel="noreferrer noopener">Explore On <img src="/img/yelp_fullcolor.png" alt="Yelp Dot Com Page">
</a>
</div>
</div>
`;
yelpDataTarget.innerHTML += yelpContent;
}); // end .forEach
}; // end yelpPanelsRender()
}); // end customElements.define('panels-row')
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment