Search code examples
javascriptpolymerpolymer-1.0web-componentcustom-element

How do I create a search box like on codelabs.developers.google.com?


I'm using Polymer 1.8 (Starter Kit template). I want to know how to create a search filter custom element like the one in https://codelabs.developers.google.com/

Desirable result:

As you can see, it filters out the cards below it, with each keystroke typed in the search bar, leaving only the cards that are containing desirable search words.

I'd like for it to find the words in both:

  • the title of a <paper-card> (text in the heading)
  • and in the inner divs (description of a <paper-card>)

The only examples of the search box I found are this page from 2015 and this page of the Polymer Element Catalog, which is using a similar search box, but I couldn't adapt them to my custom elements.

Here's my-preview-cards custom element:

It contains the cards themselves:

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="shared-styles.html">

<dom-module id="my-preview-cards">
  <template>
    <style>
      /* local DOM styles go here */
      :host {
        display: inline-block;
      }
    </style>

    <div>
      <paper-card heading="Color picture" image="http://lorempixel.com/300/200">
        <div class="card-content">An RGB picture</div>
        <div class="card-actions">
          <paper-button>Button</paper-button>
        </div>
      </paper-card>

      <paper-card heading="Grey image" image="http://lorempixel.com/g/300/200">
        <div class="card-content">That's a grey picture</div>
        <div class="card-actions">
          <paper-button>Button</paper-button>
        </div>
      </paper-card>
    </div>

  </template>
  <script>
    Polymer({
      is: 'my-preview-cards',
    });
  </script>
</dom-module>

I've created a separate custom element my-search-bar for the search bar:

It contains the search bar:

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="shared-styles.html">

<dom-module id="my-search-bar">
  <template>
    <style>
      /* local DOM styles go here */
      :host {
        display: inline-block;
      }
    </style>

    <form on-submit="performSearch" class="flex">
      <paper-input id="query" value="{{query::keyup}}" type="search" placeholder="search"></paper-input>
   </form>

  </template>
  <script>
    Polymer({
      is: 'my-search-bar',
    });
  </script>
</dom-module>

Both custom elements are getting displayed on my-homepage as:

<div>
  <my-search-bar></my-search-bar>
</div>

<div>...</div>

<div>
  <my-preview-cards></my-preview-cards>
</div>

P.S.

I understand this is a complicated question. As soon as I get 75 rep, I will assign bounty of 50 to this question, and whoever provides the working solution gets it.


Solution

  • I think it's about manipulating the data. I doubt that you would want to manually create all those <paper-card>s if you have so much data, so I suggest using <dom-repeat> for that and filtering your data from the Array. You can see an example demo here.

    .search-box {
      display: flex;
      display: -webkit-flex;
      background-color: #fff;
      border: 1px solid #eee;
      box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
      height: 40px;
      width: 100%;
      align-items: center;
    }
    
    .search-box iron-icon {
      color: var(--google-grey-700);
      fill: var(--google-grey-700);
      margin-left: auto;
      right: 0;
    }
    
    .search-box input {
      font-size: 20px;
      outline: 0;
      border: none;
      padding-left: 10px;
      width: 86%;
    }
    
    .search-box {
      @apply(--layout-flex);
      @apply(--layout-center);
      @apply(--layout-horizontal);
    }
    
    .search-box input {
      @apply(--layout-flex);
    }
    <!DOCTYPE html>
    <html>
    
    <head>
      <meta name="description" content="polymer-search">
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width">
      <title>Polymer Search</title>
      <base href="https://polygit.org/polymer+1.6.0/components/">
      <link rel="import" href="iron-icon/iron-icon.html">
      <link rel="import" href="iron-icons/iron-icons.html">
      <link rel="import" href="iron-icons/av-icons.html">
      <link rel="import" href="paper-toolbar/paper-toolbar.html">
      <link rel="import" href="paper-input/paper-input.html">
    </head>
    
    <body>
      <dom-module id="my-app">
        <template>
          <style>
              ul {
                padding:20px 10px;
                list-style: none;
                display:flex;
                flex-flow:row;
                justify-content:space-between;
              }
              .item {
                width:25%;
                background-color: whitesmoke;
                padding:5px;
                margin:5px;
                display:flex;
                flex-flow:column;
              }
          </style>
          
          <paper-toolbar>
            <div class="search-box bottom">
              <input id="search" />
              <iron-icon icon="av:mic"></iron-icon>
            </div>
          </paper-toolbar>
          <ul>
            <template is="dom-repeat" items="[[data]]">
              <li class="item">
                <span>[[item.title]]</span>
                <p>[[item.description]]</p>
              </li>
            </template>
    
        </ul>
        </template>
        <script>
          Polymer({
            is: 'my-app',
            properties: {
              defaultData: {
                type: Array,
                value: [{
                    title: 'Color picture',
                    description: 'An RGB picture'
                  },
                  {
                    title: 'Grey image',
                    description: 'That\'s a grey picture'
                  },
                  {
                    title: '3',
                    description: 'this is content 3'
                  }
                ]
              },
              data: {
                type: Array
              }
            },
            ready: function() {
              this.data = this.defaultData;
              this.$.search.addEventListener('keyup', this.searchChanged.bind(this));
            },
            searchChanged: function(e) {
              var querySearch = this.$.search.value.toLowerCase();
              if (querySearch == '') {
                this.data = this.defaultData;
              } else {
                this.data = this.defaultData.filter(function(item) {
                  return item.title.toLowerCase().indexOf(querySearch) !== -1 || item.description.toLowerCase().indexOf(querySearch) !== -1;
                });
              }
            }
          })
        </script>
      </dom-module>
      <my-app></my-app>
    </body>
    
    </html>