YousefED / ElasticUI

AngularJS directives for Elasticsearch
http://www.elasticui.com
Other
526 stars 134 forks source link

Feature:Add autocomplete to the search box #27

Closed mastermind1981 closed 9 years ago

mastermind1981 commented 9 years ago

Hi,

First of all, i want to thank you for your effort to this project.

I was guessing if it is possible to have a new type of search box featured with the autocomplete.

To populate the list of suggestions there would be another attribute with the url of the suggestion list or the path of the list on the elasticsearch server .

In fact it will be a combination of the current search box with angucomplete-alt

The searchbox directive would be used like that :

  <eui-searchbox field="INDEX_FIELD" autocomplete-url="URL_FOR_SUGGESTION_DATA" trigger-at="NUMBER_OF_CHARACTER" start-after="MILLISECONDS" ></eui-searchbox>

Thanks again for your efforts.

YousefED commented 9 years ago

This is currently out of scope of the project (it's currently focused on the core-directives, not on prebuilt widgets). You're welcome to contribute this as a separate widget.

mastermind1981 commented 9 years ago

in fact,

I resolved the problem by using bootstrap typeahead and es for getting the data from the server. May be it should be documented with an example in the docs. I will try to add it

arbh89 commented 9 years ago

@mastermind1981, can you share the solution using typeahead?

Thanks in advance.

mastermind1981 commented 9 years ago

@arbh89 here is the html part:

     <div class="input-group col-md-offset-3 col-md-6" eui-query="ejs.BoolQuery()
                   .must(ejs.MultiMatchQuery('title',qs_model.after_click )
                   .type('phrase_prefix').zeroTermsQuery('all'))
                   .must(ejs.MatchQuery('publish', 'true'))" eui-enabled="qs_model.after_click.length > 0 || fireSearch">
          <input type="text" ng-model="qs_model.live" class="form-control" typeahead="tit for tit in autocomplete($viewValue) | filter:$viewValue | limitTo:8 " typeahead-template-url="ta-template.html" placeholder="Exemple: audi a3 2007" typeahead-min-length="2"/>
          <span class="input-group-btn">
 <button class="btn btn-md btn-info" ng-click="launchSearch()"><i class="fa fa-search fa-lg icon-magnifier"></i>&nbsp; Rechercher</button>
                </span>
        </div>```

It's a little bit ugly , but the idea is  when user enters nothing get all and when the user enters some launch autocomplete once done user clicks on search. For that I used typeahead from angular bootstrap.

In the Controller (I put all the code I use to handle the interaction with the search textfield not only the autcomplete):

```       $scope.autocomplete = function(text) {
          var titles = [];
          return searchService.autocomplete(text).then(function(res) {
            angular.forEach(res.hits.hits, function(hit) {
              var title = hit.fields.title;
              titles.push(title[0]);
            });
            return titles;
          });
        };

        $scope.fireSearch = function() {
          $scope.qs_model.after_click = $scope.qs_model.live;
        }
        $scope.qs_model = {
          live: '',
          after_click: ''
        };

      $scope.launchSearch = function() {
            $scope.qs_model.after_click = $scope.qs_model.live;
            if ($scope.qs_model.after_click.length === 0) {
              $scope.fireSearch = true;
              $timeout (function(){
                  $scope.fireSearch = false;
              }, 200);
            }

         } ;

In the service (I used the official elasticsearch library official js I think it's already shipped when you use elasticui otherwise you should bower it), I put only the method for autocomplete

angular.module('ad.module')
  .value('version', '1.0')
  .service('es', ['esFactory','euiHost',
    function(esFactory, euiHost) {
      return esFactory({
        hosts: [
          euiHost
        ],
        log: 'trace',
        sniffOnStart: false
      });
    }
  ])
  .factory('searchService', ['es', '$q','GLOBAL_CONST',
    function(es,$q, GLOBAL_CONST) {
      return {
        'autocomplete': function(text) {
          return es.client.search({
            index: GLOBAL_CONST.ES.index,
            body: {
              'fields': [
                'title'
              ],
              'query': {
                'query_string': {
                  'fields': [
                    'suggest'
                  ],
                  'query': text,
                  'use_dis_max': true,
                  'auto_generate_phrase_queries': true,
                  'default_operator': 'OR'
                }
              },'filter' :{
                   'term' : {
                      'publish' : true
                   }

              },
              'highlight': {
                'number_of_fragments': 0,
                'pre_tags': [
                  '<b>'
                ],
                'post_tags': [
                  '</b>'
                ],
                'fields': {
                  'title': {}
                }
              }
            }
          });
        },

Here is the mapping


POST /cars
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 1,
    "analysis": {
     "filter": {
            "edgeNGram_filter": {
               "type": "edgeNGram",
               "min_gram": 2,
               "max_gram": 40,
               "side" : "front",
               "token_chars": [
                  "letter",
                  "digit",
                  "punctuation",
                  "symbol"
               ]
            }
         },
      "analyzer": {
        "edgeNGram_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "standard",
            "lowercase",
            "asciifolding",
            "edgeNGram_filter"
          ]
        },
        "whitespace_analyzer": {
          "type": "custom",
          "tokenizer": "whitespace",
          "filter": [
            "standard",
            "lowercase",
            "asciifolding"
          ]
        }
      }
    }
  },
  "mappings": {
    "ads": {
      "_all": {
        "enabled": true
      },
      "_id" : {
                "path" : "id"
       },
      "properties": {
        "id": {
          "type": "long"
        },
        "category": {
          "type": "string",
          "index": "not_analyzed"
        },
        "title":{
             "type" : "string",
             "copy_to" : "suggest"
        },
"suggest": {
  "type": "string",
   "index_analyzer": "edgeNGram_analyzer",
   "search_analyzer": "whitespace_analyzer"

}

} }



  }
} ```

I hope it will help