Filter

The Filter Component is a list of categories used to get more granular results in a search. With JavaScript enabled, the Filter Component will toggle open or closed using the Toggle JavaScript Utility. All of the same accessible attribute toggling functionality the Toggle Utility provides will apply (aria-controls, aria-expanded, and aria-hidden). The markup for the list can be a navigation <nav> element with hyperlinks or standard unordered list <ul> element with hyperlinks <a> or buttons <buttons> as the filtering element. Hyperlinks would be used for filters that navigate to different pages and buttons would trigger in-page functionality. Below is an example of a filter using a navigation element with hyperlinks.

<div class="c-filter">
  <button aria-controls="aria-c-programs" aria-expanded="true" class="c-filter__header active" data-js="filter" id="aria-lb-programs" type="button">All Programs</button>
  <nav aria-hidden="false" aria-labelledby="aria-lb-programs" class="c-filter__list active" id="aria-c-programs" role="region">
    <span class="c-filter__item">All Programs</span>
    <a class="c-filter__item" href="#">Cash &amp; Expenses</a>
    <a class="c-filter__item" href="#">Child Care</a>
    <a class="c-filter__item" href="#">City ID Card</a>
    <a class="c-filter__item" href="#">Education</a>
    <a class="c-filter__item" href="#">Enrichment</a>
    <a class="c-filter__item" href="#">Family Services</a>
    <a class="c-filter__item" href="#">Food</a>
    <a class="c-filter__item" href="#">Health</a>
    <a class="c-filter__item" href="#">Housing</a>
    <a class="c-filter__item" href="#">Special Needs</a>
    <a class="c-filter__item" href="#">Work</a>
  </nav>
</div>

The Multi Filter Component allows for multiple selections and nested filter categories. There are alternate patterns for toggling all checkboxes within a group which include a checkbox on the parent menu toggle or a nested short link within the menu accordian.

<div class="c-filter-multi bg-white">
  <ul class="c-filter-multi__list">
    <li class="c-filter-multi__item">
      <button aria-controls="aria-c-cash-and-expenses" aria-expanded="true" class="c-filter-multi__item-header btn-link active" data-js="filter" id="aria-lb-cash-and-expenses" type="button">Cash &amp; Expenses<span class="c-filter-multi__item-header-toggle"></span>
      </button>
      <div aria-hidden="false" aria-labelledby="aria-lb-cash-and-expenses" class="c-filter-multi__item-group active" id="aria-c-cash-and-expenses" role="region">
        <ul class="c-filter-multi__item-group-list">
          <li class="c-filter-multi__item-group-subitem">
            <button class="btn-link" type="button">Toggle All</button>
          </li>
          <li class="c-filter-multi__item-group-subitem">
            <label class="checkbox">
              <input type="checkbox" value="6871" />
              <span class="checkbox__label text-small font-normal">Home Energy Assistance Program (HEAP)</span>
            </label>
          </li>
          <li class="c-filter-multi__item-group-subitem">
            <label class="checkbox">
              <input type="checkbox" value="6868" />
              <span class="checkbox__label text-small font-normal">Earned Income Tax Credit (EITC)</span>
            </label>
          </li>
        </ul>
      </div>
    </li>
    <li class="c-filter-multi__item">
      <div class="c-filter-multi__item-header">
        <label class="checkbox">
          <input type="checkbox" />
          <span class="checkbox__label">Child Care</span>
        </label>
        <button aria-controls="aria-c-child-care" aria-expanded="true" class="c-filter-multi__item-header-toggle active" data-js="filter" type="button">
          <span class="sr-only" id="aria-lb-child-care">Child Care</span>
        </button>
      </div>
      <div aria-hidden="false" aria-labelledby="aria-lb-child-care" class="c-filter-multi__item-group active" id="aria-c-child-care" role="region">
        <ul class="c-filter-multi__item-group-list">
          <li class="c-filter-multi__item-group-subitem">
            <label class="checkbox">
              <input type="checkbox" value="6866" />
              <span class="checkbox__label text-small font-normal">EarlyLearn Child Car</span>
            </label>
          </li>
          <li class="c-filter-multi__item-group-subitem">
            <label class="checkbox">
              <input type="checkbox" value="6867" />
              <span class="checkbox__label text-small font-normal">EarlyLearn Head Start</span>
            </label>
          </li>
        </ul>
      </div>
    </li>
    <li class="c-filter-multi__item">
      <div class="c-filter-multi__item-header">
        <label class="checkbox">
          <input type="checkbox" />
          <span class="checkbox__label">City ID Card</span>
        </label>
        <button aria-controls="aria-c-city-id-card" aria-expanded="false" class="c-filter-multi__item-header-toggle" data-js="filter" type="button">
          <span class="sr-only" id="aria-lb-city-id-card">City ID Card</span>
        </button>
      </div>
      <div aria-hidden="true" aria-labelledby="aria-lb-city-id-card" class="c-filter-multi__item-group" id="aria-c-city-id-card" role="region">
        <ul class="c-filter-multi__item-group-list">
          <li class="c-filter-multi__item-group-subitem">
            <label class="checkbox">
              <input type="checkbox" value="6871" />
              <span class="checkbox__label text-small font-normal">IDNYC</span>
            </label>
          </li>
        </ul>
      </div>
    </li>
  </ul>
</div>

Global Script

The Filter and Filter Multi Components require the Filter JavaScript for toggling functionality and screen reader accessibility. It will work for both the Filter and Filter Multi Components. To use the Filter in the global ACCESS NYC Patterns script use the following code:

<!-- Global Script -->
<script src="dist/scripts/access-nyc.js"></script>

<script>
  var access = new AccessNyc();
  access.filter();
</script>

This function will attach the filter toggling event to the body of the document.

Module Import

The ES6 and IFFE modules all require importing and object instantiation in your main script:

// ES6
import Filter from 'src/components/filter/filter';

<!-- IFFE -->
<script src="dist/components/filter/filter.iffe.js"></script>

new Filter();

Polyfills

This script uses the Toggle Utility as a dependency and requires the same polyfills. See the “Toggle Usage” section for more details.

The Vue Filter is a packaged Filter Component for Vue.js applications. It encapsulates markup, accessibility, data-binding, and event emitting for a fully functional component. See Vue Component Usage below for details on data and events that can be passed as properties to the component.

<template>
  <!-- eslint-disable max-len -->
  <div class="c-filter">
    <button type="button" class="c-filter__header" :id="ariaLabelledBy" :aria-expanded="ariaActive(this.terms.active)" :aria-controls="ariaControls" :class="classActive" @click="toggle" v-html="this.terms.name">
      {{ this.terms.name }}
    </button>

    <nav v-if="nav" role="region" class="c-filter__list" :aria-labelledby="ariaLabelledBy" :class="classActive" :id="ariaControls" :aria-hidden="ariaActive(!this.terms.active)">
      <a class="c-filter__item" @click="reset" v-html="[strings.ALL, terms.name].join(' ')" :tabindex="tabindex(terms.active)">
        {{ [strings.ALL, terms.name].join(' ') }}
      </a>

      <a v-for="t in terms.filters" :key="t.id" class="c-filter__item" :href="t.href" @click="fetch({'event': $event, 'data': t})" v-html="t.name" :tabindex="tabindex(terms.active)">
        {{ t.name }}
      </a>
    </nav>
    <ul v-else role="region" class="c-filter__list" :aria-labelledby="ariaLabelledBy" :class="classActive" :id="ariaControls" :aria-hidden="ariaActive(!this.terms.active)">
      <li>
        <button type="button" class="c-filter__item" :aria-pressed="ariaPressed(terms.name)" @click="reset" v-html="[strings.ALL, terms.name].join(' ')" :tabindex="tabindex(terms.active)">
          {{ [strings.ALL, terms.name].join(' ') }}
        </button>
      </li>

      <li v-for="t in terms.filters" :key="t.id">
        <button type="button" class="c-filter__item" :aria-pressed="ariaPressed(t.name)" :href="'#' + t.slug" @click="fetch({'event': $event, 'data': t})" v-html="t.name" :tabindex="tabindex(terms.active)">
          {{ t.name }}
        </button>
      </li>
    </ul>
  </div>
</template>

<style>
  /* @import 'filter'; */
</style>

<script>
  export default {
    props: {
      'terms': {type: Object},
      'nav': {
        type: Boolean,
        default: false
      },
      'strings': {
        type: Object,
        default: () => ({
          'ALL': 'All'
        })
      }
    },
    computed: {
      classActive: function() {
        return {
          'active': this.terms.active,
          'inactive': !(this.terms.active)
        };
      },
      ariaControls: function() {
        return 'aria-c-' + this.terms.slug;
      },
      ariaLabelledBy: function() {
        return 'aria-lb-' + this.terms.slug;
      },
      current: function() {
        return (this.terms.current && this.terms.current != '')
          ? this.terms.current : this.terms.name;
      }
    },
    methods: {
      ariaActive: function(active) {
        return (active) ? 'true' : 'false';
      },
      tabindex: function(active) {
        return (active) ? false : '-1';
      },
      ariaPressed: function(name) {
        return (this.terms.current === name) ? 'true' : 'false';
      },
      fetch: function(event) {
        if (this.nav) event.event.preventDefault();
        this.$set(this.terms, 'current', event.data.name);
        this.$emit('fetch', event);
        return this;
      },
      reset: function(event) {
        this.$set(this.terms, 'current', '');
        this.$emit('reset', {
          event: event,
          data: {
            parent: this.terms.slug
          }
        });
        return this;
      },
      toggle: function(event) {
        event.preventDefault();
        this.$set(this.terms, 'active', !this.terms.active);
        return this;
      }
    }
  };
</script>
<template>
  <!-- eslint-disable max-len -->
  <div class="c-filter-multi">
    <ul class="c-filter-multi__list">
      <li class="c-filter-multi__item" v-for="t in terms" :key="t.term_id">
        <div v-if="t.checkbox" class="c-filter-multi__item-header">
          <label v-if="t.checkbox" class="checkbox">
            <input data-toggles="#" type="checkbox" :checked="t.checked" @change="fetch({'event': $event, 'data': {'parent': t.slug}})" />

            <span class="checkbox__label">{{ t.name }}</span>
          </label>

          <button type="button" class="c-filter-multi__item-header-toggle" :aria-controls="ariaControls(t.slug)" :aria-expanded="ariaActive(t.active)" :class="classActive(t)" @click="toggle($event, t)">
            <span :id="ariaLabelledBy(t.slug)" class="sr-only" v-html="t.name">{{ t.name }}</span>
          </button>
        </div>
        <button v-else type="button" class="c-filter-multi__item-header btn-link" :class="classActive(t)" :aria-controls="ariaControls(t.slug)" :aria-expanded="ariaActive(t.active)" @click="toggle($event, t)">
          <span :id="ariaLabelledBy(t.slug)" v-html="t.name">{{ t.name }}</span>

          <span class="c-filter-multi__item-header-toggle"></span>
        </button>

        <div role="region" :aria-labelledby="ariaLabelledBy(t.slug)" class="c-filter-multi__item-group" :aria-hidden="ariaActive(!t.active)" :class="classActive(t)" :id="ariaControls(t.slug)">
          <ul class="c-filter-multi__item-group-list">
            <li class='c-filter-multi__item-group-subitem' v-if="t.toggle">
              <button type='button' class='btn-link' @click="reset({'event': $event, 'data': {'parent': t.slug}})" v-html="strings.TOGGLE_ALL" :aria-hidden="ariaActive(!t.active)" :tabindex="tabindex(t.active)">Toggle All</button>
            </li>

            <li class="c-filter-multi__item-group-subitem" v-for="f in t.filters" :key="f.slug">
              <label class="checkbox" :tabindex="tabindex(t.active)">
                <input type="checkbox" :value="f.slug" :checked="f.checked" @change="fetch({'event': $event, 'data': f})" :tabindex="tabindex(t.active)"/>

                <span class="checkbox__label text-small font-normal" v-html="f.name">{{ f.name }}</span>
              </label>
            </li>
          </ul>
        </div>
      </li>
    </ul>
  </div>
</template>

<style>
  /* @import 'filter-mulit'; */
</style>

<script>
  export default {
    props: {
      'terms': {type: Array},
      'active': {type: Boolean},
      'strings': {
        type: Object,
        default: () => ({
          'ALL': 'All',
          'TOGGLE_ALL': 'Toggle All'
        })
      }
    },
    methods: {
      classActive: function(term) {
        return {
          'active': term.active,
          'inactive': !(term.active)
        };
      },
      ariaActive: function(active) {
        return (active) ? 'true' : 'false';
      },
      tabindex: function(active) {
        return (active) ? false : '-1';
      },
      ariaLabelledBy: function(slug) {
        return 'aria-l-' + slug;
      },
      ariaControls: function(slug) {
        return 'aria-c-' + slug;
      },
      fetch: function(event) {
        this.$set(event.data, 'checked', !event.data.checked);
        this.$emit('fetch', event);
        return this;
      },
      reset: function(event) {
        this.$emit('reset', event);
        return this;
      },
      toggle: function(event, terms) {
        event.preventDefault();
        this.$set(terms, 'active', !terms.active);
        return this;
      }
    }
  };
</script>

The Vue Filter and Vue Filter Multi can be imported from the paths below in your main script and added to the Vue instance before your application declaration:

import FilterVue from 'src/components/filter/filter.vue';
import FilterMultiVue from 'src/components/filter/filter-multi.vue';

Vue.component('c-filter', FilterVue);
Vue.component('c-filter-multi', FilterMultiVue);

new Vue();

Below is a guide for using these particular component properties. For basic details of using Vue Components within a Vue application, refer to the Vue.js documentation.

Props

Below is a description of accepted properties and their values.

Prop Type Description
:terms object/array Data for the filter list. A sample set of data can be seen here. The Vue Filter will only accept one terms object from the sample array. The Vue Multi Filter will accept an array of term objects.
:strings object A dictionary containing static strings used in the component. Below is a table containing the available strings.

Strings

Key Default Description
ALL All The prefix before the term name, ex; “All Programs”
TOGGLE_ALL Toggle All Text for the button that toggles all filters within a term group.

Events

The Vue Filter and Vue Filter Multi accepts two event properties that are emitted on click. Passing methods to these props provides hooks for the parent application. You can open the console log of this page to see demonstration logs for each event.

Key Params Description
@fetch event, data Internally this event will set the state of the currently selected filter in a group. The emitter can be used to “fetch” data when the term is clicked. Event is the original click event. Data contains the data bound to the clicked filter.
@reset event, data The emitter can be used to reset the filters data. Event is the original click event. Data contains the data bound to the clicked filter.