Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
feat(search-client): Add support for Custom Search Clients (#432)
Browse files Browse the repository at this point in the history
* feat: Add `search-client` support

* feat: Call `addAlgoliaAgent` only if exists

* test: Add tests for `search-client`

* docs: Add `search-client` prop to Index documentation

* refactor: Remove `Object.create()` to create client

* refactor: Use `else if` for ESLint

* docs: Add section on store with custom search client
  • Loading branch information
francoischalifour authored and Haroenv committed Apr 25, 2018
1 parent dc8de87 commit c3d35bc
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/src/components/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Provide search query parameters:
| cache | Boolean | `true` | Whether to cache results or not. See [the documentation](https://www.algolia.com/doc/tutorials/getting-started/quick-start-with-the-api-client/javascript/#cache) |
| auto-search | Boolean | `true` | Whether to initiate a query to Algolia when this component is mounted |
| stalledSearchDelay | number | `200` | Time before the search is considered unresponsive. Used to display a loading indicator. |
| search-client | Object | `` | The search client to plug to InstantSearch |

## Slots

Expand Down
36 changes: 36 additions & 0 deletions docs/src/getting-started/search-store.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,42 @@ const searchStore = createFromAlgoliaClient(client);

Note that there is no reason to provide your own client if you are not reusing it elsewhere.

### Create a search store from a custom search client

If you want to use the [official JavaScript API Client](https://github.com/algolia/algoliasearch-client-javascript) on your backend, or even a custom search client, you can create an object implementing the `search()` method (and `searchForFacetValues()` if needed).

The search client can be passed to the [`search-client`](../components/index.html#props) prop of the [`Index`](../components/index.html):

```html
<template>
<ais-index
index-name="your_indexName"
:search-client="searchClient"
>
<!-- Add your InstantSearch components here. -->
</ais-index>
</template>

<script>
export default {
data() {
return {
searchClient: {
search(requests) {
// perform the requests
return response;
},
searchForFacetValues(requests) {
// perform the requests
return response;
},
},
},
},
};
</script>
```

### Create a search store from an Algolia helper instance

The [Algolia helper](https://github.com/algolia/algoliasearch-helper-js) is a JavaScript library that is built on top of the Algolia API client. Its goal is to enable a simple API to achieve advanced queries while also providing utility methods and behavior like keeping track of the last result.
Expand Down
40 changes: 38 additions & 2 deletions src/components/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
</template>

<script>
import { createFromAlgoliaCredentials } from '../store';
import {
createFromAlgoliaCredentials,
createFromAlgoliaClient,
} from '../store';
import algoliaComponent from '../component';
export default {
Expand All @@ -17,6 +20,9 @@ export default {
return this._searchStore;
},
},
searchClient: {
type: Object,
},
apiKey: {
type: String,
default() {
Expand Down Expand Up @@ -73,7 +79,37 @@ export default {
};
},
provide() {
if (!this.searchStore) {
if (this.searchClient) {
if (!this.indexName) {
throw new Error(
'vue-instantsearch: `indexName` is required with `searchClient`'
);
}
if (this.searchStore) {
throw new Error('`searchStore` cannot be used with `searchClient`');
}
if (this.appId) {
throw new Error(
'vue-instantsearch: `appId` cannot be used with `searchClient`'
);
}
if (this.apiKey) {
throw new Error(
'vue-instantsearch: `apiKey` cannot be used with `searchClient`'
);
}
if (typeof this.searchClient.search !== 'function') {
throw new Error(
'vue-instantsearch: `searchClient` must implement a method `search(requests)`'
);
}
this._localSearchStore = createFromAlgoliaClient(this.searchClient);
} else if (!this.searchStore) {
this._localSearchStore = createFromAlgoliaCredentials(
this.appId,
this.apiKey,
Expand Down
11 changes: 11 additions & 0 deletions src/components/__tests__/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Search Client API collision and apiKey 1`] = `"vue-instantsearch: \`apiKey\` cannot be used with \`searchClient\`"`;

exports[`Search Client API collision and appId 1`] = `"vue-instantsearch: \`appId\` cannot be used with \`searchClient\`"`;

exports[`Search Client API collision and searchStore 1`] = `"\`searchStore\` cannot be used with \`searchClient\`"`;

exports[`Search Client API collision without indexName 1`] = `"vue-instantsearch: \`indexName\` is required with \`searchClient\`"`;

exports[`Search Client Properties throws if no \`search()\` method 1`] = `"vue-instantsearch: \`searchClient\` must implement a method \`search(requests)\`"`;
120 changes: 120 additions & 0 deletions src/components/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import Vue from 'vue';
import Index from '../Index.vue';

describe('Search Client', () => {
describe('Properties', () => {
it('throws if no `search()` method', () => {
expect(() => {
const Component = Vue.extend(Index);

const vm = new Component({
propsData: {
indexName: 'indexName',
searchClient: {},
},
});

vm.$mount();
}).toThrowErrorMatchingSnapshot();
});
});

describe('API collision', () => {
test('and indexName', () => {
expect(() => {
const Component = Vue.extend(Index);

const vm = new Component({
propsData: {
indexName: 'indexName',
searchClient: {
search() {
return Promise.resolve({ results: [{ hits: [] }] });
},
},
},
});

vm.$mount();
}).not.toThrow();
});

test('without indexName', () => {
expect(() => {
const Component = Vue.extend(Index);

const vm = new Component({
propsData: {
searchClient: {
search() {
return Promise.resolve({ results: [{ hits: [] }] });
},
},
},
});

vm.$mount();
}).toThrowErrorMatchingSnapshot();
});

test('and appId', () => {
expect(() => {
const Component = Vue.extend(Index);

const vm = new Component({
propsData: {
indexName: 'indexName',
appId: 'appId',
searchClient: {
search() {
return Promise.resolve({ results: [{ hits: [] }] });
},
},
},
});

vm.$mount();
}).toThrowErrorMatchingSnapshot();
});

test('and apiKey', () => {
expect(() => {
const Component = Vue.extend(Index);

const vm = new Component({
propsData: {
indexName: 'indexName',
apiKey: 'apiKey',
searchClient: {
search() {
return Promise.resolve({ results: [{ hits: [] }] });
},
},
},
});

vm.$mount();
}).toThrowErrorMatchingSnapshot();
});

test('and searchStore', () => {
expect(() => {
const Component = Vue.extend(Index);

const vm = new Component({
propsData: {
indexName: 'indexName',
searchStore: {},
searchClient: {
search() {
return Promise.resolve({ results: [{ hits: [] }] });
},
},
},
});

vm.$mount();
}).toThrowErrorMatchingSnapshot();
});
});
});
6 changes: 5 additions & 1 deletion src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ export class Store {
this._helper.on('result', onHelperResult.bind(this));
this._helper.on('search', onHelperSearch.bind(this));

this._helper.getClient().addAlgoliaAgent(`vue-instantsearch ${version}`);
const client = this._helper.getClient();

if (typeof client.addAlgoliaAgent === 'function') {
client.addAlgoliaAgent(`vue-instantsearch ${version}`);
}

this._stalledSearchTimer = null;
this.isSearchStalled = true;
Expand Down

0 comments on commit c3d35bc

Please sign in to comment.