<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
  • Content:
    .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;
          }
        }
      }
    }
    
  • URL: /components/raw/product-search/_product-search.scss
  • Filesystem Path: ../src/03_molecules/product-search/_product-search.scss
  • Size: 1.7 KB
  • Content:
    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));
    
  • URL: /components/raw/product-search/product-search.js
  • Filesystem Path: ../src/03_molecules/product-search/product-search.js
  • Size: 6.4 KB

No notes defined.