<div class="productsearch productsearch--empty" data-api="/fapi/hardware.json?value=">
<div class="productsearch__inputwrapper input-field">
<div class="input-field__inner">
<input type="text" name="product" id="product-name" value="" class="productsearch__input">
<span class="input-field__placeholder">Product</span>
</div>
<div class="input-field__error">
<p>This field is not filled in correctly or cannot be empty.</p>
</div>
</div>
<div class="product-detail hidden">
<div class="product-detail__visual">
<img src="https://www.pioneerdj.com/-/media/pioneerdj/images/products/player/cdj-350/black/cdj-350-main.png?h=480&w=640&hash=BDA69205FF6F40288BBE74CD4CBE11FC" class="product-detail__image" alt="">
</div>
<div class="product-detail__content">
<h4 class="product-detail__title">CDJ-350</h4>
<p class="product-detail__description">Compact DJ multi player with disc drive</p>
<input type="hidden" name="product-id" class="product-detail__id" value="" required />
<div class="spacer spacer--2"></div>
<button class="mypage__link link link--underlined productsearch__reset">
<span class="link__label">Change</span>
</button>
</div>
</div>
<div class="productsearch__outputwrapper">
<div class="productsearch__output">
<div class="productsearch__state productsearch__state--loading">
<p>Looking for products...</p>
</div>
<div class="productsearch__state productsearch__state--noresults">
<p>We couldn't find any products for this search query</p>
</div>
<ul class="productsearch__list"></ul>
</div>
</div>
</div>
<div class="productsearch productsearch--empty" data-api="/fapi/hardware.json?value=">
{{render '@input-field' input merge=true }}
<div class="product-detail hidden">
<div class="product-detail__visual">
<img src="https://www.pioneerdj.com/-/media/pioneerdj/images/products/player/cdj-350/black/cdj-350-main.png?h=480&w=640&hash=BDA69205FF6F40288BBE74CD4CBE11FC" class="product-detail__image" alt="">
</div>
<div class="product-detail__content">
<h4 class="product-detail__title">CDJ-350</h4>
<p class="product-detail__description">Compact DJ multi player with disc drive</p>
<input type="hidden" name="product-id" class="product-detail__id" value="" required />
<div class="spacer spacer--2"></div>
<button class="mypage__link link link--underlined productsearch__reset">
<span class="link__label">Change</span>
</button>
</div>
</div>
<div class="productsearch__outputwrapper">
<div class="productsearch__output">
<div class="productsearch__state productsearch__state--loading">
<p>Looking for products...</p>
</div>
<div class="productsearch__state productsearch__state--noresults">
<p>We couldn't find any products for this search query</p>
</div>
<ul class="productsearch__list"></ul>
</div>
</div>
</div>
input:
placeholder: Product
name: product
class: productsearch__input
id: product-name
parentClass: productsearch__inputwrapper
.productsearch {
position: relative;
z-index: 100;
&--empty,
&--hasvalue {
.product-detail {
display: none;
}
}
&--selected {
.productsearch__inputwrapper {
display: none;
}
}
&__output {
background: var(--color--neutrals-0);
border-radius: 4px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
max-height: 400px;
overflow: auto;
padding: var(--gap--4) var(--gap--2);
&wrapper {
display: flex;
flex-flow: column;
left: 0;
max-width: 350px;
position: absolute;
top: 100%;
width: 100%;
.productsearch--empty &,
.productsearch--selected & {
display: none;
}
}
}
&__list {
margin: 0 !important;
padding: 0;
}
&__item {
align-items: center;
border-bottom: 1px solid var(--color--neutrals-3);
color: var(--color--theme);
cursor: pointer;
display: flex;
flex-flow: row nowrap;
padding: var(--gap--2);
&visual {
margin-left: 0;
margin-right: var(--gap--4);
width: 50px;
}
&meta {
p {
margin-top: 0 !important;
}
}
&:last-child {
border-bottom: none;
}
&:hover {
background: var(--color--theme);
color: var(--color--neutrals-0);
}
}
&__image {
display: block;
width: 100%;
}
&__state {
&--noresults, &--loading {
padding: var(--gap--2);
.productsearch--hasvalue & {
display: none;
}
}
&--noresults {
.productsearch--loading & {
display: none;
}
}
&--loading {
.productsearch--noresults & {
display: none;
}
}
}
}
const PRODUCTSEARCH = (function(window, undefined) {
const SETTINGS = {
debug: false,
minLength: 3,
};
const SELECTORS = {
wrapper: '.productsearch',
input: '.productsearch__input',
output: '.productsearch__output',
reset: '.productsearch__reset',
list: '.productsearch__list',
detail: '.product-detail',
detailImage: '.product-detail__image',
detailTitle: '.product-detail__title',
detailDescription: '.product-detail__description',
productId: '.product-detail__id',
state: '.productsearch__state',
};
const CLASSES = {
wrapper: 'productsearch',
empty: 'productsearch--empty',
hasvalue: 'productsearch--hasvalue',
selected: 'productsearch--selected',
item: 'productsearch__item',
hidden: 'hidden'
};
const STATES = {
empty: 'empty',
hasvalue: 'hasvalue',
selected: 'selected',
loading: 'loading',
noresults: 'noresults',
results: 'results',
}
function exit(e) {
const wrappers = document.querySelectorAll(SELECTORS.wrapper);
wrappers.forEach(empty);
}
function onProductClick(wrapper, product) {
window.removeEventListener('click', exit);
if (!wrapper) { return; }
const detail = wrapper.querySelector(SELECTORS.detail);
if (!detail) { return; }
if (SETTINGS.debug) { console.log('PRODUCTSEARCH product', product) }
const image = detail.querySelector(SELECTORS.detailImage);
const title = detail.querySelector(SELECTORS.detailTitle);
const description = detail.querySelector(SELECTORS.detailDescription);
const productId = detail.querySelector(SELECTORS.productId);
image.src = product.ImageUrl;
image.alt = `${product.ModelNumber} - ${product.Description}`;
title.innerHTML = product.ModelNumber;
description.innerHTML = product.Description;
productId.value = product.Id;
wrapper.classList.add(CLASSES.selected);
wrapper.classList.remove(CLASSES.empty);
wrapper.classList.remove(CLASSES.hasvalue);
const input = wrapper.querySelector(SELECTORS.input);
input.value = product.ModelNumber;
detail.classList.remove(CLASSES.hidden);
}
async function getData(api, value) {
const response = await fetch(`${api}${value}`);
const data = await response.json();
return data;
}
async function getProducts(api, value) {
const data = await getData(api,value);
const products = data.filter(product => product.ModelNumber.toLowerCase().includes(value.toLowerCase()));
return products;
}
async function populate(wrapper, value) {
if (!wrapper) { return; };
const api = wrapper.dataset.api;
if (!api) { return; }
const output = wrapper.querySelector(SELECTORS.output);
if (!output) { return; }
setState(wrapper, STATES.loading);
const products = await getProducts(api, value);
empty(wrapper);
if (products.length <= 0) {
setState(wrapper, STATES.noresults);
return;
}
setState(wrapper, STATES.results);
const list = wrapper.querySelector(SELECTORS.list);
products.forEach(product => {
const item = document.createElement('li');
item.classList.add(CLASSES.item);
item.dataset.id = product.Id;
item.innerHTML = `
<figure class="productsearch__itemvisual"><img src="${product.ImageUrl}" class="productsearch__image" alt="${product.ModelNumber} - ${product.Description}" /></figure>
<div class="productsearch__itemmeta">
<p class="productsearch__itemtitle">${product.ModelNumber}</p>
<p class="productsearch__itemcolor">${product.Name}</p>
</div>
`;
list.appendChild(item);
item.addEventListener('click', function(e) {
e.preventDefault();
onProductClick(wrapper, product);
}, { once: true });
});
wrapper.classList.remove(CLASSES.empty);
wrapper.classList.add(CLASSES.hasvalue);
}
function empty(wrapper) {
if (!wrapper) { return; }
const list = wrapper.querySelector(SELECTORS.list);
if (!list) { return; }
if (SETTINGS.debug) { console.log('PRODUCTSEARCH empty', list) }
list.innerHTML = '';
setState(wrapper, STATES.empty);
}
function setState(wrapper, value) {
if (!wrapper) { return; }
Object.keys(STATES).forEach(state => {
wrapper.classList.remove(`${CLASSES.wrapper}--${state}`)
});
if (SETTINGS.debug) { console.log('PRODUCTSEARCH state', value) }
if (STATES[value]) {
wrapper.classList.add(`${CLASSES.wrapper}--${STATES[value]}`);
}
}
function onInput(e) {
const input = e.target;
const wrapper = input.closest(SELECTORS.wrapper);
if (!wrapper) { return; }
if (input.value.length < SETTINGS.minLength) { empty(wrapper); return; }
window.removeEventListener('click', exit);
window.addEventListener('click', exit);
if (SETTINGS.debug) { console.log('PRODUCTSEARCH input', input.value) }
clearTimeout(SETTINGS.timer);
SETTINGS.timer = setTimeout(() => {
populate(wrapper, input.value);
}, 100);
}
function onReset(e) {
e.preventDefault();
const trigger = e.target;
const wrapper = trigger.closest(SELECTORS.wrapper);
if (!wrapper) { return; }
empty(wrapper);
wrapper.classList.add(CLASSES.empty);
wrapper.classList.remove(CLASSES.hasvalue);
wrapper.classList.remove(CLASSES.selected);
const detail = wrapper.querySelector(SELECTORS.detail);
if (detail) {
detail.classList.add(CLASSES.hidden);
}
const input = wrapper.querySelector(SELECTORS.input);
if (input) {
input.value = '';
input.focus();
}
const productId = wrapper.querySelector(SELECTORS.productId);
if (productId) {
productId.value = '';
}
}
function init() {
const inputs = document.querySelectorAll(SELECTORS.input);
if (!inputs || inputs.length <= 0) { return; }
if (SETTINGS.debug) { console.log('PRODUCTSEARCH init'); }
inputs.forEach(input => input.addEventListener('input', onInput));
SETTINGS.timer = null;
const resets = document.querySelectorAll(SELECTORS.reset);
if (!resets || resets.length <= 0) { return; }
resets.forEach(reset => reset.addEventListener('click', onReset));
}
document.addEventListener('DOMContentLoaded', init);
return {
init,
};
}(window));
No notes defined.