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

feat(ais-dynamic-widgets): add implementation #922

Merged
merged 14 commits into from
Jul 9, 2021
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"dependencies": {
"algoliasearch-helper": "^3.1.0",
"instantsearch.js": "^4.20.0"
"instantsearch.js": "^4.25.0"
},
"peerDependencies": {
"algoliasearch": ">= 3.32.0 < 5",
Expand Down Expand Up @@ -114,11 +114,11 @@
"bundlesize": [
{
"path": "./dist/vue-instantsearch.js",
"maxSize": "53.00 kB"
"maxSize": "54 kB"
},
{
"path": "./dist/vue-instantsearch.common.js",
"maxSize": "16.50 kB"
"maxSize": "16.75 kB"
}
],
"resolutions": {
Expand Down
2 changes: 2 additions & 0 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ it('should have `name` the same as the suit class name everywhere', () => {
expect(installedName).toBe(name);
if (name === 'AisInstantSearchSsr') {
expect(suitClass).toBe(`ais-InstantSearch`);
} else if (name === 'AisExperimentalDynamicWidgets') {
expect(suitClass).toBe(`ais-DynamicWidgets`);
} else {
expect(suitClass).toBe(`ais-${name.substr(3)}`);
}
Expand Down
87 changes: 87 additions & 0 deletions src/components/DynamicWidgets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { createWidgetMixin } from '../mixins/widget';
import { EXPERIMENTAL_connectDynamicWidgets } from 'instantsearch.js/es/connectors';
import { createSuitMixin } from '../mixins/suit';

function getWidgetAttribute(vnode) {
const props = vnode.componentOptions && vnode.componentOptions.propsData;
if (props) {
if (props.attribute) {
return props.attribute;
}
if (Array.isArray(props.attributes)) {
return props.attributes[0];
}
}

const children =
vnode.componentOptions && vnode.componentOptions.children
? vnode.componentOptions.children
: vnode.children;

if (Array.isArray(children)) {
// return first child with a truthy attribute
return children.reduce(
(acc, curr) => acc || getWidgetAttribute(curr),
undefined
);
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
}

return undefined;
}

export default {
name: 'AisExperimentalDynamicWidgets',
mixins: [
createWidgetMixin({ connector: EXPERIMENTAL_connectDynamicWidgets }),
createSuitMixin({ name: 'DynamicWidgets' }),
],
props: {
transformItems: {
type: Function,
default: undefined,
},
},
render(createElement) {
const components = new Map();
(this.$slots.default || []).forEach(vnode => {
const attribute = getWidgetAttribute(vnode);
if (attribute) {
components.set(
attribute,
createElement(
'div',
{ key: attribute, class: [this.suit('widget')] },
[vnode]
)
);
}
});

// by default, render everything, but hidden so that the routing doesn't disappear
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this behavior result in an unexpected UI flash were some widget will appear and then get removed because computed based on state.attributesToRender?

What if we don't render anything when we don't have a state?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that the widgets we render initially are hidden, so this won't cause a flash, but possibly slightly delayed visuals compared to server side rendering (which solves this, as the widget information is available on that server + initial frontend render)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I think I rather meant: won't this results in an unnecessary search parameters computation because it mounts widgets that will likely get removed?

Why don't we render nothing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we render nothing, the parameters of the URL can't be applied unfortunately, this was the best workaround I found

if (!this.state) {
const allComponents = [];
components.forEach(component => allComponents.push(component));

return createElement(
'div',
{ attrs: { hidden: true }, class: [this.suit()] },
allComponents
);
}

return createElement(
'div',
{ class: [this.suit()] },
this.state.attributesToRender.map(attribute => components.get(attribute))
);
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
},
computed: {
widgetParams() {
return {
transformItems: this.transformItems,
// we do not pass "widgets" to the connector, since Vue is in charge of rendering
widgets: [],
};
},
},
};
Loading