SOURCE

console 命令行工具 X clear

                    
>
console
(function() {
  // TODO: be more elegant here
  function format(text) {
    return text.replace(/ /g,'').replace(/(<([^>]+)>)/ig, '').toLowerCase();  
  }
  
  var SearchOnList = {
    $LIST           : '[data-search-on-list=list]',
    $SEARCH         : '[data-search-on-list=search]',
    $LIST_ITEM      : '[data-search-on-list=list-item]',
    $COUNTER        : '[data-search-on-list=counter]',
    TEMPLATE_EMTPY  : '<li class="list-item list-item--disable">No results found</li>', 
    
    
    init: function($element) {
      this.items = [];
      this.itemsMatched = [];
      
      this.$element = $element;
      this.$list = this.$element.find(this.$LIST);
      this.$search = this.$element.find(this.$SEARCH);
      this.$counter = this.$element.find(this.$COUNTER);
      
      this.items = this._getAllItems();
      this.itemsMatched = this.items;
      
      this._updateCounter();
      this._handleResults();
      this._setEventListeners();
    },
    
    _setEventListeners: function() {
      this.$search
        .on('keyup', $.proxy(this._onKeyup, this))
        .on('query:changed', $.proxy(this._handleQueryChanged, this))
        .on('query:results:some', $.proxy(this._handleResults, this))
        .on('query:results:none', $.proxy(this._handleNoResults, this))
    },
    
    _onKeyup: function() {
      var query = this.$search.val(),
          previousQuery = this.$search.data('previousQuery', query);
      
      // TODO: Decide when query actually changed
      if (this._queryChanged()) {
        this.$search.trigger('query:changed', { 
          query: query, 
          previousQuery: previousQuery 
        });
      }
    },
    
    _queryChanged: function() {
      var query = this.$search.val();
      if ($.trim(query).length === 0 && this.$search.data('previousQuery') === undefined) {
        return false;
      }
      return true;
    },
    
    _handleQueryChanged: function(e, data) {   
      this.itemsMatched = this.items.map(function(item) {
        if (format(item.name).match(format(data.query))) {
          return { 
            name: item.name, 
            visible: true 
          } 
        }
        return { 
          name: item.name, 
          visible: false
        } 
      });
      
      this._render();
      this._updateCounter();
    },
    
    _handleNoResults: function() {
      this.$list.html(this.TEMPLATE_EMTPY);
    },
    
    _handleResults: function() {
      this.$list.empty().append(this._renderItemsVisible())
    },
    
    _someItemsVisible: function() {
      return this.itemsMatched.some(function(item) { 
        return item.visible;
      });
    },
    
    _render: function() {
      (this._someItemsVisible()) ?
        this.$search.trigger('query:results:some') :
        this.$search.trigger('query:results:none'); 
    },
    
    _updateCounter: function() {
      (this._someItemsVisible()) ?
        this.$counter.text(this._renderItemsVisible().length) :
        this.$counter.text('');
    },
    
    _getAllItems: function() {
      var $items = this.$list.find(this.$LIST_ITEM);
      
      return $items.map(function() {
        var $item = $(this);
        
        return {
          name: $item.html(),
          visible: true
        };
      }).toArray();     
    },
    
    _renderItemsVisible: function() {
      var itemInTemplate;
      return this.itemsMatched.sort(function(a, b) {
        if (a.name < b.name) return -1
        if (a.name > b.name) return 1;
        return 0;
      }).reduce(function(items, item) {
        itemInTemplate = '<li class="list-item" data-search-on-list="list-item">' + item.name + '</li>';
        if (item.visible) {
          items.push(itemInTemplate);
        }  
        return items;
      }, []);
    }
  };
  
  window.SearchOnList = SearchOnList;
})();

SearchOnList.init($('[data-behaviour=search-on-list]'));
<div class="container" data-behaviour="search-on-list">
  <input type="text" class="input-query" data-search-on-list="search" placeholder="Search name or last name..."/>
  <span class="counter" data-search-on-list="counter"></span>
  <div class="list-wrap">
  <ul class="list" data-search-on-list="list">
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Ali <span class="item-list-subtext">Smith</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Alia <span class="item-list-subtext">Johnson</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Amira<span class="item-list-subtext">Johnson</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Omar<span class="item-list-subtext">Davis</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Aaron<span class="item-list-subtext">Davis</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Bailey <span class="item-list-subtext">Moore</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Noah<span class="item-list-subtext">Thomas</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Liam<span class="item-list-subtext">Garcia</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Dakota<span class="item-list-subtext">Garcia</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Olivia<span class="item-list-subtext">Taylor</span></a>
    </li>
    <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Mason<span class="item-list-subtext">Garcia</span></a>
    </li>
       <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Abigail<span class="item-list-subtext">Martin</span></a>
    </li>
       <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Emily<span class="item-list-subtext">Robinson</span></a>
    </li>
       <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Emily<span class="item-list-subtext">García</span></a>
    </li>
       <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">John<span class="item-list-subtext">Robinson</span></a>
    </li>
           <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Emily<span class="item-list-subtext">Clark</span></a>
    </li>
           <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Aurora<span class="item-list-subtext">Lewis</span></a>
    </li>
           <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Adeline<span class="item-list-subtext">Robinson</span></a>
    </li>
           <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">John<span class="item-list-subtext">García</span></a>
    </li>
               <li class="list-item" data-search-on-list="list-item">
      <a href="#" class="list-item-link">Isla<span class="item-list-subtext">Lewis</span></a>
    </li>
  </ul>
  </div>
</div>
$blue: #23242a;

$grey: #f2f3f6;
$light-grey: lighten($grey, 2%);
$dark-grey: darken($grey, 10%);

@keyframes shake {
   0%, 100% {transform: translateX(0);} 
   10%, 30%, 50%, 70% {transform: translateX(-5px);} 
   20%, 40%, 60%, 80% {transform: translateX(5px);} 
}


@mixin center {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  margin: auto;
}

* {
  box-sizing: border-box;
}

body {
  background: $light-grey;
  font-family: sans-serif;
  color: #333;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.container {
  @include center;
  width: 300px;
  height: 300px;
  background: $grey;
  padding: 1em;
  border: 1px solid $dark-grey;
  box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 5px 10px rgba(0,0,0,0.05);;
  border-radius: 3px;
  overflow: hidden;
}

.input-query {
  width: 100%;
  padding: 0.5em;
  border: 1px solid $dark-grey;
  border-radius: 3px;
  font-size: 1em;
  
  &:focus ~ .counter {
    opacity: 0.1;
    transition: opacity 1s ease-in;
  }
}

.list-wrap {
  margin-top: 0.4em;
  overflow-y: auto;
  overflow-x: hidden;
}

.list {
  max-height: 220px;
}

.list-item {
  font-size: 0.9em;
  padding: 0.5em 0.8em;
  border-bottom: 1px solid $dark-grey;
  border-top: 1px solid white;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  word-wrap: normal;
  max-width: 100%;
  
  &:first-child {
    border-top: none;
  }
  
  &:last-child {
    border-bottom: none;
  }
}

.list-item-link {
  color: #444;
  text-decoration: none;
}

.item-list-subtext {
  font-size: 85%;
  color: grey;
  &:before { content: ' ('}
  &:after { content: ')'}
}

.list-item--disable {
  text-align: center;
  border-bottom: none;
  animation: shake 0.6s;
  color: #9da1b1;
}

.counter {
  position: absolute;
  bottom: -15px;
  right: 10px;
  z-index: 0;
  font-size: 3.5em;
  color: black;
  transform: translateY(0);
  opacity: 0;
}