├── 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 |
8 | ...
9 |
10 |
11 | ```
12 |
13 | Examples :
14 |
15 | ```html
16 |
17 |
18 |
19 |
20 | Identity (firstname, lastname) :
21 | {{ item._source.firstname }}
22 | {{ item._source.lastname }}
23 | ({{ item._source.state }}, {{ item._source.gender }})
24 |
25 |
26 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 | No result found
6 | 1 result found
7 | {{ hits.score }} results found
8 |
9 |
12 |
13 |
14 |
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 |
2 |
3 |
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 |
2 |
3 |
4 |
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 |
2 |
3 |
4 |
5 |
6 |
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | []()
2 |
3 | # InnerSearch : Vue.js components for ElasticSearch
4 | [](https://www.npmjs.com/package/vue-innersearch)
5 | [](https://www.npmjs.com/package/vue-innersearch)
6 | [](https://app.fossa.io/projects/git%2Bgithub.com%2FInnerSearch%2Fvue-innersearch?ref=badge_shield)
7 | [](https://travis-ci.org/InnerSearch/vue-innersearch)
8 | []()
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 | []()
22 |
23 | Corresponding code :
24 | ```html
25 |
26 |
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 | [](https://app.fossa.io/projects/git%2Bgithub.com%2FInnerSearch%2Fvue-innersearch?ref=badge_large)
77 |
--------------------------------------------------------------------------------
/examples/Test_RefinementListFilter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 | []()
16 |
17 | Corresponding code :
18 | ```html
19 |
20 |
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 |
2 |
3 |
4 |
5 |
6 |
75 |
76 |
79 |
--------------------------------------------------------------------------------
/examples/Test_RefinementListFilter2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
74 |
75 |
78 |
--------------------------------------------------------------------------------
/src/components/NumericListFilter.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/Test_RLF_Searchbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
27 |
28 |
34 | {{ item.key }} ( {{ item.doc_count }} )
35 | {{ item.key }}
36 |
37 |
38 |
39 | ```
40 |
41 | Refinement List with radio button
42 |
43 | ````html
44 |
45 |
46 |
47 |
53 | {{ item.key }} ( {{ item.doc_count }} )
54 | {{ item.key }}
55 |
56 |
57 |
58 | ````
59 |
60 | Refinement List with simple dropdownlist
61 | ```html
62 |
63 |
64 |
65 |
66 |
67 | {{ item.key }} ( {{ item.doc_count }} )
68 | {{ item.key }}
69 |
70 |
71 |
72 |
73 |
74 | ```
75 |
76 | Refinement List with dropdownlist (multiple)
77 | ```html
78 |
79 |
80 |
81 |
82 |
83 | {{ item.key }} ( {{ item.doc_count }} )
84 | {{ item.key }}
85 |
86 |
87 |
88 |
89 |
90 | ```
91 |
--------------------------------------------------------------------------------
/src/components/Paginate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
43 |
44 |
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 |
2 |
3 |
4 |
5 | Search : {{ targetData }}
6 |
7 |
8 |
9 |
10 |
11 | Filters ({{ targetData }})
12 |
13 |
14 |
15 |
16 | Filter : {{ item }}
17 |
18 |
19 |
20 |
21 |
22 | From {{ targetData.gte }} to {{ targetData.lte }}
23 | From {{ targetData.gte }}
24 | To {{ targetData.lte }}
25 |
26 |
27 |
28 |
29 |
30 | Clear {{ targetData.length }} filters
31 |
32 |
33 |
34 |
35 | {{ item }}
36 |
37 |
38 |
39 |
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 |
2 |
6 |
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 |
2 |
3 |
4 |
5 |
143 |
--------------------------------------------------------------------------------
/examples/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
20 | {{ item.key }} ( {{ item.doc_count }} )
21 | {{ item.key }}
22 |
23 |
24 |
25 | view more
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/SearchDatalist.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ selection }}
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 | No result for "{{ entry }}"
21 |
22 |
23 |
24 |
25 | {{ suggestion }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------