AMP Conf 2019. April 17/18. Tokyo.
AMP
  • websites

ComboBox

Introduction

This sample demonstrastes a classic dropdown combo, with a text field which filters the dropdown elements

according to the user's input. The options are rendered via amp-list.

<!doctype html>
<html >
<head>
  <title>ComboBox</title>
  <meta charset="utf-8">
  <link rel="canonical" href="http://localhost:8080/documentation/examples/interactivity-dynamic-content/combobox/index.html">

  <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
  <script async src="https://cdn.ampproject.org/v0.js"></script>

Setup

The amp-selector component is used for the user to select amongst the dropdown list options.

<script async custom-element="amp-selector" src="https://cdn.ampproject.org/v0/amp-selector-0.1.js"></script>

The amp-bind component is used for the filtering of the dropdown options according to the user's input.

<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>

The amp-list component is used to generate the dropdown list options.

<script async custom-element="amp-list" src="https://cdn.ampproject.org/v0/amp-list-0.1.js"></script>

The amp-mustache is used to generate the dropdown list options' template.

<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"></script>

Data source

First we need to add a data source. Here we're using an inline amp-state declaration defining a list of cities in the US.

For long lists of suggestions, it's possible to fetch amp-state from a remote JSON endpoint using the src attribute.

<amp-state id="cities">
  <script type="application/json">
    [
      "Albany, New York",
      "Annapolis, Maryland",
      "Atlanta, Georgia",
      "Augusta, Maine",
      "Austin, Texas",
      "Baton Rouge, Louisiana",
      "Bismarck, North Dakota",
      "Boise, Idaho",
      "Boston, Massachusetts",
      "Carson City, Nevada",
      "Charleston, West Virginia",
      "Cheyenne, Wyoming",
      "Columbia, South Carolina",
      "Columbus, Ohio",
      "Concord, New Hampshire",
      "Denver, Colorado",
      "Des Moines, Iowa",
      "Dover, Delaware",
      "Frankfort, Kentucky",
      "Harrisburg, Pennsylvania",
      "Hartford, Connecticut",
      "Helena, Montana",
      "Honolulu, Hawaii",
      "Indianapolis, Indiana",
      "Jackson, Mississippi",
      "Jefferson City, Missouri",
      "Juneau, Alaska",
      "Lansing, Michigan",
      "Lincoln, Nebraska",
      "Little Rock, Arkansas",
      "Madison, Wisconsin",
      "Montgomery, Alabama",
      "Montpelier, Vermont",
      "Nashville, Tennessee",
      "Oklahoma City, Oklahoma",
      "Olympia, Washington",
      "Phoenix, Arizona",
      "Pierre, South Dakota",
      "Providence, Rhode Island",
      "Raleigh, North Carolina",
      "Richmond, Virginia",
      "Sacramento, California",
      "Saint Paul, Minnesota",
      "Salem, Oregon",
      "Salt Lake City, Utah",
      "Santa Fe, New Mexico",
      "Springfield, Illinois",
      "Tallahassee, Florida",
      "Topeka, Kansas",
      "Trenton, New Jersey"
    ]
  </script>
</amp-state>

Filtering

We define a helper function to filter the cities based on a given text input as an amp-bind-macro.

<amp-bind-macro id="filterCities"
  arguments="query"
  expression="cities.filter( city => city.toLowerCase().indexOf(query.toLowerCase()) >= 0 )">
</amp-bind-macro>

The text input

The implementation of the combobox is based on the Autosuggest Sample. It takes an input field with auto suggestion and adds an additional button to expand and collapse the suggestions. We listen to the input-debounced event for text input. Based on the input, we filter the list of cities and write the result to a new amp-state variable filteredCities. We also bind the input value to a variable called city, which we'll use to set the value when the user clicks on a suggestion.

<input id="input-city"
  class="combo-input"
  [value]="city"
  on="input-debounced: AMP.setState({
                input: event.value,
                filteredCities: event.value.length > 0 ? filterCities(event.value) : []
             });
             change: AMP.setState( { state: event.value  })">

Combo button

The button will either show or hide the suggestions. The toggle state is tracked via the filteredCities variable.

<button class="combo-trigger"
  on="tap:AMP.setState({
           filteredCities: filteredCities.length > 0 ? [] : filterCities(input || '')
         })">
  <i class="arrow-down"
    [class]="filteredCities.length > 0 ? 'arrow-up' : 'arrow-down'"></i>
</button>

Suggestion box

We use amp-selector to make the suggestions selectable. When the user clicks on a suggestions, the select event will be fired and the selected city will be written to the city variable: city: event.targetOption. The filteredCities will be cleared, which also results in the suggestion box being hidden.

The list is initially hidden (height="0") and will only be shown when filteredCities is not empty ([height]="filteredCities.length * 34"), where 34 is the height in pixels of a single suggestion.

<amp-selector on="select:AMP.setState({
                    city: event.targetOption,
                    filteredCities: []
                 })"
  keyboard-select-mode="focus">
  <amp-list id="suggestions"
    items="."
    layout="fixed-height"
    height="0"
    [src]="filteredCities"
    [height]="filteredCities.length * 34">
    <template type="amp-mustache">
      <div option="{{.}}">{{.}}</div>
    </template>
  </amp-list>
</amp-selector>
Need further explanation?

If the explanations on this page don't cover all of your questions feel free to reach out to other AMP users to discuss your exact use case.

Go to Stack Overflow
An unexplained feature?

The AMP project strongly encourages your participation and contributions! We hope you'll become an ongoing participant in our open source community but we also welcome one-off contributions for the issues you're particularly passionate about.

Edit sample on GitHub