Skip to content

Commit

Permalink
feat(slots) add header, footer, and suggestion vue slots
Browse files Browse the repository at this point in the history
  • Loading branch information
darrenjennings authored Aug 13, 2018
2 parents 46c0932 + 235286d commit 9e2c5df
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 83 deletions.
65 changes: 56 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,15 @@ Place the component into your app!
```html
<vue-autosuggest
:suggestions="[{data:['Frodo', 'Samwise', 'Gandalf', 'Galadriel', 'Faramir', 'Éowyn']}]"
@click="clickHandler"
:on-selected="selectHandler"
:input-props="{id:'autosuggest__input', onInputChange: this.onInputChange, placeholder:'Do you feel lucky, punk?'}"
/>
@click="clickHandler"
>
<template slot-scope="{suggestion}">
<span class="my-suggestion-item">{{suggestion.item}}</span>
</template>
</vue-autosuggest

```

Advanced usage:
Expand Down Expand Up @@ -148,7 +153,7 @@ export default {
onInputChange(text, oldText) {
if (text === null) {
/* Maybe the text is null but you wanna do
* something else, but don't filter by null.
* something else, but don't filter by null.
*/
return;
}
Expand All @@ -161,16 +166,19 @@ export default {
// Store data in one property, and filtered in another
this.filteredOptions = [{ data: filteredData }];
},
clickHandler(item){
clickHandler(item) {
// items are selected by default on click, but you can add some more behavior here!
},
onSelected(item) {
this.selected = item;
},
/**
* renderSuggestion will override the default suggestion template slot.
*/
renderSuggestion(suggestion) {
/* You will need babel-plugin-transform-vue-jsx for this kind of full customizable
* rendering. If you don't use babel or the jsx transform, then you can use this
* function to just `return suggestion['propertyName'];`
/* You will need babel-plugin-transform-vue-jsx for this kind of syntax for
* rendering. If you don't use babel or the jsx transform, then you can create
* the you can create the virtual node yourself using this.$createElement.
*/
const character = suggestion.item;
return (
Expand Down Expand Up @@ -212,14 +220,52 @@ export default {
For more advanced usage, check out the examples below, and explore the
<a href="#props">properties</a> you can use.

## [Slots](#slots)

### header/footer
Slots for injecting content above all the results inside the results container.

```html
<vue-autosuggest ...>
<template slot="header">
<h1>header content goes here</h1>
</template>
<template slot="footer">
<h1>footer content goes here</h1>
</template>
</vue-autosuggest>
```

### suggestion item (i.e. default slot)
Used to style each suggestion inside the `<li>` tag. Using [scoped slots](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots)
you have access to the `suggestion` item inside the `v-for` suggestions loop. This gives you the power of Vue templating, since
vue-autosuggest does not have an opinion about how you render the items in your list.

```vue
<vue-autosuggest>
<template slot-scope="{suggestion}">
<!-- suggestion.name corresponds to which section the item is in -->
<div v-if="suggestion.name === 'blog'">
<!-- suggestion.item corresponds to the suggestion object -->
<a target="_blank" :href="suggestion.item.url">{{suggestion.item.value}}</a>
</div>
<div v-else>{{suggestion.item}}</div>
</template>
</vue-autosuggest>
```

> This slot will be overridden when the [`render-suggestion`](#renderSuggestion) prop is used.


## [Props](#props)

| Prop | Type | Required | Description |
| :------------------------------------------ | :------- | :------: | :-------------------------------------------------------- |
| [`suggestions`](#suggestionsProp) | Array || Suggestions to be rendered. |
| [`input-props`](#inputPropsTable) | Object || Add props to the `<input>`. |
| [`section-configs`](#sectionConfigsProp) | Object | | Define multiple sections `<input>`. |
| [`render-suggestion`](#renderSuggestion) | Function | | Tell vue-autosuggest how to render inside the `<li>` tag. |
| [`render-suggestion`](#renderSuggestion) | Function | | Tell vue-autosuggest how to render inside the `<li>` tag. Overrides what is inside the default suggestion template slot. |
| [`get-suggestion-value`](#getSuggestionValue) | Function | | Tells vue-autosuggest what to put in the `<input/>` value |

<a name="inputPropsTable"></a>
Expand Down Expand Up @@ -275,7 +321,8 @@ sectionConfigs: {

### renderSuggestion

This function will tell vue-autosuggest how to render the html inside the `<li>` tag.
This function can be used to tell vue-autosuggest how to render the html inside the `<li>` tag when you do not want to use the
default template slot for suggestions but would rather have the power of javascript / jsx.

In its most basic form it just returns an object property:

Expand Down
123 changes: 103 additions & 20 deletions __tests__/__snapshots__/autosuggest.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ exports[`Autosuggest can render default suggestion value by property name 1`] =
role="combobox"
aria-autocomplete="list"
aria-owns="autosuggest__results"
aria-activedescendant="autosuggest__results_item-0"
aria-haspopup="true"
aria-expanded="true"
aria-activedescendant
aria-haspopup="false"
aria-expanded="false"
id="autosuggest__input"
initialvalue
oninputchange="function onInputChange() {}"
Expand All @@ -157,25 +157,9 @@ exports[`Autosuggest can render default suggestion value by property name 1`] =
mockFn();
}"
value="Frodo"
class="form-control autosuggest__input-open cool-class"
class="form-control cool-class"
>
<div class="autosuggest__results-container">
<div aria-labelledby="autosuggest"
class="autosuggest__results"
>
<ul role="listbox"
aria-labelledby="autosuggest"
>
<li role="option"
data-suggestion-index="0"
data-section-name="default"
id="autosuggest__results_item-0"
class="autosuggest__results_item"
>
undefined
</li>
</ul>
</div>
</div>
</div>
Expand Down Expand Up @@ -376,6 +360,105 @@ exports[`Autosuggest can render simplest component with single onSelected 1`] =
`;
exports[`Autosuggest can render slots 1`] = `
<div id="autosuggest"
data-server-rendered="true"
>
<input type="text"
autocomplete="off"
role="combobox"
aria-autocomplete="list"
aria-owns="autosuggest__results"
aria-activedescendant
aria-haspopup="true"
aria-expanded="true"
id="autosuggest__input"
initialvalue
oninputchange="function onInputChange() {}"
placeholder="Type 'G'"
name="q"
onblur="function blurred() {}"
onfocus="function focused() {
mockFn();
}"
value="G"
class="form-control autosuggest__input-open"
>
<div class="autosuggest__results-container">
<div aria-labelledby="autosuggest"
class="autosuggest__results"
>
<div class="header-dude">
</div>
<ul role="listbox"
aria-labelledby="autosuggest"
>
<li role="option"
data-suggestion-index="0"
data-section-name="default"
id="autosuggest__results_item-0"
class="autosuggest__results_item"
>
<h1>
clifford kits
</h1>
</li>
<li role="option"
data-suggestion-index="1"
data-section-name="default"
id="autosuggest__results_item-1"
class="autosuggest__results_item"
>
<h1>
friendly chemistry
</h1>
</li>
<li role="option"
data-suggestion-index="2"
data-section-name="default"
id="autosuggest__results_item-2"
class="autosuggest__results_item"
>
<h1>
phonics
</h1>
</li>
<li role="option"
data-suggestion-index="3"
data-section-name="default"
id="autosuggest__results_item-3"
class="autosuggest__results_item"
>
<h1>
life of fred
</h1>
</li>
<li role="option"
data-suggestion-index="4"
data-section-name="default"
id="autosuggest__results_item-4"
class="autosuggest__results_item"
>
<h1>
life of fred math
</h1>
</li>
</ul>
<div id="footer-dude">
<span>
1
</span>
<span>
2
</span>
</div>
</div>
</div>
</div>
`;
exports[`Autosuggest can render suggestions 1`] = `
<div id="autosuggest"
Expand Down
43 changes: 40 additions & 3 deletions __tests__/autosuggest.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mount, shallow } from "@vue/test-utils";
import { mount, shallowMount } from "@vue/test-utils";
import { createRenderer } from "vue-server-renderer";

import Autosuggest from "../src/Autosuggest.vue";
Expand Down Expand Up @@ -68,7 +68,7 @@ describe("Autosuggest", () => {

props.suggestions = [filteredOptions[0]];

const wrapper = shallow(Autosuggest, {
const wrapper = shallowMount(Autosuggest, {
propsData: props
});

Expand Down Expand Up @@ -320,7 +320,6 @@ describe("Autosuggest", () => {
input.trigger("keydown.down");
});

input.trigger("keydown.enter");
wrapper.find("li").trigger("mouseover");
wrapper.find("li").trigger("mouseenter");
wrapper.find("li").trigger("mouseleave");
Expand Down Expand Up @@ -409,6 +408,10 @@ describe("Autosuggest", () => {
input.trigger("keydown.down");
input.trigger("keydown.enter");

await wrapper.vm.$nextTick(() => {});

expect(input.element.value).toBe("Frodo");

const renderer = createRenderer();
renderer.renderToString(wrapper.vm, (err, str) => {
if (err) {
Expand Down Expand Up @@ -469,4 +472,38 @@ describe("Autosuggest", () => {
// Should throw validation error
expect(mockConsole).toHaveBeenCalled();
});

it("can render slots", async () => {
const wrapper = mount(Autosuggest, {
propsData: defaultProps,
slots: {
header: '<div class="header-dude"></div>',
footer: '<div id="footer-dude"><span>1</span><span>2</span></div>'
},
scopedSlots: {
default: `
<h1 slot-scope="{suggestion}">{{ suggestion.item }}</h1>
`
},
attachToDocument: true
});

const input = wrapper.find("input");
input.trigger("click");
wrapper.setData({ searchInput: "G" });

expect(wrapper.findAll('.header-dude').length).toEqual(1);
expect(wrapper.findAll('#footer-dude span').length).toEqual(2);
expect(wrapper.findAll('h1').length).toEqual(5);

await wrapper.vm.$nextTick(() => {});

const renderer = createRenderer();
renderer.renderToString(wrapper.vm, (err, str) => {
if (err) {
return false;
}
expect(str).toMatchSnapshot();
});
});
});
18 changes: 14 additions & 4 deletions docs/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@
:section-configs="sectionConfigs"
:getSuggestionValue="getSuggestionValue"
@focus="focusMe"
ref="autocomplete" />
ref="autocomplete">
<!-- <template slot="header">
header
</template> -->
<template slot-scope="{suggestion}">
<div v-if="suggestion.name === 'blog'">
<a target="_blank" :href="suggestion.item.url">{{suggestion.item.value}}</a></div>
<div v-else>{{suggestion.item}}</div>
</template>
<!-- <template slot="footer">
footer
</template> -->
</vue-autosuggest>
</div>
</div>
</template>
Expand Down Expand Up @@ -79,15 +91,13 @@ export default {
},
blog: {
limit: 3,
type: "url-section",
onSelected: function() {
//alert("url: " + item.item.url);
// alert("url: " + item.item.url);
}
}
},
inputProps: {
id: "autosuggest__input",
onClick: () => {},
onInputChange: this.onInputChange,
placeholder: "Type 'g'"
}
Expand Down
Loading

0 comments on commit 9e2c5df

Please sign in to comment.