Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(autocomplete-js): enable HTML templating #920

Merged
merged 29 commits into from
Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e9c61ec
feat(html): enable HTML templating
sarahdayan Mar 16, 2022
9b41329
feat(html): bind function only once (#921)
Haroenv Mar 18, 2022
0951654
feat(html): allow render function injection
sarahdayan Mar 18, 2022
14a8bb2
chore: build example on CodeSandbox
sarahdayan Mar 18, 2022
64db206
test: introduce template tests
sarahdayan Mar 30, 2022
fdd0b7d
fix: adjust HTMLTemplate type
sarahdayan Mar 31, 2022
86e6681
feat: accept VNode arrays as templates
sarahdayan Mar 31, 2022
3db90db
chore: update TypeScript ESLint
sarahdayan Mar 31, 2022
b361696
test: use fixed IDs to keep test order commutable
sarahdayan Mar 31, 2022
c1e0313
test: test IE 11 shim
sarahdayan Mar 31, 2022
8d6f5e5
docs: update example
sarahdayan Mar 31, 2022
ae26254
test: update test
sarahdayan Mar 31, 2022
3000f66
test: add tests for renderNoResults
sarahdayan Mar 31, 2022
4d31a4c
test: add test for non-provided render function
sarahdayan Mar 31, 2022
e38f75c
build: adjust bundle size
sarahdayan Mar 31, 2022
07d0877
test: avoid typing shim function
sarahdayan Mar 31, 2022
7f364f9
chore: move back to previous TypeScript ESLint version
sarahdayan Mar 31, 2022
74ffbc9
test: complete tests
sarahdayan Mar 31, 2022
fd7067f
chore: no longer export type
sarahdayan Mar 31, 2022
9d8b5d9
chore: remove sandbox
sarahdayan Mar 31, 2022
76b8dfb
chore: trigger build
sarahdayan Mar 31, 2022
b2aba31
refactor: only pass renderer
sarahdayan Apr 1, 2022
0ac218c
test: it's even, not odd
sarahdayan Apr 1, 2022
e984b54
feat: default to Preact's function
sarahdayan Apr 4, 2022
a21b190
fix: avoid passing render to templates
sarahdayan Apr 4, 2022
b9bc9fc
feat: make html reactive
sarahdayan Apr 4, 2022
6f000a5
feat: warn when using render option and no renderer.render
sarahdayan Apr 5, 2022
f7c51d6
fix: remove white space
sarahdayan Apr 5, 2022
40035ff
chore: update package version
sarahdayan Apr 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
{
"path": "packages/autocomplete-js/dist/umd/index.production.js",
"maxSize": "16.75 kB"
"maxSize": "17.5 kB"
},
{
"path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js",
Expand Down
34 changes: 34 additions & 0 deletions examples/html-templates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Autocomplete with HTML templates example

This example shows how to use HTML templates in Autocomplete.

<p align="center"><img src="capture.png?raw=true" alt="A capture of the Autocomplete with HTML templates example" /></p>

## Demo

[Access the demo](https://codesandbox.io/s/github/algolia/autocomplete/tree/next/examples/html-templates)

## How to run this example locally

### 1. Clone this repository

```sh
git clone [email protected]:algolia/autocomplete.git
```

### 2. Install the dependencies and run the server

```sh
yarn
yarn workspace @algolia/autocomplete-example-html-templates start
```

Alternatively, you may use npm:

```sh
cd examples/html-templates
npm install
npm start
```

Open <http://localhost:1234> to see your app.
65 changes: 65 additions & 0 deletions examples/html-templates/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js';
import algoliasearch from 'algoliasearch/lite';

import '@algolia/autocomplete-theme-classic';

const appId = 'latency';
const apiKey = '6be0576ff61c053d5f9a3225e2a90f76';
const searchClient = algoliasearch(appId, apiKey);

autocomplete({
container: '#autocomplete',
placeholder: 'Search',
getSources({ query }) {
return [
{
sourceId: 'products',
getItems() {
return getAlgoliaResults({
searchClient,
queries: [
{
indexName: 'instant_search',
query,
},
],
});
},
templates: {
item({ item, components, html }) {
return html`<div class="aa-ItemWrapper">
<div class="aa-ItemContent">
<div
class="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop"
>
<img
src="${item.image}"
alt="${item.name}"
width="40"
height="40"
/>
</div>

<div class="aa-ItemContentBody">
<div class="aa-ItemContentTitle">
${components.Highlight({ hit: item, attribute: 'name' })}
</div>
<div class="aa-ItemContentDescription">
By <strong>${item.brand}</strong> in ${' '}
<strong>${item.categories[0]}</strong>
</div>
</div>
</div>
</div>`;
},
},
},
];
},
render({ children, render, html }, root) {
render(html`<div class="aa-SomeResults">${children}</div>`, root);
},
renderNoResults({ children, render, html }, root) {
render(html`<div class="aa-NoResults">${children}</div>`, root);
},
});
Binary file added examples/html-templates/capture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions examples/html-templates/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Parcel picks the `source` field of the monorepo packages and thus doesn't
// apply the Babel config. We therefore need to manually override the constants
// in the app.
// See https://twitter.com/devongovett/status/1134231234605830144
global.__DEV__ = process.env.NODE_ENV !== 'production';
global.__TEST__ = false;
Binary file added examples/html-templates/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions examples/html-templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="shortcut icon" href="favicon.png" type="image/x-icon" />
<link rel="stylesheet" href="style.css" />

<title>HTML Templates | Autocomplete</title>
</head>

<body>
<div class="container">
<div id="autocomplete"></div>
</div>

<script src="env.js"></script>
<script src="app.js"></script>
</body>
</html>
25 changes: 25 additions & 0 deletions examples/html-templates/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@algolia/autocomplete-example-html-templates",
"description": "Autocomplete example with HTML templates",
"version": "1.5.7",
"private": true,
"license": "MIT",
"scripts": {
"build": "parcel build index.html",
"start": "parcel index.html"
},
"dependencies": {
"@algolia/autocomplete-js": "1.5.7",
"@algolia/autocomplete-theme-classic": "1.5.7",
"algoliasearch": "4.9.1"
},
"devDependencies": {
"@algolia/client-search": "4.9.1",
"parcel": "2.0.0-beta.2"
},
"keywords": [
"algolia",
"autocomplete",
"javascript"
]
}
20 changes: 20 additions & 0 deletions examples/html-templates/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
* {
box-sizing: border-box;
}

body {
background-color: rgb(244, 244, 249);
color: rgb(65, 65, 65);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding: 1rem;
}

.container {
margin: 0 auto;
max-width: 640px;
width: 100%;
}
3 changes: 2 additions & 1 deletion packages/autocomplete-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"@algolia/autocomplete-core": "1.5.7",
"@algolia/autocomplete-preset-algolia": "1.5.7",
"@algolia/autocomplete-shared": "1.5.7",
"preact": "^10.0.0"
"preact": "^10.0.0",
"htm": "^3.0.0"
},
"devDependencies": {
"@algolia/client-search": "4.9.1"
Expand Down
76 changes: 75 additions & 1 deletion packages/autocomplete-js/src/__tests__/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { createAutocomplete } from '@algolia/autocomplete-core';
import { waitFor } from '@testing-library/dom';
import { fireEvent, waitFor } from '@testing-library/dom';
import {
createElement as preactCreateElement,
render as preactRender,
} from 'preact';

import { createCollection } from '../../../../test/utils';
import { autocomplete } from '../autocomplete';
Expand Down Expand Up @@ -371,6 +375,76 @@ describe('api', () => {
document.querySelector<HTMLButtonElement>('.aa-SubmitButton')
).toHaveAttribute('title', 'Envoyer');
});

test('updates the renderer', async () => {
const container = document.createElement('div');
const panelContainer = document.createElement('div');
document.body.appendChild(container);
document.body.appendChild(panelContainer);

const CustomFragment = (props: any) => props.children;
const mockCreateElement1 = jest.fn(preactCreateElement);
const mockCreateElement2 = jest.fn(preactCreateElement);
const mockRender = jest.fn().mockImplementation(preactRender);

const { update } = autocomplete<{ label: string }>({
container,
panelContainer,
getSources() {
return [
{
sourceId: 'testSource',
getItems({ query }) {
return [{ label: query }];
},
templates: {
item({ item, html }) {
return html`<div>${item.label}</div>`;
},
},
},
];
},
renderer: {
Fragment: CustomFragment,
render: mockRender,
createElement: mockCreateElement1,
},
});

const input = container.querySelector<HTMLInputElement>('.aa-Input');

fireEvent.input(input, { target: { value: 'apple' } });

await waitFor(() => {
expect(
panelContainer.querySelector<HTMLElement>('.aa-Panel')
).toHaveTextContent('apple');
expect(mockCreateElement1).toHaveBeenCalled();
});

mockCreateElement1.mockClear();

update({
renderer: {
Fragment: CustomFragment,
render: mockRender,
createElement: mockCreateElement2,
},
});

fireEvent.input(input, { target: { value: 'iphone' } });

await waitFor(() => {
expect(
panelContainer.querySelector<HTMLElement>('.aa-Panel')
).toHaveTextContent('iphone');
// The `createElement` function was updated, so the previous
// implementation should no longer be called.
expect(mockCreateElement1).not.toHaveBeenCalled();
expect(mockCreateElement2).toHaveBeenCalled();
});
});
});

describe('destroy', () => {
Expand Down
Loading