├── docs ├── .nojekyll ├── _navbar.md ├── images │ ├── banner.png │ └── exemple-ui.png ├── components │ ├── searchDataList.md │ ├── numericListFilter.md │ ├── searchButton.md │ ├── resetbutton.md │ ├── hits.md │ ├── tagfilter.md │ ├── paginate.md │ ├── searchbox.md │ └── refinementListFilter.md ├── _sidebar.md ├── index.html └── README.md ├── static └── .gitkeep ├── _config.yml ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── cypress ├── videos │ └── zu4gw.mp4 ├── screenshots │ └── Test RLF with Searchbox -- 2nd non regression test for issue 4.png ├── plugins │ └── index.js ├── support │ ├── index.js │ └── commands.js ├── fixtures │ ├── searchbox_2.json │ ├── searchbox_3.json │ ├── refinementFilterList_1.json │ └── searchbox_1.json └── integration │ ├── searchbox.js │ ├── resetbutton.js │ └── refinementFilter.js ├── cypress.json ├── .editorconfig ├── .postcssrc.js ├── index.html ├── .gitignore ├── server └── server.js ├── .babelrc ├── src ├── lib │ ├── functions │ │ └── prefix-tester.js │ ├── Enums.js │ ├── Store.js │ └── Generics.js ├── components │ ├── SearchButton.vue │ ├── ResetButton.vue │ ├── Hits.vue │ ├── NumericListFilter.vue │ ├── Paginate.vue │ ├── TagFilter.vue │ ├── SearchBox.vue │ ├── RefinementListFilter.vue │ └── SearchDatalist.vue ├── main.js ├── innerSearch.js └── style.css ├── book.json ├── .github └── CONTRIBUTING.md ├── LICENSE ├── .travis.yml ├── examples ├── Test_SearchBox.vue ├── Test_multipleRLF.vue ├── Demo.vue ├── Test_RefinementListFilter.vue ├── Immo.vue ├── Test_RefinementListFilter2.vue ├── Test_RLF_Searchbox.vue ├── standalone │ ├── default-innersearch-theme.min.css │ └── index.html ├── Test_ResetButton.vue └── Index.vue ├── package.json ├── README.md └── default-innersearch-theme.min.css /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | - [Demo](http://vue-innersearch.surge.sh/) 2 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /docs/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InnerSearch/vue-innersearch/HEAD/docs/images/banner.png -------------------------------------------------------------------------------- /cypress/videos/zu4gw.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InnerSearch/vue-innersearch/HEAD/cypress/videos/zu4gw.mp4 -------------------------------------------------------------------------------- /docs/components/searchDataList.md: -------------------------------------------------------------------------------- 1 | ### SearchDataList 2 | - **Tag name :** `` 3 | -------------------------------------------------------------------------------- /docs/images/exemple-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InnerSearch/vue-innersearch/HEAD/docs/images/exemple-ui.png -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectId": "16yk6o", 3 | "baseUrl" : "http://localhost:4000", 4 | "chromeWebSecurity": false 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"', 7 | PORT : '4000' 8 | }) 9 | -------------------------------------------------------------------------------- /cypress/screenshots/Test RLF with Searchbox -- 2nd non regression test for issue 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InnerSearch/vue-innersearch/HEAD/cypress/screenshots/Test RLF with Searchbox -- 2nd non regression test for issue 4.png -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | InnerSearch 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | tmp.json 16 | tmp1.json 17 | -------------------------------------------------------------------------------- /docs/components/numericListFilter.md: -------------------------------------------------------------------------------- 1 | ### NumericListFilter 2 | - **Tag name :** `` 3 | - **Properties :** 4 | - `field` (_String_) : Name of the field on which you want to perform range filter 5 | 6 | ```html 7 | 8 | ``` 9 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const express = require("express"); 3 | 4 | var app = express(); 5 | const serverPath = path.join(__dirname, "..", "dist"); 6 | 7 | app.use(express.static(serverPath)); 8 | 9 | app.get("*", (req, res) => { 10 | res.sendfile(path.join(serverPath, "index.html")); 11 | }) 12 | 13 | app.listen(3000) 14 | -------------------------------------------------------------------------------- /docs/components/searchButton.md: -------------------------------------------------------------------------------- 1 | ### SearchButton 2 | - **Tag name :** `` 3 | - **Property :** 4 | - `text` (_string_, default : _'Search'_) : Text displayed into the input button 5 | - **Description :** 6 | Create a button that display the hits when the user clicks on. 7 | 8 | ```html 9 | 10 | ``` 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/functions/prefix-tester.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prefix tester 3 | * Test if there are words begin with a prefix in a sentence 4 | * @param prefix: String 5 | * @param sentence: String 6 | * @returns Regexp matching result 7 | */ 8 | 9 | export default (prefix, sentence) => { 10 | const reg = (word => new RegExp(`(^${word}[^ ]* *)|( +${word})[^ ]* *`, "gi"))(prefix) 11 | return sentence.match(reg) 12 | } -------------------------------------------------------------------------------- /src/lib/Enums.js: -------------------------------------------------------------------------------- 1 | export const Component = Object.freeze({ 2 | 'SEARCHBOX' : 'Searchbox', 3 | 'SEARCH_DATALIST' : 'SearchDatalist', 4 | 'REFINEMENT_LIST_FILTER' : 'Refinement-List-Filter', 5 | 'PAGINATE' : 'Paginate', 6 | 'SEARCH_BUTTON' : 'SearchButton', 7 | 'RESET_BUTTON' : 'ResetButton', 8 | 'TAG_FILTER' : 'TagFilter', 9 | 'HITS' : 'Hits', 10 | 'NUMERIC_LIST_FILTER' : 'Numeric-List-Filter' 11 | }); 12 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": "3.x.x", 3 | "root":"docs", 4 | "structure": { 5 | "readme": "introduction.md", 6 | "summary": "README.md", 7 | "glossary":"glossary.md" 8 | }, 9 | "plugins": ["mermaid-2", "prism", "-highlight", "-search", "versions"], 10 | "pluginsConfig": { 11 | "versions": { 12 | "type": "tags" 13 | }, 14 | "prism": { 15 | "lang": [ 16 | "flow","typescript" 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Introduction](README.md) 2 | - [Components]() 3 | - [SearchBox](components/searchbox.md) 4 | - [RefinementListFilter](components/refinementListFilter.md) 5 | - [SearchButton](components/searchButton.md) 6 | - [Hits](components/hits.md) 7 | - [Paginate](components/paginate.md) 8 | - [SearchDataList](components/searchDataList.md) 9 | - [NumericListFilter](components/numericListFilter.md) 10 | - [ResetButton](components/resetbutton.md) 11 | - [TagFilter](components/tagfilter.md) 12 | -------------------------------------------------------------------------------- /docs/components/resetbutton.md: -------------------------------------------------------------------------------- 1 | ### ResetButton 2 | - **Tag name :** `` 3 | - **Property :** 4 | - `text` (_String_, default : _'Search'_) : Text displayed into the input button 5 | - `emptyHits` (_Boolean_, default : _true_) : Hide every hits displayed in corresponding components 6 | - **Description :** 7 | Create a button that reset the entire form when the user clicks on. 8 | 9 | ```html 10 | 11 | 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | } 18 | -------------------------------------------------------------------------------- /docs/components/hits.md: -------------------------------------------------------------------------------- 1 | ### Hits 2 | - **Tag name :** `` 3 | - **Overriding template:** You can override the display like this : 4 | 5 | ```html 6 | 7 | 10 | 11 | ``` 12 | 13 | Examples : 14 | 15 | ```html 16 | 17 | 27 | 28 | ``` 29 | 30 | 31 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Innersearch Contributing Guide 2 | Hi there, we are really glad that you are interested in contributing to InnerSearch. 3 | 4 | If you have any questions, proposal of new feature or bug to report just create a new [issue.](https://github.com/InnerSearch/vue-innersearch/issues) 5 | 6 | 7 | ## Pull Request Guidelines 8 | 9 | - The `master` branch is basically just a snapshot of the latest stable release. All development should be done in dedicated branches. **Do not submit PRs against the `master` branch.** 10 | 11 | - Checkout a topic branch from the relevant branch, e.g. `dev`, and merge back against that branch. 12 | 13 | - Work in the `src` folder and **DO NOT** checkin `dist` in the commits. 14 | 15 | - Make sure `npm run cypress:run` passes. 16 | -------------------------------------------------------------------------------- /docs/components/tagfilter.md: -------------------------------------------------------------------------------- 1 | ### TagFilter 2 | - **Tag name :** `` 3 | - **Property :** 4 | - `for` (_String_ or _Number_) : Targeted component 5 | - `clearAll` (_boolean_, default : _false_) : An optional value to specify if you want to link each item of the component to one tag filter, or if you to reset all items by clicking on the tag filter 6 | - **Description :** 7 | By adding an 'id' property on any dynamic component, you can link a reactive tag filter that allow you to reset entirely or partially the component. 8 | 9 | 10 | ```html 11 | 12 | 13 | 14 | 15 | 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Please wait...
11 | 12 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/components/paginate.md: -------------------------------------------------------------------------------- 1 | ### Paginate 2 | 3 | - **Tag name :** `` 4 | - **Properties :** 5 | - `size` (_Number_, default : _10_) : Number of results that will be returned from ElasticSearch 6 | - `page` (_Number_, default : _0_) : Number of the page currently displayed 7 | - `unpiled` (_Number_, default : _5_) : Maximum number of the page displayed both left and right of the current page 8 | - `previousText` (_String_, default : _Previous_) : Text displayed on the "previous" button 9 | - `nextText` (_String_, default : _Next_) : Text displayed on the "next" button 10 | 11 | - **Examples :** 12 | 13 | - **Description :** 14 | A component to navigate between results pages. 15 | 16 | 17 | 18 | ```html 19 | 20 | ``` 21 | -------------------------------------------------------------------------------- /cypress/fixtures/searchbox_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "took":0, 3 | "timed_out":false, 4 | "_shards":{ 5 | "total":5, 6 | "successful":5, 7 | "skipped":0, 8 | "failed":0 9 | }, 10 | "hits":{ 11 | "total":1, 12 | "max_score":0, 13 | "hits":[ 14 | { 15 | "_index":"bank", 16 | "_type":"account", 17 | "_id":"855", 18 | "_score":0, 19 | "_source":{ 20 | "account_number":855, 21 | "balance":40170, 22 | "firstname":"Mia", 23 | "lastname":"Stevens", 24 | "age":31, 25 | "gender":"F", 26 | "address":"326 Driggs Avenue", 27 | "employer":"Aeora", 28 | "email":"miastevens@aeora.com", 29 | "city":"Delwood", 30 | "state":"IL" 31 | } 32 | } 33 | ] 34 | } 35 | } -------------------------------------------------------------------------------- /cypress/fixtures/searchbox_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "took":0, 3 | "timed_out":false, 4 | "_shards":{ 5 | "total":5, 6 | "successful":5, 7 | "skipped":0, 8 | "failed":0 9 | }, 10 | "hits":{ 11 | "total":1, 12 | "max_score":0, 13 | "hits":[ 14 | { 15 | "_index":"bank", 16 | "_type":"account", 17 | "_id":"793", 18 | "_score":0, 19 | "_source":{ 20 | "account_number":793, 21 | "balance":16911, 22 | "firstname":"Alford", 23 | "lastname":"Compton", 24 | "age":36, 25 | "gender":"M", 26 | "address":"186 Veronica Place", 27 | "employer":"Zyple", 28 | "email":"alfordcompton@zyple.com", 29 | "city":"Sugartown", 30 | "state":"AK" 31 | } 32 | } 33 | ] 34 | } 35 | } -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /docs/components/searchbox.md: -------------------------------------------------------------------------------- 1 | ### SearchBox 2 | 3 | - **Tag name :** `` 4 | - **Properties :** 5 | - `autofocus` (_Boolean_, default : _false_) : Cursor focusing on the input by default 6 | - `realtime` (_Boolean_, default : _false_) : Performing ES request on every input change 7 | - `timeout` (_Number_, default : _300_) : Timeout between to ES request (available only if realtime is true) 8 | - `field` (_String_ or _Array_) : A string or an array of elasticsearch fields to search within 9 | - `operator` (_String_, default : _'OR'_) : Logical operator applied between several fields 10 | - `placeholder` (_String_, default : _'Search'_): Placeholder for the input box 11 | 12 | - **Examples :** 13 | 14 | ```html 15 | 16 | 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 InnerSearch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/SearchButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - npm i -g npm && npm install 3 | 4 | before_script: 5 | ## runs the 'start' script which 6 | ## boots our local app server on port 8080 7 | ## which cypress expects to be running 8 | ## ----------------------------------- 9 | ## the '-- --silent' passes arguments 10 | ## to http-server which silences its output 11 | ## else our travis logs would be cluttered 12 | ## with output from HTTP requests 13 | ## https://docs.npmjs.com/cli/start 14 | ## https://github.com/indexzero/http-server 15 | ## --------------------------------------- 16 | ## we use the '&' ampersand which tells 17 | ## travis to run this process in the background 18 | ## else it would block execution and hang travis 19 | - npm run dev -- --silent & 20 | 21 | script: 22 | ## now run cypress headlessly 23 | ## and record all of the tests. 24 | ## Cypress will search for a 25 | ## CYPRESS_RECORD_KEY environment 26 | ## variable by default and apply 27 | ## this to the run. 28 | - $(npm bin)/cypress run --record --key 0ae8477a-1492-4fcd-bffc-cf84b29700f1 29 | 30 | ## alternatively we could specify 31 | ## a specific record key to use 32 | ## like this without having to 33 | ## configure environment variables 34 | ## - cypress run --record --key 35 | notifications: 36 | slack: calosearch:laHm03qpWkLRHO7jU2E4ZNXy 37 | webhooks: 38 | - https://discordapp.com/api/webhooks/372840550233276416/Dpme3s10AkJ3o5v4K5Dyg60wJvyhiwSVS4TEFCl33DpOOdQuzg5dzHFPf8ogw1WDAEg- 39 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | Vue.use(VueRouter); 4 | 5 | // Routes definitions 6 | const Index = () => import('../examples/Index.vue'); 7 | const Immo = () => import('../examples/Immo.vue'); 8 | const Test_SearchBox = () => import('../examples/Test_SearchBox.vue'); 9 | const Test_RefinementListFilter = () => import('../examples/Test_RefinementListFilter.vue'); 10 | const Test_RefinementListFilter2 = () => import('../examples/Test_RefinementListFilter2.vue'); 11 | const Test_multipleRLF = () => import('../examples/Test_multipleRLF.vue'); 12 | const Test_multipleRLF_Searchbox = () => import('../examples/Test_RLF_Searchbox.vue'); 13 | const Test_ResetButton = () => import('../examples/Test_ResetButton.vue'); 14 | 15 | 16 | 17 | // Routes binding with a specific page 18 | const routes = [ 19 | { path : '/index', component : Index }, 20 | { path : '/immo', component : Immo }, 21 | { path : '/test_searchbox', component : Test_SearchBox }, 22 | { path : '/test_refinementListFilter', component : Test_RefinementListFilter }, 23 | { path : '/test_refinementListFilter2', component : Test_RefinementListFilter2 }, 24 | { path : '/test_multipleRLF', component : Test_multipleRLF }, 25 | { path : '/test_RLF_With_Searchbox', component : Test_multipleRLF_Searchbox }, 26 | { path : '/test_resetbutton', component : Test_ResetButton }, 27 | 28 | { path : '*', redirect : 'index' } 29 | ]; 30 | 31 | // Mount routes 32 | const router = new VueRouter({ routes }); 33 | new Vue({ router }).$mount('#innerSearch'); 34 | -------------------------------------------------------------------------------- /src/components/ResetButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/innerSearch.js: -------------------------------------------------------------------------------- 1 | import Generics from './lib/Generics'; 2 | import Searchbox from './components/SearchBox'; 3 | import SearchDatalist from './components/SearchDatalist'; 4 | import RefinementListFilter from './components/RefinementListFilter'; 5 | import Paginate from './components/Paginate'; 6 | import SearchButton from './components/SearchButton'; 7 | import ResetButton from './components/ResetButton'; 8 | import TagFilter from './components/TagFilter'; 9 | import Hits from './components/Hits'; 10 | import NumericListFilter from "./components/NumericListFilter" 11 | 12 | const InnerSearch = { 13 | Searchbox, 14 | SearchDatalist, 15 | Hits, 16 | RefinementListFilter, 17 | SearchButton, 18 | ResetButton, 19 | TagFilter, 20 | Paginate, 21 | NumericListFilter, 22 | 23 | install(Vue, options) { 24 | Vue.component('searchbox', Searchbox); 25 | Vue.component('search-datalist', SearchDatalist); 26 | Vue.component('refinement-list-filter', RefinementListFilter); 27 | Vue.component('paginate', Paginate); 28 | Vue.component('search-button', SearchButton); 29 | Vue.component('reset-button', ResetButton); 30 | Vue.component('tag-filter', TagFilter); 31 | Vue.component('hits', Hits); 32 | Vue.component("numeric-list-filter", NumericListFilter); 33 | 34 | Vue.mixin(Generics); 35 | } 36 | }; 37 | 38 | export default InnerSearch; 39 | 40 | export { 41 | Searchbox, 42 | SearchDatalist, 43 | RefinementListFilter, 44 | Paginate, 45 | SearchButton, 46 | ResetButton, 47 | TagFilter, 48 | Hits, 49 | NumericListFilter, 50 | Generics 51 | }; 52 | -------------------------------------------------------------------------------- /src/components/Hits.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict' 3 | // Template version: 1.1.3 4 | // see http://vuejs-templates.github.io/webpack for documentation. 5 | 6 | const path = require('path') 7 | 8 | module.exports = { 9 | build: { 10 | env: require('./prod.env'), 11 | index: path.resolve(__dirname, '../dist/index.html'), 12 | assetsRoot: path.resolve(__dirname, '../dist'), 13 | assetsSubDirectory: 'static', 14 | assetsPublicPath: '/', 15 | productionSourceMap: true, 16 | // Gzip off by default as many popular static hosts such as 17 | // Surge or Netlify already gzip all static assets for you. 18 | // Before setting to `true`, make sure to: 19 | // npm install --save-dev compression-webpack-plugin 20 | productionGzip: false, 21 | productionGzipExtensions: ['js', 'css'], 22 | // Run the build command with an extra argument to 23 | // View the bundle analyzer report after build finishes: 24 | // `npm run build --report` 25 | // Set to `true` or `false` to always turn it on or off 26 | bundleAnalyzerReport: process.env.npm_config_report 27 | }, 28 | dev: { 29 | env: require('./dev.env'), 30 | port: process.env.PORT || 8080, 31 | autoOpenBrowser: true, 32 | assetsSubDirectory: 'static', 33 | assetsPublicPath: '/', 34 | proxyTable: {}, 35 | // CSS Sourcemaps off by default because relative paths are "buggy" 36 | // with this option, according to the CSS-Loader README 37 | // (https://github.com/webpack/css-loader#sourcemaps) 38 | // In our experience, they generally work as expected, 39 | // just be aware of this issue when enabling this option. 40 | cssSourceMap: false 41 | }, 42 | lib: { 43 | env: require('./prod.env'), 44 | assetsRoot: path.resolve(__dirname, '../dist'), 45 | assetsSubDirectory: 'lib', 46 | assetsPublicPath: '/', 47 | productionSourceMap: true, 48 | productionGzip: false, 49 | productionGzipExtensions: ['js', 'css'], 50 | bundleAnalyzerReport: process.env.npm_config_report 51 | }, 52 | } 53 | -------------------------------------------------------------------------------- /examples/Test_SearchBox.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 55 | -------------------------------------------------------------------------------- /cypress/integration/searchbox.js: -------------------------------------------------------------------------------- 1 | import searchbox_sample_1 from '../fixtures/searchbox_1.json'; 2 | import searchbox_sample_2 from '../fixtures/searchbox_2.json'; 3 | import searchbox_sample_3 from '../fixtures/searchbox_3.json'; 4 | 5 | const 6 | _URL = 'http://localhost:4000/#/test_searchbox', 7 | _ES_URL = '**/_search', 8 | _SEARCHBOX = '.is-field.is-searchbox', 9 | _BUTTON = '.is-button.is-search-button', 10 | _HITS = '.is-score.is-hits', 11 | _ITEMS = 'div.hit', 12 | 13 | _FIRSTNAME = '.firstname', 14 | _LASTNAME = '.lastname', 15 | _STATE = '.state', 16 | _GENDER = '.gender'; 17 | 18 | function LoopForHits(sample) { 19 | cy.get(_ITEMS).each((item, index) => { 20 | let _currentHit = sample.hits.hits[index]._source; 21 | cy.wrap(item).find(_FIRSTNAME).contains(_currentHit.firstname); 22 | cy.wrap(item).find(_LASTNAME).contains(_currentHit.lastname); 23 | cy.wrap(item).find(_STATE).contains(_currentHit.state); 24 | cy.wrap(item).find(_GENDER).contains(_currentHit.gender); 25 | }); 26 | } 27 | 28 | describe('Test SearchBox with basic submit button', () => { 29 | beforeEach(function() { 30 | cy.visit(_URL); 31 | cy.server(); 32 | cy.route('POST', _ES_URL).as('ES'); 33 | }); 34 | 35 | it('Field is focused by default' , function() { 36 | cy.focused().should('have.class', 'is-searchbox').and('have.class', 'is-field'); 37 | }); 38 | 39 | it('Empty field returns 1002 hits' , function() { 40 | cy.get(_BUTTON).click(); 41 | cy.get(_HITS).contains('1002 results found'); 42 | }); 43 | 44 | it('Hits results for : s', function() { 45 | cy.get(_SEARCHBOX).type('s'); 46 | cy.wait('@ES'); 47 | cy.get(_HITS).contains('73 results found'); 48 | LoopForHits(searchbox_sample_1); 49 | }); 50 | 51 | it('Hits results for : mia', function() { 52 | cy.get(_SEARCHBOX).type('mia'); 53 | cy.wait('@ES'); 54 | cy.get(_HITS).contains('1 result found'); 55 | LoopForHits(searchbox_sample_2); 56 | }); 57 | 58 | it('Hits results for : alford', function() { 59 | cy.get(_SEARCHBOX).type('alford'); 60 | cy.wait('@ES'); 61 | cy.get(_HITS).contains('1 result found'); 62 | LoopForHits(searchbox_sample_3); 63 | }); 64 | 65 | it('No case sensitive', function() { 66 | cy.get(_SEARCHBOX).type('MiA'); 67 | cy.wait('@ES'); 68 | cy.get(_HITS).contains('1 result found'); 69 | LoopForHits(searchbox_sample_2); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /examples/Test_multipleRLF.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-innersearch", 3 | "version": "0.0.12", 4 | "description": "Vue.js components for ElasticSearch", 5 | "main": "innersearch.js", 6 | "author": "InnerSearch Organization", 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js", 11 | "lint": "eslint --ext .js,.vue src", 12 | "serve": "node server/server.js", 13 | "cypress:open": "cypress open", 14 | "cypress:run": "cypress run", 15 | "test": "npm run cypress:open", 16 | "build:lib": "node build/build-lib.js" 17 | }, 18 | "dependencies": { 19 | "babel-polyfill": "^6.26.0", 20 | "bodybuilder": "^2.2.10", 21 | "debounce": "^1.1.0", 22 | "elasticsearch": "^13.3.1", 23 | "elasticsearch-browser": "^13.3.1", 24 | "vue-router": "^3.0.1", 25 | "vuex": "^3.0.1", 26 | "vue": "^2.5.2" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "^7.2.6", 30 | "babel-core": "^6.26.3", 31 | "babel-eslint": "^7.1.1", 32 | "babel-loader": "^7.1.4", 33 | "babel-plugin-transform-runtime": "^6.22.0", 34 | "babel-preset-env": "^1.3.2", 35 | "babel-preset-stage-2": "^6.22.0", 36 | "babel-register": "^6.22.0", 37 | "chalk": "^2.4.1", 38 | "connect-history-api-fallback": "^1.5.0", 39 | "copy-webpack-plugin": "^4.5.1", 40 | "css-loader": "^0.28.11", 41 | "cypress": "^2.1.0", 42 | "eventsource-polyfill": "^0.9.6", 43 | "express": "^4.16.3", 44 | "extract-text-webpack-plugin": "^3.0.2", 45 | "file-loader": "^1.1.11", 46 | "friendly-errors-webpack-plugin": "^1.7.0", 47 | "html-webpack-plugin": "^2.30.1", 48 | "http-proxy-middleware": "^0.17.3", 49 | "node-sass": "^4.9.0", 50 | "opn": "^5.3.0", 51 | "optimize-css-assets-webpack-plugin": "^3.2.0", 52 | "ora": "^1.4.0", 53 | "portfinder": "^1.0.13", 54 | "rimraf": "^2.6.0", 55 | "sass-loader": "^6.0.7", 56 | "semver": "^5.5.0", 57 | "shelljs": "^0.7.6", 58 | "style-loader": "^0.20.3", 59 | "uglifyjs-webpack-plugin": "^1.2.5", 60 | "url-loader": "^0.5.8", 61 | "vue-loader": "^13.7.1", 62 | "vue-style-loader": "^3.1.2", 63 | "vue-template-compiler": "^2.5.16", 64 | "webpack": "^3.11.0", 65 | "webpack-bundle-analyzer": "^2.11.1", 66 | "webpack-dev-middleware": "^1.12.2", 67 | "webpack-hot-middleware": "^2.22.1", 68 | "webpack-merge": "^4.1.2" 69 | }, 70 | "engines": { 71 | "node": ">= 8.0.0", 72 | "npm": ">= 5.0.0" 73 | }, 74 | "license": "MIT", 75 | "browserslist": [ 76 | "> 1%", 77 | "last 2 versions", 78 | "not ie <= 8" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /examples/Demo.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Banner](https://raw.githubusercontent.com/InnerSearch/vue-innersearch/master/docs/images/banner.png)]() 2 | 3 | # InnerSearch : Vue.js components for ElasticSearch 4 | [![npm](https://img.shields.io/npm/v/vue-innersearch.svg)](https://www.npmjs.com/package/vue-innersearch) 5 | [![npm](https://img.shields.io/npm/dm/vue-innersearch.svg)](https://www.npmjs.com/package/vue-innersearch) 6 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FInnerSearch%2Fvue-innersearch.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FInnerSearch%2Fvue-innersearch?ref=badge_shield) 7 | [![Build Status](https://travis-ci.org/InnerSearch/vue-innersearch.svg?branch=master)](https://travis-ci.org/InnerSearch/vue-innersearch) 8 | [![npm](https://img.shields.io/npm/l/vue-innersearch.svg)]() 9 | 10 | 11 | ## What is InnerSearch ? 12 | An Open Source project created to help developers working with vue.js and Elastic, give them the possibility to create search UIs within the hour. 13 | 14 | InnerSearch is a suite of UI components like SearchBox, RefinementListFilter, Paginator and many others to come built with Vue.js. 15 | 16 | The aim is to rapidly create beautiful specified search interfaces using declarative components without being an ElasticSearch and Vue.js expert. 17 | 18 | Thanks too component props and slot features from Vue.js, the components are easily customizable 19 | 20 | An UI example buit with InnerSearch : 21 | [![Exemple UI](https://raw.githubusercontent.com/InnerSearch/vue-innersearch/master/docs/images/exemple-ui.png)]() 22 | 23 | Corresponding code : 24 | ```html 25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 | 41 | 42 |
43 |
44 |
45 | ``` 46 | 47 | ## Quick Start 48 | Checkout innersearch starter app [starter app](https://github.com/TrimA74/innerSearch-starter-app) 49 | 50 | See full [Documentation](https://innersearch.github.io/vue-innersearch) 51 | ### Installing via NPM 52 | ```bash 53 | $ npm install --save vue-innersearch 54 | ``` 55 | 56 | ### Using as ` 61 | 62 | 63 | ```` 64 | 65 | Have a look at how to use the standalone UMD build in our [Jsbin example](http://jsbin.com/gayugup/edit?html,output) 66 | 67 | ## Quick Intro 68 | 69 | [Live demo](http://vue-innersearch.surge.sh/) 70 | 71 | 72 | 73 | 74 | 75 | ## License 76 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FInnerSearch%2Fvue-innersearch.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FInnerSearch%2Fvue-innersearch?ref=badge_large) 77 | -------------------------------------------------------------------------------- /examples/Test_RefinementListFilter.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # InnerSearch : Vue.js components for ElasticSearch 2 | 3 | ## 1 Introduction 4 | ### 1.1 What is InnerSearch ? 5 | 6 | An Open Source project created to help developers working with vue.js and Elastic, give them the possibility to create search UIs within the hour. 7 | 8 | InnerSearch is a suite of UI components like SearchBox, RefinementListFilter, Paginator and many others to come built with Vue.js. 9 | 10 | The aim is to rapidly create beautiful specified search interfaces using declarative components without being an ElasticSearch and Vue.js expert. 11 | 12 | Thanks too component props and slot features from Vue.js, the components are easily customizable 13 | 14 | An UI example buit with InnerSearch : 15 | [![Exemple UI](https://raw.githubusercontent.com/InnerSearch/vue-innersearch/master/docs/images/exemple-ui.png)]() 16 | 17 | Corresponding code : 18 | ```html 19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 | 31 | 32 |
33 | 34 | 35 | 36 |
37 |
38 |
39 | ``` 40 | 41 | ## 2 Installation 42 | ### Installing via NPM 43 | ```bash 44 | $ npm install --save vue-innersearch 45 | ``` 46 | 47 | ## 3 Getting Started 48 | ### Setup a new Vue project using vue-innersearch 49 | You can use the innersearch-starter-app 50 | - `git clone https://github.com/TrimA74/innerSearch-starter-app.git` 51 | - `cd innerSearch-starter-app` 52 | - `npm i` 53 | ### Run the dev server 54 | - `npm run dev` 55 | 56 | This should open a new tab in your browser at [http://localhost:8080](http://localhost:8080) 57 | 58 | ### Use the vue-innersearch plugin 59 | 60 | You need to tell Vue to use the Vue InnerSearch plugin so that all components are available 61 | 62 | ```javascript 63 | import InnerSearch from 'vue-innersearch'; 64 | 65 | Vue.use(InnerSearch); 66 | ``` 67 | 68 | If you only want specific components like SearchBox and Hits components, you can do the following. 69 | 70 | :warning: Don't forget to import the Generics mixin component. 71 | 72 | ```javascript 73 | import {Searchbox, Hits, Generics} from 'vue-innersearch'; 74 | 75 | Vue.component('searchbox', Searchbox); 76 | Vue.component('hits', Hits); 77 | Vue.mixin(Generics); 78 | ``` 79 | 80 | ### Your first search UI 81 | You need first to set ElasticSearch host, index and type. 82 | 83 | ```javascript 84 | // ES server configuration 85 | this.setHost('http://es.yinyan.fr'); 86 | this.setIndex('bank'); 87 | this.setType('account'); 88 | ``` 89 | 90 | 91 | 92 | 93 | 94 | ## 4 Components list 95 | - [SearchBox](components/searchbox.md) 96 | - [RefinementListFilter](components/refinementListFilter.md) 97 | - [SearchButton](components/searchButton.md) 98 | - [Hits](components/hits.md) 99 | - [Paginate](components/paginate.md) 100 | - [SearchDataList](components/searchDataList.md) 101 | - [NumericListFilter](components/numericListFilter.md) 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /examples/Immo.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 75 | 76 | 79 | -------------------------------------------------------------------------------- /examples/Test_RefinementListFilter2.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 74 | 75 | 78 | -------------------------------------------------------------------------------- /src/components/NumericListFilter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /examples/Test_RLF_Searchbox.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 85 | 86 | 89 | -------------------------------------------------------------------------------- /docs/components/refinementListFilter.md: -------------------------------------------------------------------------------- 1 | ### RefinementListFilter 2 | - **Tag name :** `` 3 | - **Properties :** 4 | - `field` (_String_) : Name of the aggregation 5 | - `size` (_Number_): Amount of facets 6 | - `orderKey` (_String_) : Possible value (`_term` |`_count` ) to order by count number or by name term 7 | - `orderDirection` (_String_) : Possible value ( `asc` | `desc ` ) 8 | - `displayCount` (_Boolean_) : Display or not numbers of aggregations 9 | - `title` (_String_) : Title of the menu. Shown as a header and within selected filters 10 | - `operator` (_'AND'|'OR'_) : If you filter on a and b with OR, results with either the value a or b will match. If you select a and b, results will show which have both a and b. 11 | - `search` (_Boolean_) : Add a input text filter above the component 12 | 13 | - **Description :** 14 | A component to add facet refinements in the form of a list of checkboxes. 15 | 16 | ```html 17 | 18 | ``` 19 | 20 | You can also customize the title and the aggregations display using : 21 | `````` 22 | 23 | Refinment List with checkbox 24 | ```html 25 | 26 | 38 | 39 | ``` 40 | 41 | Refinement List with radio button 42 | 43 | ````html 44 | 45 | 57 | 58 | ```` 59 | 60 | Refinement List with simple dropdownlist 61 | ```html 62 | 63 | 72 | 73 | 74 | ``` 75 | 76 | Refinement List with dropdownlist (multiple) 77 | ```html 78 | 79 | 88 | 89 | 90 | ``` 91 | -------------------------------------------------------------------------------- /src/components/Paginate.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | -------------------------------------------------------------------------------- /cypress/integration/resetbutton.js: -------------------------------------------------------------------------------- 1 | const 2 | _URL = 'http://localhost:4000/#/test_resetbutton', 3 | _ES_URL = '**/_search', 4 | _SEARCHBOX = '.is-field.is-searchbox', 5 | _SEARCH_DATALIST = '.is-field.is-search-datalist', 6 | _REFINEMENT_LIST_FILTER = '.is-component.is-refinement-list', 7 | _NUMBER_LIST_FILTER = '.is-range.is-field', 8 | _HITS = '.is-score.is-hits', 9 | _AGGS = '.is-item.is-refinement-list', 10 | _ITEMS = 'div.hit', 11 | _SEARCH = '.is-button.is-search-button', 12 | _RESET = '.is-button.is-reset-button', 13 | _PAGINATE = '.is-paginate.is-component', 14 | _FILTERS = '.filters', 15 | 16 | _FIRST_SUGGESTION = '.is-search-datalist-suggestions > ul > li:nth-child(2)', 17 | _SELECTED_SUGGESTIONS = '.is-search-datalist-items > ul'; 18 | 19 | describe('Default ResetButton bevahior' , () => { 20 | beforeEach(function() { 21 | cy.visit(_URL); 22 | cy.server(); 23 | cy.route('POST', _ES_URL).as('ES'); 24 | cy.wait('@ES'); 25 | }); 26 | 27 | it('Click on button should show all hits' , () => { 28 | cy.get(_RESET).click(); 29 | cy.wait('@ES'); 30 | cy.get(_HITS).contains('1002 results found'); 31 | }); 32 | }); 33 | 34 | describe.only('Basic ResetButton behavior' , () => { 35 | beforeEach(function() { 36 | cy.visit(_URL); 37 | cy.server(); 38 | cy.route('POST', _ES_URL).as('ES'); 39 | cy.wait('@ES'); 40 | }); 41 | 42 | it('Dynamic searchbox reset' , () => { 43 | cy.get(_SEARCHBOX).type('f'); 44 | cy.wait('@ES'); 45 | cy.get(_RESET).click(); 46 | cy.wait('@ES').get(_SEARCHBOX).should('be.empty'); 47 | cy.get(_HITS).contains('1002 results found'); 48 | }); 49 | 50 | it('Dynamic searchdatalist reset' , () => { 51 | cy.get(_SEARCH_DATALIST).type('f'); 52 | cy.wait('@ES'); 53 | cy.get(_FIRST_SUGGESTION).click(); 54 | cy.wait('@ES'); 55 | cy.get(_RESET).click(); 56 | cy.wait('@ES').get(_SEARCH_DATALIST).should('be.empty'); 57 | cy.get(_SELECTED_SUGGESTIONS).should('be.empty'); 58 | cy.get(_HITS).contains('1002 results found'); 59 | }); 60 | 61 | it('Balance reset', () => { 62 | let _from = _NUMBER_LIST_FILTER + '[placeholder="from"]', 63 | _to = _NUMBER_LIST_FILTER + '[placeholder="to"]'; 64 | 65 | cy.get(_from).type('10'); 66 | cy.get(_to).type('40'); 67 | cy.wait('@ES'); 68 | cy.get(_RESET).click(); 69 | cy.wait('@ES').get(_from).should('be.empty'); 70 | cy.get(_to).should('be.empty'); 71 | cy.get(_HITS).contains('1002 results found'); 72 | }); 73 | 74 | it('RLF reset (select list)', () => { 75 | let _select = _REFINEMENT_LIST_FILTER + ' select'; 76 | 77 | cy.get(_select).select('ar'); 78 | cy.wait('@ES'); 79 | cy.get(_RESET).click(); 80 | cy.wait('@ES').get(_HITS).contains('1002 results found'); 81 | }); 82 | 83 | it('RLF reset (radio button)', () => { 84 | let _radio = _REFINEMENT_LIST_FILTER + ' input[type="radio"]'; 85 | 86 | cy.get(_radio).first().check(); 87 | cy.wait('@ES'); 88 | cy.get(_RESET).click(); 89 | cy.wait('@ES').get(_radio).first().should('not.be.checked'); 90 | cy.get(_HITS).contains('1002 results found'); 91 | }); 92 | 93 | it('RLF reset (checkbox)', () => { 94 | let _checkbox = _REFINEMENT_LIST_FILTER + ' input[type="checkbox"]'; 95 | 96 | cy.get(_checkbox).first().check(); 97 | cy.wait('@ES'); 98 | cy.get(_RESET).click(); 99 | cy.wait('@ES').get(_checkbox).first().should('not.be.checked'); 100 | cy.get(_HITS).contains('1002 results found'); 101 | }); 102 | 103 | it('Paginate reset', () => { 104 | let _page = _PAGINATE + ' li:nth-child(4) a'; 105 | 106 | cy.get(_SEARCH).click(); 107 | cy.wait('@ES'); 108 | 109 | cy.get(_page).click(); 110 | cy.wait('@ES'); 111 | 112 | cy.get(_RESET).click(); 113 | 114 | cy.wait('@ES').get(_page).should('not.have.class', 'is-active'); 115 | cy.get(_HITS).contains('1002 results found'); 116 | }); 117 | }); 118 | 119 | describe('Complete ResetButton behavior' , () => { 120 | beforeEach(function() { 121 | cy.visit(_URL); 122 | cy.server(); 123 | cy.route('POST', _ES_URL).as('ES'); 124 | cy.wait('@ES'); 125 | }); 126 | 127 | it('Reset full form and filter tags', () => { 128 | let _from = _NUMBER_LIST_FILTER + '[placeholder="from"]', 129 | _to = _NUMBER_LIST_FILTER + '[placeholder="to"]', 130 | _select = _REFINEMENT_LIST_FILTER + ' select', 131 | _radio = _REFINEMENT_LIST_FILTER + ' input[type="radio"]', 132 | _checkbox = _REFINEMENT_LIST_FILTER + ' input[type="checkbox"]'; 133 | 134 | cy.get(_SEARCHBOX).type('d'); 135 | cy.wait('@ES'); 136 | 137 | cy.get(_SEARCH_DATALIST).type('f'); 138 | cy.wait('@ES').wait(500); 139 | cy.get(_FIRST_SUGGESTION).click(); 140 | cy.wait('@ES'); 141 | 142 | cy.get(_from).type('10'); 143 | cy.get(_to).type('50000'); 144 | cy.wait('@ES'); 145 | 146 | cy.get(_select).select('va'); 147 | cy.wait('@ES'); 148 | 149 | cy.get(_radio).first().check(); 150 | cy.wait('@ES'); 151 | 152 | cy.get(_checkbox).first().check(); 153 | cy.wait('@ES'); 154 | 155 | cy.get(_RESET).click(); 156 | cy.wait('@ES'); 157 | 158 | /* 159 | cy.get(_SEARCHBOX).should('be.empty'); 160 | cy.get(_SEARCH_DATALIST).should('be.empty'); 161 | cy.get(_SELECTED_SUGGESTIONS).should('be.empty'); 162 | cy.get(_from).should('be.empty'); 163 | cy.get(_to).should('be.empty'); 164 | cy.get(_radio).first().should('not.be.checked'); 165 | cy.get(_checkbox).first().should('not.be.checked'); 166 | cy.get(_FILTERS).should('be.empty'); 167 | */ 168 | 169 | cy.get(_HITS).contains('1002 results found'); 170 | }); 171 | }); -------------------------------------------------------------------------------- /src/lib/Store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | Vue.use(Vuex); 4 | 5 | 6 | export default new Vuex.Store({ 7 | modules : { 8 | /* 9 | Elasticsearch header Store 10 | For API ES object 11 | For Request 12 | */ 13 | Elasticsearch : { 14 | namespaced : true, 15 | 16 | state : { 17 | // ElasticSearch server informations 18 | header : { 19 | client : {}, 20 | index : "", 21 | type : "" 22 | }, 23 | 24 | // Request 25 | body : undefined, 26 | 27 | // Request instructions 28 | instructions : [], 29 | 30 | // Aggregations 31 | aggregations : {}, 32 | 33 | // Hanged debounce list 34 | debounce : {}, 35 | 36 | // Components identification for interactions 37 | components : { 38 | CID : 0, // Static component counter 39 | bus : new Vue(), // communication bus 40 | refs : {} 41 | } 42 | }, 43 | 44 | mutations : { 45 | setHost (state, value) { 46 | state.header.client = value; 47 | }, 48 | 49 | setIndex (state, value) { 50 | state.header.index = value; 51 | }, 52 | 53 | setType (state, value) { 54 | state.header.type = value; 55 | }, 56 | 57 | setBody (state, value) { 58 | state.body = value; 59 | }, 60 | 61 | addInstruction (state, value) { 62 | state.instructions.push(value); 63 | }, 64 | 65 | removeInstruction (state, value) { 66 | state.instructions = state.instructions.filter(function(object) { 67 | return object !== value; 68 | }); 69 | }, 70 | 71 | setAggregations (state, { name, value, orderKey, orderDirection }) { 72 | Vue.set(state.aggregations, name, value); 73 | }, 74 | 75 | addDebounce(state, value) { 76 | if (state.debounce[value.component] === undefined) 77 | state.debounce[value.component] = []; 78 | state.debounce[value.component].push(value.debounce); 79 | }, 80 | 81 | resetDebounce(state, value) { 82 | if (value !== null) { 83 | let _obj = state.debounce[value]; 84 | if (_obj !== undefined) { 85 | _obj.forEach(debounce => { 86 | debounce.clear(); 87 | }); 88 | } 89 | } 90 | else { 91 | for (let key in state.debounce) { 92 | if (!state.debounce.hasOwnProperty(key)) continue; 93 | 94 | let _obj = state.debounce[key]; 95 | if (_obj !== undefined) { 96 | _obj.forEach(debounce => { 97 | debounce.clear(); 98 | }); 99 | } 100 | } 101 | } 102 | }, 103 | 104 | addComponent(state, value) { 105 | if (state.components.refs[value.type] === undefined) 106 | state.components.refs[value.type] = []; 107 | 108 | state.components.refs[value.type].push(value.self); 109 | ++state.components.CID; 110 | } 111 | }, 112 | 113 | getters : { 114 | getHeader : state => { 115 | return state.header; 116 | }, 117 | 118 | getProperties : state => { 119 | return state.properties; 120 | }, 121 | 122 | getBody : state => { 123 | return state.body; 124 | }, 125 | 126 | getInstructions : state => { 127 | return state.instructions; 128 | }, 129 | 130 | getAggregations : state => { 131 | return state.aggregations; 132 | }, 133 | 134 | getCid : state => { 135 | return state.components.CID; 136 | }, 137 | 138 | getBus : state => { 139 | return state.components.bus; 140 | }, 141 | 142 | getComponents : state => { 143 | return state.components.refs; 144 | } 145 | } 146 | }, 147 | 148 | 149 | /* 150 | Hits Store 151 | */ 152 | Hits : { 153 | namespaced : true, 154 | 155 | state : { 156 | // Hits list 157 | items : [], 158 | 159 | // Hits count 160 | score : undefined 161 | }, 162 | 163 | mutations : { 164 | addItem (state, value) { 165 | state.items.push(value); 166 | }, 167 | 168 | clearItems (state) { 169 | state.items = []; 170 | }, 171 | 172 | setScore (state, value) { 173 | state.score = value; 174 | }, 175 | }, 176 | 177 | getters : { 178 | getItems : state => { 179 | return state.items; 180 | }, 181 | 182 | getScore : state => { 183 | return state.score; 184 | } 185 | } 186 | } 187 | } 188 | }); 189 | -------------------------------------------------------------------------------- /src/components/TagFilter.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 152 | -------------------------------------------------------------------------------- /cypress/fixtures/refinementFilterList_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "took":0, 3 | "timed_out":false, 4 | "_shards":{ 5 | "total":5, 6 | "successful":5, 7 | "skipped":0, 8 | "failed":0 9 | }, 10 | "hits":{ 11 | "total":30, 12 | "max_score":0.0, 13 | "hits":[ 14 | { 15 | "_index":"bank", 16 | "_type":"account", 17 | "_id":"108", 18 | "_score":0.0, 19 | "_source":{ 20 | "account_number":108, 21 | "balance":19015, 22 | "firstname":"Christensen", 23 | "lastname":"Weaver", 24 | "age":21, 25 | "gender":"M", 26 | "address":"398 Dearborn Court", 27 | "employer":"Quilk", 28 | "email":"christensenweaver@quilk.com", 29 | "city":"Belvoir", 30 | "state":"TX" 31 | } 32 | }, 33 | { 34 | "_index":"bank", 35 | "_type":"account", 36 | "_id":"383", 37 | "_score":0.0, 38 | "_source":{ 39 | "account_number":383, 40 | "balance":48889, 41 | "firstname":"Knox", 42 | "lastname":"Larson", 43 | "age":28, 44 | "gender":"F", 45 | "address":"962 Bartlett Place", 46 | "employer":"Bostonic", 47 | "email":"knoxlarson@bostonic.com", 48 | "city":"Smeltertown", 49 | "state":"TX" 50 | } 51 | }, 52 | { 53 | "_index":"bank", 54 | "_type":"account", 55 | "_id":"89", 56 | "_score":0.0, 57 | "_source":{ 58 | "account_number":89, 59 | "balance":13263, 60 | "firstname":"Mcdowell", 61 | "lastname":"Bradley", 62 | "age":28, 63 | "gender":"M", 64 | "address":"960 Howard Alley", 65 | "employer":"Grok", 66 | "email":"mcdowellbradley@grok.com", 67 | "city":"Toftrees", 68 | "state":"TX" 69 | } 70 | }, 71 | { 72 | "_index":"bank", 73 | "_type":"account", 74 | "_id":"349", 75 | "_score":0.0, 76 | "_source":{ 77 | "account_number":349, 78 | "balance":24180, 79 | "firstname":"Allison", 80 | "lastname":"Fitzpatrick", 81 | "age":22, 82 | "gender":"F", 83 | "address":"913 Arlington Avenue", 84 | "employer":"Veraq", 85 | "email":"allisonfitzpatrick@veraq.com", 86 | "city":"Marbury", 87 | "state":"TX" 88 | } 89 | }, 90 | { 91 | "_index":"bank", 92 | "_type":"account", 93 | "_id":"933", 94 | "_score":0.0, 95 | "_source":{ 96 | "account_number":933, 97 | "balance":18071, 98 | "firstname":"Tabitha", 99 | "lastname":"Cole", 100 | "age":21, 101 | "gender":"F", 102 | "address":"916 Rogers Avenue", 103 | "employer":"Eclipto", 104 | "email":"tabithacole@eclipto.com", 105 | "city":"Lawrence", 106 | "state":"TX" 107 | } 108 | }, 109 | { 110 | "_index":"bank", 111 | "_type":"account", 112 | "_id":"378", 113 | "_score":0.0, 114 | "_source":{ 115 | "account_number":378, 116 | "balance":27100, 117 | "firstname":"Watson", 118 | "lastname":"Simpson", 119 | "age":36, 120 | "gender":"F", 121 | "address":"644 Thomas Street", 122 | "employer":"Wrapture", 123 | "email":"watsonsimpson@wrapture.com", 124 | "city":"Keller", 125 | "state":"TX" 126 | } 127 | }, 128 | { 129 | "_index":"bank", 130 | "_type":"account", 131 | "_id":"514", 132 | "_score":0.0, 133 | "_source":{ 134 | "account_number":514, 135 | "balance":30125, 136 | "firstname":"Solomon", 137 | "lastname":"Bush", 138 | "age":34, 139 | "gender":"M", 140 | "address":"409 Harkness Avenue", 141 | "employer":"Snacktion", 142 | "email":"solomonbush@snacktion.com", 143 | "city":"Grayhawk", 144 | "state":"TX" 145 | } 146 | }, 147 | { 148 | "_index":"bank", 149 | "_type":"account", 150 | "_id":"605", 151 | "_score":0.0, 152 | "_source":{ 153 | "account_number":605, 154 | "balance":38427, 155 | "firstname":"Mcclain", 156 | "lastname":"Manning", 157 | "age":24, 158 | "gender":"M", 159 | "address":"832 Leonard Street", 160 | "employer":"Qiao", 161 | "email":"mcclainmanning@qiao.com", 162 | "city":"Calvary", 163 | "state":"TX" 164 | } 165 | }, 166 | { 167 | "_index":"bank", 168 | "_type":"account", 169 | "_id":"214", 170 | "_score":0.0, 171 | "_source":{ 172 | "account_number":214, 173 | "balance":24418, 174 | "firstname":"Luann", 175 | "lastname":"Faulkner", 176 | "age":37, 177 | "gender":"F", 178 | "address":"697 Hazel Court", 179 | "employer":"Zolar", 180 | "email":"luannfaulkner@zolar.com", 181 | "city":"Ticonderoga", 182 | "state":"TX" 183 | } 184 | }, 185 | { 186 | "_index":"bank", 187 | "_type":"account", 188 | "_id":"396", 189 | "_score":0.0, 190 | "_source":{ 191 | "account_number":396, 192 | "balance":14613, 193 | "firstname":"Marsha", 194 | "lastname":"Elliott", 195 | "age":38, 196 | "gender":"F", 197 | "address":"297 Liberty Avenue", 198 | "employer":"Orbiflex", 199 | "email":"marshaelliott@orbiflex.com", 200 | "city":"Windsor", 201 | "state":"TX" 202 | } 203 | } 204 | ] 205 | }, 206 | "aggregations":{ 207 | "agg_terms_state":{ 208 | "doc_count_error_upper_bound":0, 209 | "sum_other_doc_count":0, 210 | "buckets":[ 211 | { 212 | "key":"tx", 213 | "doc_count":30 214 | } 215 | ] 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /default-innersearch-theme.min.css: -------------------------------------------------------------------------------- 1 | *{box-sizing:inherit;margin:0;padding:0}body,button,input,select,textarea{font-family:BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif}h1,h2,h3,h4,h5,h6{margin-bottom:6px;font-size:125%;font-weight:300}.is-columns{display:flex;width:95%;margin:0 auto}.is-column.is-one-fifth{flex:none;width:25%}.is-column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1}.is-title{font-size:4em;color:#3c434b;font-family:Calibri,sans-serif;margin:20px 0 10px 5%}.is-line{width:90%;margin:auto}.is-component{border:1px solid #a9a9a9;font-family:Calibri,sans-serif}.is-icon{background-repeat:no-repeat;background-position:50%;background-size:contain}.is-field{font-size:2em;padding:5px}.is-button{padding:15px;border:1px solid transparent;border-radius:4px;font-size:1.25em;cursor:pointer;user-select:none}.is-field:focus{outline:none}.is-component.is-paginate,.is-component.is-search-datalist,.is-component.is-searchbox{display:flex;width:90%}.is-component.is-searchbox{margin:20px auto}.is-icon.is-search-datalist,.is-icon.is-searchbox{width:50px;height:50px;margin:5px;background-image:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KCgo8IS0tIFRoZSBpY29uIGNhbiBiZSB1c2VkIGZyZWVseSBpbiBib3RoIHBlcnNvbmFsIGFuZCBjb21tZXJjaWFsIHByb2plY3RzIHdpdGggbm8gYXR0cmlidXRpb24gcmVxdWlyZWQsIGJ1dCBhbHdheXMgYXBwcmVjaWF0ZWQuIApZb3UgbWF5IE5PVCBzdWItbGljZW5zZSwgcmVzZWxsLCByZW50LCByZWRpc3RyaWJ1dGUgb3Igb3RoZXJ3aXNlIHRyYW5zZmVyIHRoZSBpY29uIHdpdGhvdXQgZXhwcmVzcyB3cml0dGVuIHBlcm1pc3Npb24gZnJvbSBpY29ubW9uc3RyLmNvbSAtLT4KCgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgoKPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoKCSB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4KCjxwYXRoIGlkPSJtYWduaWZpZXItMi1pY29uIiBkPSJNNDYwLjM1NSw0MjEuNTlMMzUzLjg0NCwzMTUuMDc4YzIwLjA0MS0yNy41NTMsMzEuODg1LTYxLjQzNywzMS44ODUtOTguMDM3CgoJQzM4NS43MjksMTI0LjkzNCwzMTAuNzkzLDUwLDIxOC42ODYsNTBDMTI2LjU4LDUwLDUxLjY0NSwxMjQuOTM0LDUxLjY0NSwyMTcuMDQxYzAsOTIuMTA2LDc0LjkzNiwxNjcuMDQxLDE2Ny4wNDEsMTY3LjA0MQoKCWMzNC45MTIsMCw2Ny4zNTItMTAuNzczLDk0LjE4NC0yOS4xNThMNDE5Ljk0NSw0NjJMNDYwLjM1NSw0MjEuNTl6IE0xMDAuNjMxLDIxNy4wNDFjMC02NS4wOTYsNTIuOTU5LTExOC4wNTYsMTE4LjA1NS0xMTguMDU2CgoJYzY1LjA5OCwwLDExOC4wNTcsNTIuOTU5LDExOC4wNTcsMTE4LjA1NmMwLDY1LjA5Ni01Mi45NTksMTE4LjA1Ni0xMTguMDU3LDExOC4wNTZDMTUzLjU5LDMzNS4wOTcsMTAwLjYzMSwyODIuMTM3LDEwMC42MzEsMjE3LjA0MQoKCXoiLz4KCjwvc3ZnPgoK");opacity:.4}.is-field.is-search-datalist,.is-field.is-searchbox{width:100%;border:0;color:#2c2c2c}.is-component.is-search-datalist{margin:0 auto}.is-search-datalist-items,.is-tag-filter{width:90%;margin:0 auto}.is-search-datalist-items li,.is-tag-filter li{display:inline-block;margin:10px 5px;padding:5px 10px;border-radius:4px;background-color:hsla(0,0%,82%,.8);transition:all .3s ease;cursor:pointer}.is-search-datalist-items li:hover,.is-tag-filter li:hover{background-color:#d1d1d1}.is-search-datalist-items li:first-child,.is-tag-filter li:first-child{margin-left:0!important}.is-search-datalist-items li:after,.is-tag-filter li:after{content:"x";margin-left:5px;color:rgba(129,35,35,.6);font-family:Calibri,sans-serif;font-weight:bolder;transition:all .3s ease}.is-search-datalist-items li:hover:after,.is-tag-filter li:hover:after{color:rgba(129,35,35,.8)}.is-search-datalist-suggestions{width:90%;margin:0 auto;border-right:1px solid #a9a9a9;border-bottom:1px solid #a9a9a9;border-left:1px solid #a9a9a9}.is-search-datalist-suggestions ul{max-height:250px;overflow:auto}.is-search-datalist-suggestions li{padding:5px 10px;font-family:Calibri,sans-serif}.is-search-datalist-suggestions li:not(.noresult){cursor:pointer}.is-search-datalist-suggestions li:not(.noresult):nth-child(odd){background-color:#eaeaea}.is-search-datalist-suggestions li:not(.noresult):nth-child(2n){background-color:#f9f9f9}.is-search-datalist-suggestions li.noresult{font-style:oblique;font-weight:bolder}.is-search-datalist-suggestions li:not(.noresult).selected{background:#181818;color:#f0f0f0}.is-search-datalist-suggestions li em{font-style:normal;font-weight:700}.is-nlf-inputs{margin:0;display:flex}.is-nlf{margin:20px auto;width:90%}.is-range{width:50%}.is-nlf-title{margin-bottom:5px}.is-component.is-refinement-list{width:90%;margin:20px auto;padding:15px;font-size:1.25em;box-sizing:border-box}.is-item.is-refinement-list{display:inline-block;width:100%}.is-component.is-reset-button,.is-component.is-search-button{display:inline-block;margin:5px 10px 5px 0;border:0}.is-button.is-search-button{color:#fff;background-color:#337ab7;border-color:#2e6da4}.is-button.is-search-button:active,.is-button.is-search-button:hover{color:#fff;background-color:#286090;border-color:#204d74}.is-button.is-search-button:focus{color:#fff;background-color:#286090;border-color:#122b40}.is-button.is-reset-button{color:#fff;background-color:#b73337;border-color:#a42e2e}.is-button.is-reset-button:active,.is-button.is-reset-button:hover{color:#fff;background-color:#902828;border-color:#742020}.is-button.is-reset-button:focus{color:#fff;background-color:#902828;border-color:#401212}.is-component.is-hits{width:90%;margin:20px auto;padding:15px;font-size:1.25em;box-sizing:border-box}.is-score.is-hits{margin:10px 0 20px 50px;font-size:1.75em;font-weight:bolder;font-variant:small-caps}.is-item.is-hits{width:90%;margin:20px auto;padding:20px;background:#efefef}.is-item.is-hits ul{margin:0}.is-component.is-paginate{margin:20px auto;align-items:center;justify-content:center;text-align:center;border:1px solid transparent;user-select:none}.is-previous{order:1}.is-list{display:flex;flex-grow:1;align-items:center;justify-content:center;text-align:center;list-style:none;list-style-type:none;list-style-position:initial;list-style-image:none;order:2}.is-next{order:3}.is-next,.is-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.is-next,.is-page,.is-previous{min-width:2.25em;border-color:#dbdbdb;color:#363636;cursor:pointer}.is-page.is-active{background-color:#3273dc;border-color:#3273dc;color:#fff}.is-next:active,.is-page:active:not(.is-active),.is-previous:active{box-shadow:inset 0 1px 2px hsla(0,0%,4%,.2)}.is-next:hover,.is-page:hover:not(.is-active),.is-previous:hover{border-color:#b5b5b5}.is-ellipsis,.is-next,.is-page,.is-previous{margin:.25rem;justify-content:center;text-align:center;display:inline-flex;align-items:center;border:1px solid transparent;border-radius:4px;font-size:1rem;height:2.25em;padding:calc(.175em - 1px) calc(.325em - 1px)}.is-component.is-tag-filter{display:inline-block;width:auto;border:none;margin:0 auto} -------------------------------------------------------------------------------- /examples/standalone/default-innersearch-theme.min.css: -------------------------------------------------------------------------------- 1 | *{box-sizing:inherit;margin:0;padding:0}body,button,input,select,textarea{font-family:BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif}h1,h2,h3,h4,h5,h6{margin-bottom:6px;font-size:125%;font-weight:300}.is-columns{display:flex;width:95%;margin:0 auto}.is-column.is-one-fifth{flex:none;width:25%}.is-column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1}.is-title{font-size:4em;color:#3c434b;font-family:Calibri,sans-serif;margin:20px 0 10px 5%}.is-line{width:90%;margin:auto}.is-component{border:1px solid #a9a9a9;font-family:Calibri,sans-serif}.is-icon{background-repeat:no-repeat;background-position:50%;background-size:contain}.is-field{font-size:2em;padding:5px}.is-button{padding:15px;border:1px solid transparent;border-radius:4px;font-size:1.25em;cursor:pointer;user-select:none}.is-field:focus{outline:none}.is-component.is-paginate,.is-component.is-search-datalist,.is-component.is-searchbox{display:flex;width:90%}.is-component.is-searchbox{margin:20px auto}.is-icon.is-search-datalist,.is-icon.is-searchbox{width:50px;height:50px;margin:5px;background-image:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KCgo8IS0tIFRoZSBpY29uIGNhbiBiZSB1c2VkIGZyZWVseSBpbiBib3RoIHBlcnNvbmFsIGFuZCBjb21tZXJjaWFsIHByb2plY3RzIHdpdGggbm8gYXR0cmlidXRpb24gcmVxdWlyZWQsIGJ1dCBhbHdheXMgYXBwcmVjaWF0ZWQuIApZb3UgbWF5IE5PVCBzdWItbGljZW5zZSwgcmVzZWxsLCByZW50LCByZWRpc3RyaWJ1dGUgb3Igb3RoZXJ3aXNlIHRyYW5zZmVyIHRoZSBpY29uIHdpdGhvdXQgZXhwcmVzcyB3cml0dGVuIHBlcm1pc3Npb24gZnJvbSBpY29ubW9uc3RyLmNvbSAtLT4KCgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgoKPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoKCSB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4KCjxwYXRoIGlkPSJtYWduaWZpZXItMi1pY29uIiBkPSJNNDYwLjM1NSw0MjEuNTlMMzUzLjg0NCwzMTUuMDc4YzIwLjA0MS0yNy41NTMsMzEuODg1LTYxLjQzNywzMS44ODUtOTguMDM3CgoJQzM4NS43MjksMTI0LjkzNCwzMTAuNzkzLDUwLDIxOC42ODYsNTBDMTI2LjU4LDUwLDUxLjY0NSwxMjQuOTM0LDUxLjY0NSwyMTcuMDQxYzAsOTIuMTA2LDc0LjkzNiwxNjcuMDQxLDE2Ny4wNDEsMTY3LjA0MQoKCWMzNC45MTIsMCw2Ny4zNTItMTAuNzczLDk0LjE4NC0yOS4xNThMNDE5Ljk0NSw0NjJMNDYwLjM1NSw0MjEuNTl6IE0xMDAuNjMxLDIxNy4wNDFjMC02NS4wOTYsNTIuOTU5LTExOC4wNTYsMTE4LjA1NS0xMTguMDU2CgoJYzY1LjA5OCwwLDExOC4wNTcsNTIuOTU5LDExOC4wNTcsMTE4LjA1NmMwLDY1LjA5Ni01Mi45NTksMTE4LjA1Ni0xMTguMDU3LDExOC4wNTZDMTUzLjU5LDMzNS4wOTcsMTAwLjYzMSwyODIuMTM3LDEwMC42MzEsMjE3LjA0MQoKCXoiLz4KCjwvc3ZnPgoK");opacity:.4}.is-field.is-search-datalist,.is-field.is-searchbox{width:100%;border:0;color:#2c2c2c}.is-component.is-search-datalist{margin:0 auto}.is-search-datalist-items,.is-tag-filter{width:90%;margin:0 auto}.is-search-datalist-items li,.is-tag-filter li{display:inline-block;margin:10px 5px;padding:5px 10px;border-radius:4px;background-color:hsla(0,0%,82%,.8);transition:all .3s ease;cursor:pointer}.is-search-datalist-items li:hover,.is-tag-filter li:hover{background-color:#d1d1d1}.is-search-datalist-items li:first-child,.is-tag-filter li:first-child{margin-left:0!important}.is-search-datalist-items li:after,.is-tag-filter li:after{content:"x";margin-left:5px;color:rgba(129,35,35,.6);font-family:Calibri,sans-serif;font-weight:bolder;transition:all .3s ease}.is-search-datalist-items li:hover:after,.is-tag-filter li:hover:after{color:rgba(129,35,35,.8)}.is-search-datalist-suggestions{width:90%;margin:0 auto;border-right:1px solid #a9a9a9;border-bottom:1px solid #a9a9a9;border-left:1px solid #a9a9a9}.is-search-datalist-suggestions ul{max-height:250px;overflow:auto}.is-search-datalist-suggestions li{padding:5px 10px;font-family:Calibri,sans-serif}.is-search-datalist-suggestions li:not(.noresult){cursor:pointer}.is-search-datalist-suggestions li:not(.noresult):nth-child(odd){background-color:#eaeaea}.is-search-datalist-suggestions li:not(.noresult):nth-child(2n){background-color:#f9f9f9}.is-search-datalist-suggestions li.noresult{font-style:oblique;font-weight:bolder}.is-search-datalist-suggestions li:not(.noresult).selected{background:#181818;color:#f0f0f0}.is-search-datalist-suggestions li em{font-style:normal;font-weight:700}.is-nlf-inputs{margin:0;display:flex}.is-nlf{margin:20px auto;width:90%}.is-range{width:50%}.is-nlf-title{margin-bottom:5px}.is-component.is-refinement-list{width:90%;margin:20px auto;padding:15px;font-size:1.25em;box-sizing:border-box}.is-item.is-refinement-list{display:inline-block;width:100%}.is-component.is-reset-button,.is-component.is-search-button{display:inline-block;margin:5px 10px 5px 0;border:0}.is-button.is-search-button{color:#fff;background-color:#337ab7;border-color:#2e6da4}.is-button.is-search-button:active,.is-button.is-search-button:hover{color:#fff;background-color:#286090;border-color:#204d74}.is-button.is-search-button:focus{color:#fff;background-color:#286090;border-color:#122b40}.is-button.is-reset-button{color:#fff;background-color:#b73337;border-color:#a42e2e}.is-button.is-reset-button:active,.is-button.is-reset-button:hover{color:#fff;background-color:#902828;border-color:#742020}.is-button.is-reset-button:focus{color:#fff;background-color:#902828;border-color:#401212}.is-component.is-hits{width:90%;margin:20px auto;padding:15px;font-size:1.25em;box-sizing:border-box}.is-score.is-hits{margin:10px 0 20px 50px;font-size:1.75em;font-weight:bolder;font-variant:small-caps}.is-item.is-hits{width:90%;margin:20px auto;padding:20px;background:#efefef}.is-item.is-hits ul{margin:0}.is-component.is-paginate{margin:20px auto;align-items:center;justify-content:center;text-align:center;border:1px solid transparent;user-select:none}.is-previous{order:1}.is-list{display:flex;flex-grow:1;align-items:center;justify-content:center;text-align:center;list-style:none;list-style-type:none;list-style-position:initial;list-style-image:none;order:2}.is-next{order:3}.is-next,.is-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.is-next,.is-page,.is-previous{min-width:2.25em;border-color:#dbdbdb;color:#363636;cursor:pointer}.is-page.is-active{background-color:#3273dc;border-color:#3273dc;color:#fff}.is-next:active,.is-page:active:not(.is-active),.is-previous:active{box-shadow:inset 0 1px 2px hsla(0,0%,4%,.2)}.is-next:hover,.is-page:hover:not(.is-active),.is-previous:hover{border-color:#b5b5b5}.is-ellipsis,.is-next,.is-page,.is-previous{margin:.25rem;justify-content:center;text-align:center;display:inline-flex;align-items:center;border:1px solid transparent;border-radius:4px;font-size:1rem;height:2.25em;padding:calc(.175em - 1px) calc(.325em - 1px)}.is-component.is-tag-filter{display:inline-block;width:auto;border:none;margin:0 auto} -------------------------------------------------------------------------------- /cypress/fixtures/searchbox_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "took":1, 3 | "timed_out":false, 4 | "_shards":{ 5 | "total":5, 6 | "successful":5, 7 | "skipped":0, 8 | "failed":0 9 | }, 10 | "hits":{ 11 | "total":287, 12 | "max_score":0, 13 | "hits":[ 14 | { 15 | "_index":"bank", 16 | "_type":"account", 17 | "_id":"948", 18 | "_score":0, 19 | "_source":{ 20 | "account_number":948, 21 | "balance":37074, 22 | "firstname":"Sargent", 23 | "lastname":"Powers", 24 | "age":40, 25 | "gender":"M", 26 | "address":"532 Fiske Place", 27 | "employer":"Accuprint", 28 | "email":"sargentpowers@accuprint.com", 29 | "city":"Umapine", 30 | "state":"AK" 31 | } 32 | }, 33 | { 34 | "_index":"bank", 35 | "_type":"account", 36 | "_id":"19", 37 | "_score":0, 38 | "_source":{ 39 | "account_number":19, 40 | "balance":27894, 41 | "firstname":"Schwartz", 42 | "lastname":"Buchanan", 43 | "age":28, 44 | "gender":"F", 45 | "address":"449 Mersereau Court", 46 | "employer":"Sybixtex", 47 | "email":"schwartzbuchanan@sybixtex.com", 48 | "city":"Greenwich", 49 | "state":"KS" 50 | } 51 | }, 52 | { 53 | "_index":"bank", 54 | "_type":"account", 55 | "_id":"470", 56 | "_score":0, 57 | "_source":{ 58 | "account_number":470, 59 | "balance":20455, 60 | "firstname":"Schneider", 61 | "lastname":"Hull", 62 | "age":35, 63 | "gender":"M", 64 | "address":"724 Apollo Street", 65 | "employer":"Exospeed", 66 | "email":"schneiderhull@exospeed.com", 67 | "city":"Watchtower", 68 | "state":"ID" 69 | } 70 | }, 71 | { 72 | "_index":"bank", 73 | "_type":"account", 74 | "_id":"487", 75 | "_score":0, 76 | "_source":{ 77 | "account_number":487, 78 | "balance":30718, 79 | "firstname":"Sawyer", 80 | "lastname":"Vincent", 81 | "age":26, 82 | "gender":"F", 83 | "address":"238 Lancaster Avenue", 84 | "employer":"Brainquil", 85 | "email":"sawyervincent@brainquil.com", 86 | "city":"Galesville", 87 | "state":"MS" 88 | } 89 | }, 90 | { 91 | "_index":"bank", 92 | "_type":"account", 93 | "_id":"526", 94 | "_score":0, 95 | "_source":{ 96 | "account_number":526, 97 | "balance":35375, 98 | "firstname":"Sweeney", 99 | "lastname":"Fulton", 100 | "age":33, 101 | "gender":"F", 102 | "address":"550 Martense Street", 103 | "employer":"Cormoran", 104 | "email":"sweeneyfulton@cormoran.com", 105 | "city":"Chalfant", 106 | "state":"IA" 107 | } 108 | }, 109 | { 110 | "_index":"bank", 111 | "_type":"account", 112 | "_id":"879", 113 | "_score":0, 114 | "_source":{ 115 | "account_number":879, 116 | "balance":48332, 117 | "firstname":"Sabrina", 118 | "lastname":"Lancaster", 119 | "age":31, 120 | "gender":"F", 121 | "address":"382 Oak Street", 122 | "employer":"Webiotic", 123 | "email":"sabrinalancaster@webiotic.com", 124 | "city":"Lindisfarne", 125 | "state":"AZ" 126 | } 127 | }, 128 | { 129 | "_index":"bank", 130 | "_type":"account", 131 | "_id":"256", 132 | "_score":0, 133 | "_source":{ 134 | "account_number":256, 135 | "balance":48318, 136 | "firstname":"Simon", 137 | "lastname":"Hogan", 138 | "age":31, 139 | "gender":"M", 140 | "address":"789 Suydam Place", 141 | "employer":"Dancerity", 142 | "email":"simonhogan@dancerity.com", 143 | "city":"Dargan", 144 | "state":"GA" 145 | } 146 | }, 147 | { 148 | "_index":"bank", 149 | "_type":"account", 150 | "_id":"264", 151 | "_score":0, 152 | "_source":{ 153 | "account_number":264, 154 | "balance":22084, 155 | "firstname":"Samantha", 156 | "lastname":"Ferrell", 157 | "age":35, 158 | "gender":"F", 159 | "address":"488 Fulton Street", 160 | "employer":"Flum", 161 | "email":"samanthaferrell@flum.com", 162 | "city":"Brandywine", 163 | "state":"MT" 164 | } 165 | }, 166 | { 167 | "_index":"bank", 168 | "_type":"account", 169 | "_id":"365", 170 | "_score":0, 171 | "_source":{ 172 | "account_number":365, 173 | "balance":3176, 174 | "firstname":"Sanders", 175 | "lastname":"Holder", 176 | "age":31, 177 | "gender":"F", 178 | "address":"453 Cypress Court", 179 | "employer":"Geekola", 180 | "email":"sandersholder@geekola.com", 181 | "city":"Staples", 182 | "state":"TN" 183 | } 184 | }, 185 | { 186 | "_index":"bank", 187 | "_type":"account", 188 | "_id":"332", 189 | "_score":0, 190 | "_source":{ 191 | "account_number":332, 192 | "balance":37770, 193 | "firstname":"Shepherd", 194 | "lastname":"Davenport", 195 | "age":28, 196 | "gender":"F", 197 | "address":"586 Montague Terrace", 198 | "employer":"Ecraze", 199 | "email":"shepherddavenport@ecraze.com", 200 | "city":"Accoville", 201 | "state":"NM" 202 | } 203 | } 204 | ] 205 | } 206 | } -------------------------------------------------------------------------------- /cypress/integration/refinementFilter.js: -------------------------------------------------------------------------------- 1 | const _URL = 'http://localhost:4000/#/test_refinementListFilter', 2 | _URL2 = 'http://localhost:4000/#/test_refinementListFilter2', 3 | _URL3 = 'http://localhost:4000/#/test_multipleRLF', 4 | _URL4 = 'http://localhost:4000/#/test_RLF_With_Searchbox', 5 | _ES_URL = '**/_search', 6 | _SEARCHBOX = '.is-field.is-searchbox', 7 | _REFINEMENT_LIST_FILTER = '.is-component.is-refinement-list', 8 | _HITS = '.is-score.is-hits', 9 | _AGGS = '.is-item.is-refinement-list', 10 | _ITEMS = 'div.hit', 11 | _TITLE = '.is-refinement-menu-title', 12 | 13 | _FIRSTNAME = '.firstname', 14 | _LASTNAME = '.lastname', 15 | _STATE = '.state', 16 | _GENDER = '.gender'; 17 | 18 | import refinementFilterList_sample_1 from '../fixtures/refinementFilterList_1.json'; 19 | 20 | 21 | describe('Test RLF with Searchbox' , () => { 22 | it('2nd non regression test for issue #4' , () => { 23 | cy.visit(_URL4); 24 | cy.server(); 25 | cy.route('POST', _ES_URL).as('ES'); 26 | cy.wait('@ES'); 27 | cy.wait('@ES'); 28 | 29 | cy.get(_SEARCHBOX).type('fred'); 30 | cy.get('.is-component.is-search-button > .is-button').click(); 31 | cy.wait('@ES'); 32 | cy.wait('@ES'); 33 | cy.wait('@ES'); 34 | cy.get('.gender_rlf > .is-component > .is-item > label').then ( $e => { 35 | expect($e.get(0).innerHTML).to.equal("f ( 1 )"); 36 | }); 37 | 38 | }); 39 | }); 40 | 41 | 42 | describe('Test Multiple RLF', () => { 43 | beforeEach(function() { 44 | cy.visit(_URL3); 45 | cy.server(); 46 | cy.route('POST', _ES_URL).as('ES'); 47 | cy.wait('@ES'); 48 | cy.wait('@ES'); 49 | }); 50 | 51 | 52 | 53 | it('non regression test for issue #4', () => { 54 | cy.get('.gender_rlf > .is-component > :nth-child(2) > input').click(); // Check F gender 55 | cy.get(':nth-child(14) > input').click(); // Check CA State 56 | cy.get('.gender_rlf > .is-component > :nth-child(2) > input').click(); // Uncheck F gender 57 | cy.get('.state_rlf > .is-component > .is-item > label').then($e => { 58 | let regex = new RegExp(/^[A-Za-z]*\s\(\s[1-9]+[0-9]*\s\)$/); 59 | for(let i=0;i < $e.length; i++){ 60 | expect(regex.test($e.get(i).innerHTML)).to.be.true; 61 | } 62 | }); 63 | }); 64 | 65 | 66 | it('non regression test for issue #5', () => { 67 | cy.get(':nth-child(14) > input').click(); // Check CA State 68 | cy.get('.gender_rlf > .is-component > :nth-child(3) > input').click(); // Check M gender 69 | let ctLabel = ':nth-child(28) > label'; 70 | 71 | cy.wait('@ES'); 72 | cy.wait('@ES'); 73 | cy.wait('@ES'); 74 | cy.wait('@ES'); 75 | 76 | cy.get(ctLabel).then(e => { 77 | expect(e.get(0).innerHTML).to.contains('11'); // CT State contains 11 78 | cy.get(ctLabel).click(); // Check CT State 79 | 80 | cy.wait('@ES'); 81 | cy.wait('@ES'); 82 | 83 | cy.get('.gender_rlf > .is-component > :nth-child(2) > label').then(e => { expect(e.get(0).innerHTML).to.contains('15')}); 84 | }); 85 | 86 | }); 87 | 88 | }); 89 | 90 | 91 | 92 | describe('Test RefinementListFilter', () => { 93 | beforeEach(function() { 94 | cy.visit(_URL); 95 | cy.server(); 96 | cy.route('POST', _ES_URL).as('ES'); 97 | }); 98 | 99 | it('should check the input when the label is clicked' , function() { 100 | cy.get('label').then(($el) => { 101 | $el.get(0).click(); 102 | }); 103 | cy.get('input').then(($el) => { 104 | expect($el.get(0)).to.have.prop("checked",true); 105 | }) 106 | }); 107 | it('should display the correct title' , function () { 108 | cy.get(_TITLE).contains("State : "); 109 | }); 110 | context('Filtered research', function () { 111 | const valCheck = { 112 | name : 'tx', 113 | count : 30 114 | }; 115 | beforeEach(function () { 116 | cy.get(_REFINEMENT_LIST_FILTER + ' input').check(valCheck.name); 117 | cy.wait('@ES'); 118 | }); 119 | it('should be filtering as expected' , function () { 120 | cy.get(_REFINEMENT_LIST_FILTER + ' label[for="'+valCheck.name+'"]').contains(valCheck.name+' ( '+valCheck.count+' )'); 121 | }); 122 | it('Hits results for filtering by md state', function() { 123 | cy.get(_ITEMS).each((item, index) => { 124 | let _currentHit = refinementFilterList_sample_1.hits.hits[index]._source; 125 | cy.wrap(item).find(_FIRSTNAME).contains(_currentHit.firstname); 126 | cy.wrap(item).find(_LASTNAME).contains(_currentHit.lastname); 127 | cy.wrap(item).find(_STATE).contains(_currentHit.state); 128 | cy.wrap(item).find(_GENDER).contains(_currentHit.gender); 129 | }); 130 | }); 131 | it('should display aggregations ordered by _count ascending', function () { 132 | cy.get(_REFINEMENT_LIST_FILTER + ' label[for="'+valCheck.name+'"]').invoke('text').then((text1) => { 133 | cy.get(_REFINEMENT_LIST_FILTER + ' label:last').invoke('text').should((text2) => { 134 | expect(text1).to.equal(text2); 135 | }); 136 | }); 137 | }); 138 | }); 139 | 140 | }); 141 | 142 | 143 | describe('Test RefinementListFilter2', () => { 144 | const valCheck = { 145 | name : 'tx', 146 | name2 : 'id' 147 | }; 148 | beforeEach(function() { 149 | cy.visit(_URL2); 150 | cy.server(); 151 | cy.route('POST', _ES_URL).as('ES'); 152 | }); 153 | 154 | it('should display at least 23 aggs state', function () { 155 | cy.get('.is-item .is-refinement-list').should('have.length.lte',23); 156 | }); 157 | 158 | it('should display 57 hits when filtering Texas (tx) or Idaho (id) states' , function () { 159 | 160 | 161 | cy.wait('@ES').get(_REFINEMENT_LIST_FILTER + ' input').check(valCheck.name); 162 | cy.wait('@ES').get(_REFINEMENT_LIST_FILTER + ' input').check(valCheck.name2); 163 | 164 | cy.wait('@ES').get(_HITS).contains('57 results found'); 165 | 166 | 167 | }); 168 | it('title slot should replace the default title', function (){ 169 | cy.get(_REFINEMENT_LIST_FILTER + ' > h2').contains('US State : '); 170 | }); 171 | 172 | it('label slot should replace the default display using template', function () { 173 | cy.get(_REFINEMENT_LIST_FILTER + ' label[for="'+valCheck.name+'"]').contains('tx : 30'); 174 | 175 | }); 176 | 177 | }); 178 | -------------------------------------------------------------------------------- /src/components/SearchBox.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 199 | -------------------------------------------------------------------------------- /examples/standalone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue InnerSearch 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 157 | 158 | -------------------------------------------------------------------------------- /src/lib/Generics.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import elasticsearch from 'elasticsearch'; 3 | import Bodybuilder from 'bodybuilder'; 4 | import Store from './Store'; 5 | import { Component } from './Enums.js'; 6 | 7 | export default { 8 | computed : { 9 | // Full Elasticsearch request 10 | request : function() { 11 | return Object.assign({ 12 | index : this.header.index, 13 | type : this.header.type 14 | }, { 15 | body : this.body 16 | }); 17 | }, 18 | 19 | // Request header (index, type, client) 20 | header : () => { 21 | return Store.getters["Elasticsearch/getHeader"]; 22 | }, 23 | 24 | // Request query (generated bodybuilder json request) 25 | body : () => { 26 | return Store.getters["Elasticsearch/getBody"]; 27 | }, 28 | 29 | // Instructions (contains bodybuilder functions) 30 | instructions : () => { 31 | return Store.getters["Elasticsearch/getInstructions"]; 32 | }, 33 | 34 | // Aggregations (contains all components aggregations objects) 35 | aggregations : () => { 36 | return Store.getters["Elasticsearch/getAggregations"]; 37 | }, 38 | 39 | // Communication bus 40 | bus : () => { 41 | return Store.getters["Elasticsearch/getBus"]; 42 | }, 43 | 44 | // Interactive components (for $emit events) 45 | components : () => { 46 | return Store.getters["Elasticsearch/getComponents"] 47 | }, 48 | 49 | // Output items 50 | items : () => { 51 | return Store.getters["Hits/getItems"]; 52 | }, 53 | 54 | // Items count 55 | score : () => { 56 | return Store.getters["Hits/getScore"]; 57 | }, 58 | }, 59 | 60 | methods : { 61 | /* 62 | Store Elasticsearch Header Setters 63 | */ 64 | setHost : (host) => { 65 | Store.commit("Elasticsearch/setHost", new elasticsearch.Client({ host })); 66 | }, 67 | setIndex : (index) => { 68 | Store.commit("Elasticsearch/setIndex", index); 69 | }, 70 | setType : (type) => { 71 | Store.commit("Elasticsearch/setType", type); 72 | }, 73 | 74 | 75 | /* 76 | Store Elasticsearch Body Setter 77 | */ 78 | setBody : (body) => { 79 | Store.commit("Elasticsearch/setBody", body); 80 | }, 81 | 82 | 83 | /* 84 | Store Elasticsearch Instructions Add 85 | */ 86 | addInstruction : (instruction) => { 87 | Store.commit("Elasticsearch/addInstruction", instruction); 88 | }, 89 | 90 | /* 91 | Store Elasticsearch Instructions Remove 92 | */ 93 | removeInstruction : (instruction) => { 94 | Store.commit("Elasticsearch/removeInstruction", instruction); 95 | }, 96 | 97 | 98 | /* 99 | Store Elasticsearch Aggregations Settings 100 | */ 101 | setAggregations : (name, value, isDynamic, orderKey, orderDirection) => { 102 | Store.commit("Elasticsearch/setAggregations", { name, value, isDynamic, orderKey, orderDirection }); 103 | }, 104 | 105 | 106 | /* 107 | Add a debounce event to ES store 108 | That permits to clear (reset) all listed hanged debounces when an user is triggered fetch() 109 | */ 110 | addDebounce : (component, debounce) => { 111 | Store.commit("Elasticsearch/addDebounce", { component, debounce }); 112 | }, 113 | 114 | 115 | /* 116 | Reset all the listed debounces 117 | Called by fetch() 118 | */ 119 | resetDebounce : (component = null) => { 120 | Store.commit("Elasticsearch/resetDebounce", component); 121 | }, 122 | 123 | 124 | /* 125 | Add an interactive component to the store 126 | Returns the component ID (CID) 127 | */ 128 | addComponent : function(type, self) { 129 | let _CID = Store.getters["Elasticsearch/getCid"], 130 | _name = type + '_' + _CID; 131 | 132 | Store.commit('Elasticsearch/addComponent', { type, self }); 133 | 134 | return _name; 135 | }, 136 | 137 | /* 138 | Get a specific component list by their enum type 139 | Returns a list of components or an empty list if any component is found 140 | */ 141 | getComponents : function(type) { 142 | let _components = this.components; 143 | if (_components[type] !== undefined) 144 | return _components[type]; 145 | 146 | return []; 147 | }, 148 | 149 | 150 | /* 151 | Add item into the Store 152 | */ 153 | addItem : (item) => { 154 | Store.commit("Hits/addItem", item); 155 | }, 156 | 157 | 158 | /* 159 | Empty the store items list 160 | */ 161 | clearItems : () => { 162 | Store.commit("Hits/clearItems"); 163 | }, 164 | 165 | 166 | /* 167 | Set the score of items 168 | */ 169 | setScore : (score) => { 170 | Store.commit("Hits/setScore", score); 171 | }, 172 | 173 | 174 | /* 175 | Mount global function 176 | */ 177 | mountInstructions : function(instructions) { 178 | // Bodybuilder object 179 | let _BD = Bodybuilder(); 180 | 181 | // Execute all instructions to create request 182 | instructions.forEach(instr => { 183 | _BD[instr.fun](...instr.args); 184 | }); 185 | 186 | // Return the built request 187 | return _BD.build(); 188 | }, 189 | 190 | /* 191 | Mount full request 192 | */ 193 | mount : function() { 194 | // Bodybuilder object 195 | let _BD = Bodybuilder().from(0).size(10); 196 | 197 | // Execute all instructions to create request 198 | this.instructions.forEach(instr => { 199 | _BD[instr.fun](...instr.args); 200 | }); 201 | 202 | // Store the JSON request into the body 203 | this.setBody(_BD.build()); 204 | 205 | // Debug 206 | console.log("[Generics:Mount] Request : ", this.request); 207 | }, 208 | 209 | 210 | /* 211 | Execute ES request 212 | */ 213 | fetch : function(self = undefined) { 214 | console.log("[Generics:Fetch] Request : ", this.request); 215 | // Reset debounce events 216 | this.resetDebounce(); 217 | 218 | // Reset the pagination by emitting to appropriate components 219 | this.bus.$emit('resetPagination'); 220 | 221 | // Fetch the hits 222 | return this.header.client.search(this.request).then((resp) => { 223 | 224 | // Remove all hits 225 | this.clearItems(); 226 | 227 | var hits = resp.hits.hits; 228 | //console.log("[Generics:Fetch] Response : ", resp); 229 | //console.log("[Generics:Fetch] Aggs : ", resp.aggregations); 230 | 231 | // Update aggregations after each ES request 232 | let _params = { 233 | 'aggs' : resp.aggregations 234 | }; 235 | if (self !== undefined) 236 | _params.base = self.$data.CID; 237 | this.bus.$emit('updateAggs', _params); 238 | 239 | if (hits.length === 0) 240 | this.setScore(0); 241 | else { 242 | hits.forEach((hit) => { 243 | this.addItem(hit); 244 | }); 245 | 246 | this.setScore(resp.hits.total) 247 | } 248 | 249 | }, function (err) { 250 | this.setScore(0); 251 | }); 252 | }, 253 | 254 | 255 | /* 256 | Remove instructions of the component 257 | */ 258 | removeInstructions : function() { 259 | this.localInstructions.forEach(instruction => { 260 | this.removeInstruction(instruction); 261 | }); 262 | this.localInstructions = []; 263 | }, 264 | 265 | 266 | /* 267 | Create independent request for autocomplete component 268 | Fetch the hits which match with the value 269 | */ 270 | createRequestForSuggs : function(value, fields, selections, fun, size) { 271 | // Bodybuilder object 272 | let _request = this.clone(this.request); 273 | 274 | // Feed the request 275 | let _body = Bodybuilder().size(size); 276 | fields.forEach(field => { 277 | _body[fun]('prefix', field, value); 278 | }); 279 | 280 | // Don't fetch items already selected 281 | selections.forEach(selection => { 282 | _body.notFilter('term', '_id', selection._id); 283 | }); 284 | 285 | // Convert the object to json 286 | _request.body = _body.build(); 287 | 288 | // Highlighter 289 | _request.body["highlight"] = { 290 | "fields" : {} 291 | }; 292 | 293 | fields.forEach(field => { 294 | _request.body.highlight.fields[field] = {}; 295 | }); 296 | 297 | return _request; 298 | }, 299 | 300 | 301 | /* 302 | Create independent request for RefinementListFilter component 303 | Get the value of each aggs to init items count 304 | */ 305 | createRequestForAggs : function (field, size, orderKey, orderDirection) { 306 | // Bodybuilder object 307 | let _request = this.clone(this.request); 308 | 309 | // Store the JSON request into the body 310 | _request.body = Bodybuilder() 311 | .size(0) 312 | .aggregation("terms", field, { 313 | order : { 314 | [orderKey] : orderDirection 315 | }, 316 | size : size 317 | }).aggregation("cardinality",field) 318 | .build(); 319 | 320 | return _request; 321 | }, 322 | 323 | clone : (object) => { 324 | return JSON.parse(JSON.stringify(object)); 325 | }, 326 | 327 | remove : (object) => { 328 | object.fun = null; 329 | object.args = null; 330 | } 331 | } 332 | }; 333 | -------------------------------------------------------------------------------- /examples/Test_ResetButton.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 143 | -------------------------------------------------------------------------------- /examples/Index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Native tags 3 | */ 4 | 5 | * { 6 | box-sizing : inherit; 7 | margin : 0; 8 | padding : 0; 9 | } 10 | 11 | body, button, input, select, textarea { 12 | font-family : BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; 13 | } 14 | 15 | h1, h2, h3, h4, h5, h6 { 16 | margin-bottom : 6px; 17 | font-size : 125%; 18 | font-weight : 300; 19 | } 20 | 21 | 22 | /* 23 | Global tags 24 | */ 25 | 26 | .is-columns { 27 | display : flex; 28 | width : 95%; 29 | margin : 0 auto; 30 | } 31 | 32 | .is-column.is-one-fifth { 33 | flex : none; 34 | width : 25%; 35 | } 36 | 37 | .is-column { 38 | display : block; 39 | flex-basis : 0; 40 | flex-grow : 1; 41 | flex-shrink : 1; 42 | } 43 | 44 | .is-title { 45 | font-size : 4em; 46 | color : #3C434B; 47 | font-family : Calibri, sans-serif; 48 | margin : 20px 0 10px 5%; 49 | } 50 | 51 | .is-line { 52 | width : 90%; 53 | margin: auto; 54 | } 55 | 56 | 57 | /* 58 | Component tags 59 | */ 60 | 61 | .is-component { 62 | border : 1px solid #A9A9A9; 63 | font-family : Calibri, sans-serif; 64 | } 65 | 66 | .is-icon { 67 | background-repeat : no-repeat; 68 | background-position : center center; 69 | background-size : contain; 70 | } 71 | 72 | .is-field { 73 | font-size : 2em; 74 | padding : 5px; 75 | } 76 | 77 | .is-button { 78 | padding : 15px; 79 | border : 1px solid transparent; 80 | border-radius : 4px; 81 | font-size : 1.25em; 82 | cursor : pointer; 83 | user-select : none; 84 | } 85 | 86 | .is-field:focus { 87 | outline: none; 88 | } 89 | 90 | 91 | /* 92 | Searchbox component 93 | */ 94 | 95 | .is-component.is-searchbox, .is-component.is-search-datalist, .is-component.is-paginate { 96 | display : flex; 97 | width : 90%; 98 | } 99 | 100 | .is-component.is-searchbox { 101 | margin : 20px auto; 102 | } 103 | 104 | .is-icon.is-searchbox, .is-icon.is-search-datalist { 105 | width : 50px; 106 | height : 50px; 107 | margin : 5px; 108 | background-image : url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KCgo8IS0tIFRoZSBpY29uIGNhbiBiZSB1c2VkIGZyZWVseSBpbiBib3RoIHBlcnNvbmFsIGFuZCBjb21tZXJjaWFsIHByb2plY3RzIHdpdGggbm8gYXR0cmlidXRpb24gcmVxdWlyZWQsIGJ1dCBhbHdheXMgYXBwcmVjaWF0ZWQuIApZb3UgbWF5IE5PVCBzdWItbGljZW5zZSwgcmVzZWxsLCByZW50LCByZWRpc3RyaWJ1dGUgb3Igb3RoZXJ3aXNlIHRyYW5zZmVyIHRoZSBpY29uIHdpdGhvdXQgZXhwcmVzcyB3cml0dGVuIHBlcm1pc3Npb24gZnJvbSBpY29ubW9uc3RyLmNvbSAtLT4KCgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgoKPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoKCSB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4KCjxwYXRoIGlkPSJtYWduaWZpZXItMi1pY29uIiBkPSJNNDYwLjM1NSw0MjEuNTlMMzUzLjg0NCwzMTUuMDc4YzIwLjA0MS0yNy41NTMsMzEuODg1LTYxLjQzNywzMS44ODUtOTguMDM3CgoJQzM4NS43MjksMTI0LjkzNCwzMTAuNzkzLDUwLDIxOC42ODYsNTBDMTI2LjU4LDUwLDUxLjY0NSwxMjQuOTM0LDUxLjY0NSwyMTcuMDQxYzAsOTIuMTA2LDc0LjkzNiwxNjcuMDQxLDE2Ny4wNDEsMTY3LjA0MQoKCWMzNC45MTIsMCw2Ny4zNTItMTAuNzczLDk0LjE4NC0yOS4xNThMNDE5Ljk0NSw0NjJMNDYwLjM1NSw0MjEuNTl6IE0xMDAuNjMxLDIxNy4wNDFjMC02NS4wOTYsNTIuOTU5LTExOC4wNTYsMTE4LjA1NS0xMTguMDU2CgoJYzY1LjA5OCwwLDExOC4wNTcsNTIuOTU5LDExOC4wNTcsMTE4LjA1NmMwLDY1LjA5Ni01Mi45NTksMTE4LjA1Ni0xMTguMDU3LDExOC4wNTZDMTUzLjU5LDMzNS4wOTcsMTAwLjYzMSwyODIuMTM3LDEwMC42MzEsMjE3LjA0MQoKCXoiLz4KCjwvc3ZnPgoK'); 109 | opacity : .4; 110 | } 111 | 112 | .is-field.is-searchbox, .is-field.is-search-datalist { 113 | width : 100%; 114 | border : 0; 115 | color : #2C2C2C; 116 | } 117 | 118 | 119 | /* 120 | SearchDatalist component 121 | */ 122 | 123 | .is-component.is-search-datalist { 124 | margin : 0 auto; 125 | } 126 | 127 | .is-search-datalist-items, .is-tag-filter { 128 | width : 90%; 129 | margin : 0 auto; 130 | } 131 | 132 | .is-search-datalist-items li, .is-tag-filter li { 133 | display : inline-block; 134 | margin : 10px 5px; 135 | padding : 5px 10px; 136 | border-radius : 4px; 137 | background-color : rgba(209, 209, 209, 0.8); 138 | transition : all 0.3s ease; 139 | cursor : pointer; 140 | } 141 | 142 | .is-search-datalist-items li:hover, .is-tag-filter li:hover { 143 | background-color : rgba(209, 209, 209, 1); 144 | } 145 | 146 | .is-search-datalist-items li:first-child, .is-tag-filter li:first-child { 147 | margin-left : 0 !important; 148 | } 149 | 150 | .is-search-datalist-items li:after, .is-tag-filter li:after { 151 | content : 'x'; 152 | margin-left : 5px; 153 | color : rgba(129, 35, 35, 0.6); 154 | font-family : Calibri, sans-serif; 155 | font-weight : bolder; 156 | transition : all 0.3s ease; 157 | } 158 | 159 | .is-search-datalist-items li:hover:after, .is-tag-filter li:hover:after { 160 | color : rgba(129, 35, 35, 0.8); 161 | } 162 | 163 | .is-search-datalist-suggestions { 164 | width : 90%; 165 | margin : 0 auto; 166 | border-right : 1px solid #A9A9A9; 167 | border-bottom : 1px solid #A9A9A9; 168 | border-left : 1px solid #A9A9A9; 169 | } 170 | 171 | .is-search-datalist-suggestions ul { 172 | max-height : 250px; 173 | overflow : auto; 174 | } 175 | 176 | .is-search-datalist-suggestions li { 177 | padding : 5px 10px; 178 | font-family : Calibri, sans-serif; 179 | } 180 | 181 | .is-search-datalist-suggestions li:not(.noresult) { 182 | cursor : pointer; 183 | } 184 | 185 | .is-search-datalist-suggestions li:not(.noresult):nth-child(odd) { 186 | background-color : #EAEAEA; 187 | } 188 | 189 | .is-search-datalist-suggestions li:not(.noresult):nth-child(even) { 190 | background-color : #F9F9F9; 191 | } 192 | 193 | .is-search-datalist-suggestions li.noresult { 194 | font-style : oblique; 195 | font-weight : bolder; 196 | } 197 | 198 | .is-search-datalist-suggestions li:not(.noresult).selected { 199 | background : #181818; 200 | color : #F0F0F0; 201 | } 202 | 203 | .is-search-datalist-suggestions li em { 204 | font-style : normal; 205 | font-weight : bold; 206 | } 207 | 208 | 209 | /* 210 | NumericListFilter component 211 | */ 212 | 213 | .is-nlf-inputs { 214 | margin : 0; 215 | display: flex; 216 | } 217 | 218 | .is-nlf { 219 | margin: 20px auto; 220 | width: 90%; 221 | } 222 | 223 | .is-range{ 224 | width: 50%; 225 | 226 | } 227 | .is-nlf-title { 228 | margin-bottom: 5px; 229 | } 230 | 231 | 232 | /* 233 | RefinementListFilter component 234 | */ 235 | 236 | .is-component.is-refinement-list { 237 | width : 90%; 238 | margin : 20px auto; 239 | padding : 15px; 240 | font-size : 1.25em; 241 | box-sizing : border-box; 242 | } 243 | 244 | .is-item.is-refinement-list { 245 | display : inline-block; 246 | width : 100%; 247 | } 248 | 249 | 250 | /* 251 | SearchButton component 252 | */ 253 | 254 | .is-component.is-search-button, .is-component.is-reset-button { 255 | display : inline-block; 256 | margin : 5px 10px 5px 0; 257 | border : 0; 258 | } 259 | 260 | .is-button.is-search-button { 261 | color : #fff; 262 | background-color : #337ab7; 263 | border-color : #2e6da4; 264 | } 265 | 266 | .is-button.is-search-button:hover, .is-button.is-search-button:active { 267 | color : #fff; 268 | background-color : #286090; 269 | border-color : #204d74; 270 | } 271 | 272 | .is-button.is-search-button:focus { 273 | color : #fff; 274 | background-color : #286090; 275 | border-color : #122b40; 276 | } 277 | 278 | 279 | /* 280 | ResetButton component 281 | */ 282 | 283 | .is-button.is-reset-button { 284 | color : #fff; 285 | background-color : #B73337; 286 | border-color : #A42E2E; 287 | } 288 | 289 | .is-button.is-reset-button:hover, .is-button.is-reset-button:active { 290 | color : #fff; 291 | background-color : #902828; 292 | border-color : #742020; 293 | } 294 | 295 | .is-button.is-reset-button:focus { 296 | color : #fff; 297 | background-color : #902828; 298 | border-color : #401212; 299 | } 300 | 301 | 302 | /* 303 | Hits component 304 | */ 305 | 306 | .is-component.is-hits { 307 | width : 90%; 308 | margin : 20px auto; 309 | padding : 15px; 310 | font-size : 1.25em; 311 | box-sizing : border-box; 312 | } 313 | 314 | .is-score.is-hits { 315 | margin : 10px 0 20px 50px; 316 | font-size : 1.75em; 317 | font-weight : bolder; 318 | font-variant : small-caps; 319 | } 320 | 321 | .is-item.is-hits { 322 | width : 90%; 323 | margin : 20px auto; 324 | padding : 20px; 325 | background : #EFEFEF; 326 | } 327 | 328 | .is-item.is-hits ul { 329 | margin : 0; 330 | } 331 | 332 | 333 | /* 334 | Paginate component 335 | */ 336 | 337 | .is-component.is-paginate { 338 | margin : 20px auto; 339 | align-items : center; 340 | justify-content : center; 341 | text-align : center; 342 | border : 1px solid transparent; 343 | user-select : none; 344 | } 345 | 346 | .is-previous { 347 | order: 1; 348 | } 349 | 350 | .is-list { 351 | display : flex; 352 | flex-grow : 1; 353 | align-items : center; 354 | justify-content : center; 355 | text-align : center; 356 | list-style : none; 357 | list-style-type : none; 358 | list-style-position : initial; 359 | list-style-image : initial; 360 | order : 2; 361 | } 362 | 363 | .is-next { 364 | order: 3; 365 | } 366 | 367 | .is-next, .is-previous { 368 | padding-left : .75em; 369 | padding-right : .75em; 370 | white-space : nowrap; 371 | } 372 | 373 | .is-page, .is-next, .is-previous { 374 | min-width : 2.25em; 375 | border-color : #dbdbdb; 376 | color : #363636; 377 | cursor : pointer; 378 | } 379 | 380 | .is-page.is-active { 381 | background-color: #3273dc; 382 | border-color: #3273dc; 383 | color: #fff; 384 | } 385 | 386 | .is-page:active:not(.is-active), .is-next:active, .is-previous:active { 387 | box-shadow : inset 0 1px 2px rgba(10,10,10,.2); 388 | } 389 | 390 | .is-page:hover:not(.is-active), .is-next:hover, .is-previous:hover { 391 | border-color : #b5b5b5; 392 | } 393 | 394 | .is-page, .is-next, .is-previous, .is-ellipsis { 395 | margin : .25rem; 396 | justify-content : center; 397 | text-align : center; 398 | } 399 | 400 | .is-page, .is-next, .is-previous, .is-ellipsis { 401 | display : inline-flex; 402 | align-items : center; 403 | border : 1px solid transparent; 404 | border-radius : 4px; 405 | font-size : 1rem; 406 | height : 2.25em; 407 | padding-bottom : calc(.175em - 1px); 408 | padding-left : calc(.325em - 1px); 409 | padding-right : calc(.325em - 1px); 410 | padding-top : calc(.175em - 1px); 411 | } 412 | 413 | 414 | /* 415 | TagFilter component 416 | */ 417 | 418 | .is-component.is-tag-filter { 419 | display : inline-block; 420 | width : initial; 421 | border : none; 422 | margin : 0 auto; 423 | } -------------------------------------------------------------------------------- /src/components/RefinementListFilter.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/SearchDatalist.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | --------------------------------------------------------------------------------