<div class="acl-searchbox" style="width: 420px">

    <div class="acl-searchbox__header">
        <div class="acl-searchbox__search-icon"><i class="aforza-icons">acl_search</i></div>
        <div class="autocomplete acl-searchbox__input">
            <input autocomplete="off" id="routeBuilderSearch" type="text" name="routeBuilderSearch" placeholder="Type here">
        </div>
        <div class="acl-searchbox__filter-button acl-searchbox__filter-button--grey">
            Filter <span class="acl-searchbox__filter-selected-count"></span>
        </div>
    </div>

    <div class="acl-searchbox__filters-banner acl-searchbox__filters-banner--hidden">
        <div class="acl-searchbox__filters-banner-preview"></div>
    </div>

    <div class="acl-chip-set acl-chip-set--filter acl-searchbox__filters acl-searchbox__filters--hidden">
        <div class="acl-searchbox__filters-header">
            <div class="acl-searchbox__filters-description">Select the filters for your search results</div>
            <div class="acl-searchbox__filters-close"><i class="material-icons">close</i></div>
        </div>
        <div class="acl-searchbox__filters-chips"></div>
    </div>
</div>

<script>
    var filters = [{
            id: 'filter01',
            name: "Big"
        },
        {
            id: 'filter02',
            name: "Medium"
        },
        {
            id: 'filter03',
            name: "Small"
        },
        {
            id: 'filter04',
            name: "Xsmall"
        },
        {
            id: 'filter05',
            name: "Supermarket"
        },
        {
            id: 'filter06',
            name: "Petrol Station"
        },
    ];
    const searchboxEl = document.querySelector(".acl-searchbox");
    const searchbox = new acl.ACLSearchboxSuggestions(searchboxEl, filters);
    searchbox.autocomplete();
    searchboxEl.addEventListener('selectedfilterchips', function(e) {
        console.log('selectedfilterchips');
        console.log(e.detail.chips);
    });
    searchboxEl.addEventListener('resultselected', function(e) {
        console.log('resultselected');
        console.log(e.detail.resultId);
    });
    searchboxEl.addEventListener('searchquery', function(e) {
        console.log('searchquery');
        console.log(e.detail.searchValue);
    });
</script>
<div class="acl-searchbox" style="width: 420px">

    <div class="acl-searchbox__header">
        <div class="acl-searchbox__search-icon"><i class="aforza-icons">acl_search</i></div>
        <div class="autocomplete acl-searchbox__input">
            <input autocomplete="off" id="routeBuilderSearch" type="text" name="routeBuilderSearch"
                placeholder="Type here">
        </div>
        <div class="acl-searchbox__filter-button acl-searchbox__filter-button--grey">
            Filter <span class="acl-searchbox__filter-selected-count"></span>
        </div>
    </div>

    <div class="acl-searchbox__filters-banner acl-searchbox__filters-banner--hidden">
        <div class="acl-searchbox__filters-banner-preview"></div>
    </div>

    <div class="acl-chip-set acl-chip-set--filter acl-searchbox__filters acl-searchbox__filters--hidden">
        <div class="acl-searchbox__filters-header">
            <div class="acl-searchbox__filters-description">Select the filters for your search results</div>
            <div class="acl-searchbox__filters-close"><i class="material-icons">close</i></div>
        </div>
        <div class="acl-searchbox__filters-chips"></div>
    </div>
</div>



<script>
    var filters = [
        { id: 'filter01', name: "Big" },
        { id: 'filter02', name: "Medium" },
        { id: 'filter03', name: "Small" },
        { id: 'filter04', name: "Xsmall" },
        { id: 'filter05', name: "Supermarket" },
        { id: 'filter06', name: "Petrol Station" },
    ];

    const searchboxEl = document.querySelector(".acl-searchbox");

    const searchbox = new acl.ACLSearchboxSuggestions(searchboxEl, filters);

    searchbox.autocomplete();

    searchboxEl.addEventListener('selectedfilterchips', function (e) {
        console.log('selectedfilterchips');
        console.log(e.detail.chips);
    });

    searchboxEl.addEventListener('resultselected', function (e) {
        console.log('resultselected');
        console.log(e.detail.resultId);
    });

    searchboxEl.addEventListener('searchquery', function (e) {
        console.log('searchquery');
        console.log(e.detail.searchValue);
    });

</script>
/* No context defined. */
  • Content:
    class ACLSearchboxSuggestions {
    	constructor(searchboxEl, filters) {
    		this.searchboxEl = searchboxEl;
    		this.filters = filters;
    		this.filterChips;
    		this.resultCount = 0;
    		this.searchValue;
    		this.results;
    		this.currentFocus;
    		this.resultsEl;
    	}
    
    	autocomplete(results) {
    		let self = this;
    
    		this.results = results;
    
    		let chipSet;
    		let allChips;
    		let selectedChips;
    		let filterCount;
    
    		this.filterButtonEl = this.searchboxEl.querySelector('.acl-searchbox__filter-button');
    		this.resultsEl = this.searchboxEl.querySelector('.acl-searchbox__results');
    
    		const searchInputEl = this.searchboxEl.querySelector('input');
    		const closeFiltersButtonEl = this.searchboxEl.querySelector('.acl-searchbox__filters-close');
    		const filterBannerEl = this.searchboxEl.querySelector('.acl-searchbox__filters-banner');
    		const filterBannerResultsEl = this.searchboxEl.querySelector('.acl-searchbox__filters-banner-preview');
    		const selectedFilterCountLabel = this.searchboxEl.querySelector('.acl-searchbox__filter-selected-count');
    		const filterPanel = this.searchboxEl.querySelector('.acl-searchbox__filters-chips');
    		const searchboxHeader = this.searchboxEl.querySelector('.acl-searchbox__header');
    		const searchboxFilters = this.searchboxEl.querySelector('.acl-searchbox__filters');
    
    		if (this.filters) {
    			addFilters(this.filters);
    		}
    
    		if (closeFiltersButtonEl) {
    			closeFiltersButtonEl.addEventListener('click', function() {
    				toggleFilterPanel();
    
    				if (filterCount > 0) {
    					filterBannerEl.classList.remove('acl-searchbox__filters-banner--hidden');
    				}
    
    				postFilterEventChanges();
    			});
    		}
    
    		if (this.filterButtonEl) {
    			this.filterButtonEl.addEventListener('click', function() {
    				toggleFilterPanel();
    				filterBannerEl.classList.add('acl-searchbox__filters-banner--hidden');
    				hideDropdownList(self.searchboxEl);
    			});
    		}
    
    		if (this.resultsEl) {
    			this.resultsEl.addEventListener('click', function(e) {
    				let autocompleteText = e.target.dataset.autocompleteItemName;
    				let autocompleteId = e.target.dataset.autocompleteItemId;
    
    				if (autocompleteText) {
    					searchInputEl.value = e.target.dataset.autocompleteItemName;
    				}
    
    				postResultSelectedEvent(autocompleteId);
    				toggleDropdownListVisibility(searchboxEl);
    			});
    		}
    
    		/*execute a function when someone writes in the text field:*/
    		searchInputEl.addEventListener('input', function(e) {
    			self.searchValue = this.value;
    			self.closeAllLists();
    
    			if (!self.searchValue) {
    				if (filterCount == 0) {
    					self.filterButtonEl.classList.add('acl-searchbox__filter-button--hidden', 'animated', 'fadeOut');
    				}
    
    				postSearchQueryEvent();
    			} else {
    				if (self.searchValue.length > 2 && self.results) {
    					self.generateResults(self.searchValue, self);
    				}
    
    				if (self.searchValue.length > 2 && self.filterButtonEl) {
    					self.filterButtonEl.classList.remove('acl-searchbox__filter-button--hidden', 'animated', 'fadeOut');
    				}
    
    				postSearchQueryEvent(self.searchValue);
    			}
    		});
    
    		/*execute a function presses a key on the keyboard:*/
    		searchInputEl.addEventListener('keydown', function(e) {
    			var x = document.getElementById(this.id + 'acl-autocomplete-list');
    			if (x) x = x.querySelectorAll('.acl-autocomplete__item');
    
    			if (e.keyCode == 40) {
    				/*If the arrow DOWN key is pressed, increase the currentFocus variable:*/
    				this.currentFocus++;
    				/*and and make the current item more visible:*/
    
    				addActive(x);
    			} else if (e.keyCode == 38) {
    				//up
    
    				/*If the arrow UP key is pressed, decrease the currentFocus variable:*/
    				this.currentFocus--;
    				/*and and make the current item more visible:*/
    
    				addActive(x);
    			} else if (e.keyCode == 13) {
    				/*If the ENTER key is pressed, prevent the form from being submitted,*/
    				e.preventDefault();
    
    				if (this.currentFocus > -1) {
    					/*and simulate a click on the "active" item:*/
    					if (x) {
    						x[this.currentFocus].click();
    					}
    				}
    			}
    		});
    
    		function addActive(x) {
    			/*a function to classify an item as "active":*/
    			if (!x) return false;
    			/*start by removing the "active" class on all items:*/
    
    			removeActive(x);
    			if (this.currentFocus >= x.length) this.currentFocus = 0;
    			if (this.currentFocus < 0) this.currentFocus = x.length - 1;
    			/*add class "autocomplete-active":*/
    
    			x[this.currentFocus].classList.add('autocomplete-active');
    		}
    
    		function removeActive(x) {
    			/*a function to remove the "active" class from all autocomplete items:*/
    			for (var i = 0; i < x.length; i++) {
    				x[i].classList.remove('autocomplete-active');
    			}
    		}
    
    		function toggleDropdownListVisibility(elmnt) {
    			const dropdown = elmnt.querySelector('.acl-autocomplete__items');
    			dropdown.classList.toggle('acl-autocomplete__items--hidden');
    		}
    
    		function hideDropdownList(elmnt) {
    			const dropdown = elmnt.querySelector('.acl-autocomplete__items');
    			if (dropdown) {
    				dropdown.classList.add('acl-autocomplete__items--hidden');
    			}
    		}
    
    		function postFilterEventChanges() {
    			let filterIds = 0;
    
    			if (selectedChips.length > 0) {
    				filterIds = selectedChips.map(selectedChip => {
    					return selectedChip.id;
    				});
    			}
    
    			self.searchboxEl.dispatchEvent(
    				new CustomEvent('selectedfilterchips', {
    					detail: {
    						chips: filterIds,
    					},
    				})
    			);
    		}
    
    		function postResultSelectedEvent(autocompleteId) {
    			self.searchboxEl.dispatchEvent(
    				new CustomEvent('resultselected', {
    					detail: {
    						resultId: autocompleteId,
    					},
    				})
    			);
    		}
    
    		function postSearchQueryEvent(searchValue) {
    			self.searchboxEl.dispatchEvent(
    				new CustomEvent('searchquery', {
    					detail: {
    						searchValue: searchValue,
    					},
    				})
    			);
    		}
    
    		function addFilters(filters) {
    			if (filterPanel) {
    				for (var i = 0; i < filters.length; i++) {
    					filterPanel.innerHTML += `<div class="acl-chip acl-ripple-upgraded" role="row" data-filter-text="${filters[i].name}" id="${filters[i].id}"
            style="--acl-ripple-fg-size:57px; --acl-ripple-fg-scale:1.94633; --acl-ripple-fg-translate-start:16.4414px, 
                - 21.9688px; --acl-ripple-fg-translate-end: 19.3672px, -12.5px;">
                    <span role="button" tabindex="0" class="acl-chip__text">${filters[i].name}</span>
                        <div class="acl-chip__checkmark">
                            <svg class="acl-chip__checkmark-svg" viewBox="-3 -3 30 30">
                                <path class="" fill="white" stroke="none" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" /><path d="M0 0h24v24H0z" fill="none" />
                            </svg>
                        </div>
    </div>`;
    				}
    
    				if (self.searchboxEl) {
    					getAllChips();
    				}
    			}
    		}
    
    		function toggleFilterPanel() {
    			searchboxFilters.classList.toggle('acl-searchbox__filters--hidden');
    			searchboxHeader.classList.toggle('acl-searchbox__header--hidden');
    			setSelectedFilterCount();
    			setFilterBannerPreview();
    		}
    
    		function getAllChips() {
    			const chipSetEls = Array.from(self.searchboxEl.querySelectorAll('.acl-chip-set'));
    			chipSetEls.forEach(el => {
    				chipSet = new acl.MDCChipSet(el);
    			});
    			return chipSet;
    		}
    
    		function getSelectedChips() {
    			allChips = getAllChips();
    			selectedChips = allChips.chips.filter(element => element.selected == true);
    			return selectedChips;
    		}
    
    		function setSelectedFilterCount() {
    			const selectedFilters = getSelectedChips();
    			filterCount = selectedFilters.length;
    			selectedFilterCountLabel.innerHTML = filterCount;
    
    			if (selectedFilters.length > 0) {
    				selectedFilterCountLabel.classList.add('acl-searchbox__filter-selected-count--visible');
    				self.filterButtonEl.classList.add('acl-searchbox__filter-button--inc-count');
    			} else {
    				selectedFilterCountLabel.classList.remove('acl-searchbox__filter-selected-count--visible');
    				filterBannerEl.classList.add('acl-searchbox__filters-banner--hidden');
    				self.filterButtonEl.classList.remove('acl-searchbox__filter-button--inc-count');
    			}
    		}
    
    		function setFilterBannerPreview() {
    			const selectedFilters = getSelectedChips();
    			filterBannerResultsEl.innerHTML = '';
    
    			if (selectedFilters.length > 0) {
    				selectedFilters.slice(0, 2).forEach(function(element) {
    					filterBannerResultsEl.innerHTML += `<div class="acl-searchbox__filters-banner-item" data-filter-id="${element.id}" id="${element.id}-preview" >
                ${element.root_.dataset.filterText}
            <svg class="acl-searchbox__filters-banner-item-close" viewBox="-3 -3 30 30">
                <path class="" fill="#1FACEC" stroke="none" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" /><path d="M0 0h24v24H0z" fill="none" />
            </svg>
        </div> `;
    				});
    			}
    
    			if (selectedFilters.length > 2) {
    				filterBannerResultsEl.innerHTML += `<div class="acl-searchbox__filters-more"> + ${selectedFilters.length -
    					2} more</div>`;
    				const searchboxFilterPreviewMore = searchboxEl.querySelector('.acl-searchbox__filters-more');
    				searchboxFilterPreviewMore.addEventListener('click', function() {
    					toggleFilterPanel();
    					toggleDropdownListVisibility(searchboxEl);
    					filterBannerEl.classList.add('acl-searchbox__filters-banner--hidden');
    				});
    			}
    
    			let filterPreviewEls = searchboxEl.querySelectorAll('.acl-searchbox__filters-banner-item');
    
    			for (const filterEl of filterPreviewEls) {
    				filterEl.addEventListener('click', function(event) {
    					selectedChips.filter(obj => {
    						if (obj.selected == true && obj.id == event.target.dataset.filterId) {
    							obj.selected = false;
    							event.target.innerHTML = '';
    						}
    					});
    					setSelectedFilterCount();
    					setFilterBannerPreview();
    					postFilterEventChanges(searchboxEl);
    				});
    			}
    		}
    	}
    
    	closeAllLists() {
    		/*close all autocomplete lists in the document,
    except the one passed as an argument:*/
    		var x = this.searchboxEl.getElementsByClassName('acl-autocomplete__items');
    
    		for (var i = 0; i < x.length; i++) {
    			x[i].parentNode.removeChild(x[i]);
    		}
    	}
    
    	showDropdownList() {
    		const dropdown = this.searchboxEl.querySelector('.acl-autocomplete__items');
    		dropdown.classList.remove('acl-autocomplete__items--hidden');
    	}
    
    	setResults(updatedResults) {
    		this.results = updatedResults;
    		this.generateResults(this.searchValue);
    	}
    
    	generateResults(inputValue) {
    		if (inputValue && this.resultsEl) {
    			this.closeAllLists();
    			this.resultCount = 0;
    
    			var a,
    				b,
    				i,
    				val = inputValue;
    			this.currentFocus = -1;
    			a = document.createElement('DIV');
    			a.setAttribute('id', this.id + 'acl-autocomplete-list');
    			a.setAttribute('class', 'acl-autocomplete__items');
    			this.resultsEl.appendChild(a);
    
    			for (i = 0; i < this.results.length; i++) {
    				const searchTerm = `${this.results[i].primary.toUpperCase()} ${this.results[
    					i
    				].secondary.searchValue.toUpperCase()}`;
    
    				if (searchTerm.includes(val.toUpperCase())) {
    					b = document.createElement('DIV');
    					b.classList.add('acl-autocomplete__item');
    					b.setAttribute(
    						'data-autocomplete-item-name',
    						`${this.results[i].primary} ${this.results[i].secondary.searchValue}`
    					);
    					b.setAttribute('data-autocomplete-item-id', `${this.results[i].id}`);
    					let ctaBtn = `<span class="acl-autocomplete__item-cta">View</span>`;
    
    					if (!this.results[i].cta) {
    						ctaBtn = '';
    					}
    
    					b.innerHTML += `<div class="acl-autocomplete__item-details">
    <i class="aforza-icons acl-autocomplete__icon--${this.results[i].iconSize}" style="color: ${
    						this.results[i].iconColour
    					}">${this.results[i].icon}</i>
        <strong> ${this.results[i].primary} </strong>
             ${this.results[i].seperator ? '&nbsp;- ' : '&nbsp;'} ${this.results[i].secondary.displayValue}
     </div>
     ${ctaBtn}
    `;
    					a.appendChild(b);
    					this.resultCount++;
    				} else {
    					var dropdownItems = this.searchboxEl.getElementsByClassName('acl-autocomplete__item');
    
    					if (dropdownItems) {
    						this.resultCount = dropdownItems.length;
    					} else {
    						this.resultCount = 0;
    					}
    				}
    			}
    
    			if (this.resultCount == 0) {
    				b = document.createElement('DIV');
    				b.classList.add('acl-autocomplete__item');
    				b.innerHTML = `<div class="acl-autocomplete__no-results">There are no results matching your search</div>`;
    				a.appendChild(b);
    			} else {
    				this.showDropdownList();
    				this.filterButtonEl.classList.remove('acl-searchbox__filter-button--hidden', 'animated', 'fadeIn');
    			}
    		}
    	}
    }
    
    export { ACLSearchboxSuggestions };
    
  • URL: /components/raw/searchbox-suggestions/index.js
  • Filesystem Path: src/components/searchbox/searchbox-suggestions/index.js
  • Size: 12.5 KB
  • Handle: @searchbox-suggestions--input
  • Preview:
  • Filesystem Path: src/components/searchbox/searchbox-suggestions/searchbox-suggestions--input.hbs

Buttons

Buttons allow users to take actions, and make choices, with a single tap.

Design & API Documentation

Installation

npm install ..//button

Basic Usage

HTML Structure

<button class="acl-button">
  <div class="acl-button__ripple"></div>
  <span class="acl-button__label">Button</span>
</button>

NOTE: Examples here use the generic <button>, but users can also apply the acl-button class to <a> elements.

Styles

@import "../button/acl-button";

JavaScript Instantiation

The button will work without JavaScript, but you can enhance it to have a ripple effect by instantiating MDCRipple on the root element. See MDC Ripple for details.

import {MDCRipple} from '..//ripple';

const buttonRipple = new MDCRipple(document.querySelector('.acl-button'));

See Importing the JS component for more information on how to import JavaScript.

Variants

Contained Button

To style a contained button, add the acl-button--raised class to the <button> element for a contained button with elevation, or the acl-button--unelevated class for a contained button flush with the surface.

Outlined Button

To style an outlined button, add the class acl-button--outlined to the <button> element.

Icons

We recommend using Material Icons from Google Fonts:

<head>
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>

However, you can also use SVG, Font Awesome, or any other icon library you wish.

To add an icon, add an element with the acl-button__icon class inside the button element and set the attribute aria-hidden="true". The icon is set to 18px to meet legibility requirements.

<button class="acl-button">
  <div class="acl-button__ripple"></div>
  <i class="material-icons acl-button__icon" aria-hidden="true">favorite</i>
  <span class="acl-button__label">Button</span>
</button>

It’s also possible to use an SVG icon:

<button class="acl-button">
  <div class="acl-button__ripple"></div>
  <svg class="acl-button__icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="...">
  ...
  </svg>
  <span class="acl-button__label">Button</span>
</button>

Trailing Icon

Certain icons make more sense to appear after the button’s text label rather than before. This can be accomplished by putting the icon markup after the acl-button__label element.

<button class="acl-button">
  <div class="acl-button__ripple"></div>
  <span class="acl-button__label">Button</span>
  <i class="material-icons acl-button__icon" aria-hidden="true">favorite</i>
</button>

NOTE: The acl-button__label element is required in order for the trailing icon to be styled appropriately.

Disabled

To disable a button, add the disabled attribute directly to the <button>, or set the disabled attribute on the <fieldset> containing the button. Disabled buttons cannot be interacted with and have no visual interaction effect.

<button class="acl-button" disabled>
  <div class="acl-button__ripple"></div>
  <span class="acl-button__label">Button</span>
</button>

Additional Information

Accessibility

Material Design spec advises that touch targets should be at least 48 x 48 px. To meet this requirement, add the following to your button:

<div class="acl-touch-target-wrapper">
  <button class="acl-button acl-button--touch">
    <div class="acl-button__ripple"></div>
    <span class="acl-button__label">My Accessible Button</span>
    <div class="acl-button__touch"></div>
  </button>
</div>

Note that the outer acl-touch-target-wrapper element is only necessary if you want to avoid potentially overlapping touch targets on adjacent elements (due to collapsing margins).

Style Customization

CSS Classes

CSS Class Description
acl-button Mandatory. Defaults to a text button that is flush with the surface.
acl-button__ripple Mandatory. Indicates the element which shows the ripple styling.
acl-button--raised Optional. Styles a contained button that is elevated above the surface.
acl-button--unelevated Optional. Styles a contained button that is flush with the surface.
acl-button--outlined Optional. Styles an outlined button that is flush with the surface.
acl-button__label Recommended.* Indicates the element containing the button’s text label.
acl-button__icon Optional. Indicates the element containing the button’s icon.

*NOTE: The acl-button__label element is required for buttons with a trailing icon, but it is currently optional for buttons with no icon or a leading icon. In the latter cases, it is acceptable for the text label to simply exist directly within the acl-button element. However, the acl-button__label class may become mandatory for all cases in the future, so it is recommended to always include it to be future-proof.

Sass Mixins

To customize a button’s color and properties, you can use the following mixins.

Basic Sass Mixins

MDC Button uses MDC Theme‘s primary color by default. Use the following mixins to customize it.

Mixin Description
acl-button-filled-accessible($container-fill-color) Sets the container fill color for a contained (raised or unelevated) button, and updates the button’s ink, icon, and ripple colors to meet accessibility standards

Advanced Sass Mixins

These mixins will override the color of the container, ink, outline or ripple. It is up to you to ensure your button meets accessibility standards.

Mixin Description
acl-button-container-fill-color($color) Sets the container fill color to the given color.
acl-button-icon-color($color) Sets the icon color to the given color.
acl-button-ink-color($color) Sets the ink color to the given color, and sets the icon color to the given color unless acl-button-icon-color is also used.
acl-button-density($density-scale) Sets density scale for button. Supported density scale values (-3, -2, -1, 0).
acl-button-height($height) Sets custom height of button.
acl-button-shape-radius($radius, $density-scale, $rtl-reflexive) Sets rounded shape to button with given radius size. $density-scale is only required when $radius value is in percentage unit, defaults to $acl-button-density-default-scale. Set $rtl-reflexive to true to flip radius values in RTL context, defaults to false.
acl-button-horizontal-padding($padding) Sets horizontal padding to the given number.
acl-button-outline-color($color) Sets the outline color to the given color.
acl-button-outline-width($width, $padding) Sets the outline width to the given number (defaults to 2px) and adjusts padding accordingly. $padding is only required in cases where acl-button-horizontal-padding is also included with a custom value.
Caveat: Edge and CSS Custom Properties

In browsers that fully support CSS custom properties, the above mixins will work if you pass in a MDC Theme property (e.g. primary) as an argument. However, Edge does not fully support CSS custom properties. If you are using the acl-button-container-fill-color mixin, you must pass in an actual color value for support in Edge.