Autocomplete sử dụng Typeahead và Searchkick trong Rails
Thư viện sử dụng Gem Searchkick cho việc tìm kiếm Gem ElasticSearch cho Full Text Search Thư viện javascript Typeahead cho việc autocomplete Cài đặt Searchkick Tạo 1 project Rails 5 và thêm vào gem Searchkick gem 'searchkick' Chạy lệnh bunlde và tạo 1 resource có tên là article rails ...
Thư viện sử dụng
- Gem Searchkick cho việc tìm kiếm
- Gem ElasticSearch cho Full Text Search
- Thư viện javascript Typeahead cho việc autocomplete
Cài đặt Searchkick Tạo 1 project Rails 5 và thêm vào gem Searchkick gem 'searchkick' Chạy lệnh bunlde và tạo 1 resource có tên là article rails g scaffold article title content:text Thêm searchkick và model article
class Article < ApplicationRecord searchkick end
Tạo database Tạo 1 vài database trong file seeds.rb:
Article.destroy_all data = [{ title: 'Star Wars', content: 'Wonderful adventure in the space' }, { title: 'Lord of the Rings', content: 'Lord that became a ring' }, { title: 'Man of the Rings', content: 'Lord that became a ring' }, { title: 'Woman of the Rings', content: 'Lord that became a ring' }, { title: 'Dog of the Rings', content: 'Lord that became a ring' }, { title: 'Daddy of the Rings', content: 'Lord that became a ring' }, { title: 'Mommy of the Rings', content: 'Lord that became a ring' }, { title: 'Duck of the Rings', content: 'Lord that became a ring' }, { title: 'Drug Lord of the Rings', content: 'Lord that became a ring' }, { title: 'Native of the Rings', content: 'Lord that became a ring' }, { title: 'Naysayer of the Rings', content: 'Lord that became a ring' }, { title: 'Tab Wars', content: 'Lord that became a ring' }, { title: 'Drug Wars', content: 'Lord that became a ring' }, { title: 'Cheese Wars', content: 'Lord that became a ring' }, { title: 'Dog Wars', content: 'Lord that became a ring' }, { title: 'Dummy Wars', content: 'Lord that became a ring' }, { title: 'Dummy of the Rings', content: 'Lord that became a ring' } ] Article.create(data)
Chạy migrate và chạy file seed:
rails db:migrate rails db:seed
Kết nối thử nghiệm với ElasticSearch Chỉ mục các dữ liệu article ở trong elasticsearch rake searchkick:reindex CLASS=Article Giờ đây chúng ta có thể dùng rails console để xác minh các tính năng tìm kiếm
> results = Article.search('War') Article Search (11.7ms) curl http://localhost:9200/articles_development/_search?pretty -d '{"query":{"dis_max":{"queries":[{"match":{"_all":{"query":"War","boost":10,"operator":"and","analyzer":"searchkick_search"}}},{"match":{"_all":{"query":"War","boost":10,"operator":"and","analyzer":"searchkick_search2"}}},{"match":{"_all":{"query":"War","boost":1,"operator":"and","analyzer":"searchkick_search","fuzziness":1,"prefix_length":0,"max_expansions":3,"fuzzy_transpositions":true}}},{"match":{"_all":{"query":"War","boost":1,"operator":"and","analyzer":"searchkick_search2","fuzziness":1,"prefix_length":0,"max_expansions":3,"fuzzy_transpositions":true}}}]}},"size":1000,"from":0,"fields":[]}' => #<Searchkick::Results:0x007fcf42475dd8 @klass=Article (call 'Article.connection' to establish a connection), @response={"took"=>9, "timed_out"=>false, "_shards"=>{"total"=>5, "successful"=>5, "failed"=>0}, "hits"=>{"total"=>6, "max_score"=>0.37037593, "hits"=>[{"_index"=>"articles_development_20160518103333170", "_type"=>"article", "_id"=>"16", "_score"=>0.37037593}, {"_index"=>"articles_development_20160518103333170", "_type"=>"article", "_id"=>"15", "_score"=>0.37037593}, {"_index"=>"articles_development_20160518103333170", "_type"=>"article", "_id"=>"12", "_score"=>0.3074455}, {"_index"=>"articles_development_20160518103333170", "_type"=>"article", "_id"=>"14", "_score"=>0.3074455}, {"_index"=>"articles_development_20160518103333170", "_type"=>"article", "_id"=>"1", "_score"=>0.21875}, {"_index"=>"articles_development_20160518103333170", "_type"=>"article", "_id"=>"13", "_score"=>0.21875}]}}, @options={:page=>1, :per_page=>1000, :padding=>0, :load=>true, :includes=>nil, :json=>false, :match_suffix=>"analyzed", :highlighted_fields=>[]}>
Chúng ta có thể kết nối tới máy elasticsearch server bằng việc sử dụng thư viện searchkick và lấy kết quả tìm kiếm
> results.class => Searchkick::Results
Kết quả là đối tượng Searchkick :: Results. Chúng ta có 6 data trong kết quả.
> results.size Article Load (0.4ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (16, 15, 12, 14, 1, 13) => 6
Tích hợp thư viện Typehead Hãy thêm form tìm kiếm vào trang index artciles
<%= form_tag articles_path, method: :get do %> <%= text_field_tag :query, params[:query], class: 'form-control' %> <%= submit_tag 'Search' %> <% end %>
Giờ bạn có thể tìm kiếm trong trang index nhưng không có autocomplete. Hãy cùng tạo tính năng autocomplete. Tải thư viện typehead.js version 0.11.1 và chuyển nó vào thư mục vendor/assets/javascripts. Thêm typehead.js vào trong application.js :
//= require typeahead
Thêm vào trong controller hàm tìm kiếm với autocomplete:
def autocomplete render json: Article.search(params[:query], autocomplete: true, limit: 10).map(&:title) end
Định nghĩa route:
Rails.application.routes.draw do resources :articles do get :autocomplete end end
Thêm id và thuộc tính autocomplete vào trong trường tìm kiếm ở trong trang index
<%= text_field_tag :query, params[:query], class: 'form-control', id: "article_search" %>
Tạo file xử lí article.js:
var ready; ready = function() { var engine = new Bloodhound({ datumTokenizer: function(d) { console.log(d); return Bloodhound.tokenizers.whitespace(d.title); }, queryTokenizer: Bloodhound.tokenizers.whitespace, remote: { url: '../articles/autocomplete?query=%QUERY', wildcard: '%QUERY' } }); var promise = engine.initialize(); promise .done(function() { console.log('success!'); }) .fail(function() { console.log('err!'); }); $('.typeahead').typeahead(null, { name: 'engine', displayKey: 'title', source: engine.ttAdapter() }); } $(document).ready(ready); $(document).on('page:load', ready);
Nếu bạn không cung cấp kí tự đại diện, bạn sẽ gặp lỗi như sau:
GET http://localhost:3000/search/autocomplete?query=%QUERY 400 (Bad Request)
Trong console của cửa sổ trình duyệt:
HTTP parse error, malformed request puma
Tách vấn đề Bạn dùng curl để cô lập các vấn đề cho front-end và back-end curl http://localhost:3000/articles?query='dog' Trong log, bạn có thể nhìn thấy:
Article Search (19.4ms) curl http://localhost:9200/articles_development/_search?pretty -d '{"query":{"dis_max":{"queries":[{"match":{"_all":{"query":"dog","boost":10,"operator":"and","analyzer":"searchkick_search"}}},{"match":{"_all":{"query":"dog","boost":10,"operator":"and","analyzer":"searchkick_search2"}}},{"match":{"_all":{"query":"dog","boost":1,"operator":"and","analyzer":"searchkick_search","fuzziness":1,"prefix_length":0,"max_expansions":3,"fuzzy_transpositions":true}}},{"match":{"_all":{"query":"dog","boost":1,"operator":"and","analyzer":"searchkick_search2","fuzziness":1,"prefix_length":0,"max_expansions":3,"fuzzy_transpositions":true}}}]}},"size":1000,"from":0,"fields":[]}' Rendering articles/index.html.erb within layouts/application
Đầu ra trong terminal:
<!DOCTYPE html> <html> <head> <title>Autoc</title> <meta name="csrf-param" content="authenticity_token" /> <meta name="csrf-token" content="O9rx6qf0ik6ae" /> <link rel="stylesheet" media="all" href="/assets/articles.self-e3b04b855.css?body=1" data-turbolinks-track="reload" /> <link rel="stylesheet" media="all" href="/assets/scaffolds.self-c8daf17deb4.css?body=1" data-turbolinks-track="reload" /> <link rel="stylesheet" media="all" href="/assets/application.self-a9e16886.css?body=1" data-turbolinks-track="reload" /> <script src="/assets/jquery.self-35bf4c.js?body=1" data-turbolinks-track="reload"></script> <script src="/assets/jquery_ujs.self-e87806d0cf4489.js?body=1" data-turbolinks-track="reload"></script> <script src="/assets/typeahead.self-7d0ec0be4d31a26122.js?body=1" data-turbolinks-track="reload"></script> <script src="/assets/turbolinks.self-979a09514ef27c8.js?body=1" data-turbolinks-track="reload"></script> <script src="/assets/articles.self-ca74ce155498e7f0.js?body=1" data-turbolinks-track="reload"></script> <script src="/assets/action_cable.self-97a1acc11db.js?body=1" data-turbolinks-track="reload"></script> <script src="/assets/cable.self-6e05142.js?body=1" data-turbolinks-track="reload"></script> <script src="/assets/application.self-afe802b04eaf.js?body=1" data-turbolinks-track="reload"></script> </head> <body> <p id="notice"></p> <form action="/articles" accept-charset="UTF-8" method="get"><input name="utf8" type="hidden" value="✓" /> <input type="text" name="query" id="article_search" value="dog" class="form-control" /> <input type="submit" name="commit" value="Search" data-disable-with="Search" /> </form> <h1>Articles</h1> <table> <thead> <tr> <th>Title</th> <th>Content</th> <th colspan="3"></th> </tr> </thead> <tbody> <tr> <td>Dog Wars</td> <td>Lord that became a ring</td> <td><a href="/articles/15">Show</a></td> <td><a href="/articles/15/edit">Edit</a></td> <td><a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/articles/15">Destroy</a></td> </tr> <tr> <td>Dog of the Rings</td> <td>Lord that became a ring</td> <td><a href="/articles/5">Show</a></td> <td><a href="/articles/5/edit">Edit</a></td> <td><a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/articles/5">Destroy</a></td> </tr> </tbody> </table> <a href="/articles/new">New Article</a> </body> </html>
đây không phải là autocomplete, nếu bạn tìm kiếm 1 cái gì đó bạn sẽ gặp lỗi: ActiveRecord::RecordNotFound (Couldn't find Article with 'id'=autocomplete): Bở vì chúng ta không định tuyến trong routes, hãy kiểm tra bằng rake routes article_autocomplete GET /articles/:article_id/autocomplete(.:format) articles#autocomplete route không đúng, giờ hãy sửa lại như sau:
Rails.application.routes.draw do resources :articles do collection do get :autocomplete end end end
Triển khai autocomplete Cấu hình autocomplete trong model article: searchkick autocomplete: ['title'] Triển khai autocomplete trong controller:
def autocomplete render json: Article.search(params[:query], autocomplete: false, limit: 10).map do |book| { title: book.title, value: book.id } end end
Bạn cần thêm class typehead và trong form tìm kiếm:
<%= text_field_tag :query, params[:query], class: 'form-control typeahead' %>
Giờ thì bạn hãy thử F5 trình duyệt và thử tìm kiếm, nó sẽ tự động autocomplete cho bạn Style Autocomplete DropDown Tạo typehead.scss và thêm vào:
.tt-query { -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } .tt-hint { color: #999 } .tt-menu { /* used to be tt-dropdown-menu in older versions */ awidth: 422px; margin-top: 4px; padding: 4px 0; background-color: #fff; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, 0.2); -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); box-shadow: 0 5px 10px rgba(0,0,0,.2); } .tt-suggestion { padding: 3px 20px; line-height: 24px; } .tt-suggestion.tt-cursor,.tt-suggestion:hover { color: #fff; background-color: #0097cf; } .tt-suggestion p { margin: 0; }
Bắt việc search ở trong trang index:
def index @articles = if params[:query].present? Article.search(params[:query]) else Article.all end end
Bây giờ bạn có thể nhìn thấy autocomplete với hiệu ứng đẹp mắt.