Skip to content

Commit 456d781

Browse files
Jerskavvo
authored and
vvo
committed
feat(templatesConfig): helpers and options transferred to Template
New parameter passed to the `init` and `render` method: `templatesConfig`. It replaces `templateHelpers`. This parameter embeds both the `helpers` and the `Hogan` options. It uses the same chain that the `templateHelpers` previously used to be passed from a widget to the `Template` component. The `Template` component now also accepts a `templateDefault` parameter which allows us to ignore `Hogan`'s `options` if there is no `template` provided Closes #99.
1 parent 9298ca1 commit 456d781

18 files changed

+157
-85
lines changed

README.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,13 @@ search.addWidget(
143143
);
144144
```
145145

146-
### Template helpers
146+
### Template configuration
147147

148148
In order to help you when defining your templates, `instantsearch.js` exposes
149149
a few helpers. All helpers are accessible in the Mustache templating through
150150
`{{#helpers.nameOfTheHelper}}{{valueToFormat}}{{/helpers.nameOfTheHelper}}`. To
151151
use them in the function templates, you'll have to call
152-
`search.templateHelpers.nameOfTheHelper` where `search` is your current
152+
`search.templatesConfig.helpers.nameOfTheHelper` where `search` is your current
153153
`instantsearch` instance.
154154

155155
Here is the list of the currently available helpers:
@@ -159,6 +159,16 @@ Here is the list of the currently available helpers:
159159
option (defaults to `en-EN`).
160160
eg. `100000` will be formatted as `100 000` with `en-EN`
161161

162+
`instantsearch.js` also provides the ability to set options to use with your
163+
templating engine. These options are accessible through
164+
`search.templatesConfig.options`.
165+
With the embedded widgets using our `Template` component (widgets with
166+
`template(s)` parameter), we're passing these options to `Hogan.compile`
167+
if you're not using the default `template`.
168+
```js
169+
search.templatesConfig.options.delimiters = '[[ ]]';
170+
```
171+
162172
## Development workflow
163173

164174
```sh

components/Hits.js

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var Template = require('./Template');
66
class Hits extends React.Component {
77
renderWithResults() {
88
var template = this.props.hitTemplate;
9+
var templatesConfig = this.props.templatesConfig;
910
var transformData = this.props.hitTransformData;
1011

1112
var renderedHits = map(this.props.hits, function(hit) {
@@ -15,6 +16,7 @@ class Hits extends React.Component {
1516
transformData={transformData}
1617
key={hit.objectID}
1718
template={template}
19+
config={templatesConfig}
1820
/>
1921
);
2022
}, this);
@@ -25,6 +27,7 @@ class Hits extends React.Component {
2527
renderNoResults() {
2628
var data = this.props.results;
2729
var template = this.props.noResultsTemplate;
30+
var templatesConfig = this.props.templatesConfig;
2831
var transformData = this.props.noResultsTransformData;
2932

3033
return (
@@ -33,6 +36,7 @@ class Hits extends React.Component {
3336
data={data}
3437
transformData={transformData}
3538
template={template}
39+
config={templatesConfig}
3640
/>
3741
</div>
3842
);
@@ -57,6 +61,7 @@ Hits.propTypes = {
5761
React.PropTypes.string,
5862
React.PropTypes.func
5963
]).isRequired,
64+
templatesConfig: React.PropTypes.object.isRequired,
6065
noResultsTransformData: React.PropTypes.func
6166
};
6267

components/RefinementList.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class RefinementList extends React.Component {
5555
data={facetValue}
5656
transformData={this.props.transformData}
5757
template={this.props.templates.item}
58+
defaultTemplate={this.props.defaultTemplates.item}
59+
config={this.props.templatesConfig}
5860
/>
5961
</div>
6062
);
@@ -80,8 +82,15 @@ RefinementList.propTypes = {
8082
item: React.PropTypes.oneOfType([
8183
React.PropTypes.string,
8284
React.PropTypes.func
83-
]).isRequired
85+
])
86+
}),
87+
defaultTemplates: React.PropTypes.shape({
88+
item: React.PropTypes.oneOfType([
89+
React.PropTypes.string,
90+
React.PropTypes.func
91+
])
8492
}),
93+
templatesConfig: React.PropTypes.object.isRequired,
8594
transformData: React.PropTypes.func,
8695
toggleRefinement: React.PropTypes.func.isRequired
8796
};

components/Stats.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ var Template = require('./Template');
55
class Stats extends React.Component {
66
render() {
77
var template = this.props.template;
8-
var templateHelpers = this.props.templateHelpers;
8+
var defaultTemplate = this.props.defaultTemplate;
9+
var templatesConfig = this.props.templatesConfig;
910
var transformData = this.props.transformData;
1011
var data = {
1112
hasManyResults: this.props.nbHits > 1,
@@ -24,7 +25,8 @@ class Stats extends React.Component {
2425
data={data}
2526
transformData={transformData}
2627
template={template}
27-
templateHelpers={templateHelpers}
28+
defaultTemplate={defaultTemplate}
29+
config={templatesConfig}
2830
/>
2931
);
3032
}
@@ -39,9 +41,13 @@ Stats.propTypes = {
3941
template: React.PropTypes.oneOfType([
4042
React.PropTypes.func,
4143
React.PropTypes.string
44+
]),
45+
defaultTemplate: React.PropTypes.oneOfType([
46+
React.PropTypes.func,
47+
React.PropTypes.string
4248
]).isRequired,
4349
transformData: React.PropTypes.func,
44-
templateHelpers: React.PropTypes.object,
50+
templatesConfig: React.PropTypes.object.isRequired,
4551
query: React.PropTypes.string
4652
};
4753

components/Template.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ class Template {
66
render() {
77
var content = renderTemplate({
88
template: this.props.template,
9-
templateHelpers: this.props.templateHelpers,
9+
defaultTemplate: this.props.defaultTemplate,
10+
config: this.props.config,
1011
data: this.props.transformData ? this.props.transformData(this.props.data) : this.props.data
1112
});
1213

14+
if (content === null) {
15+
// Adds a noscript to the DOM but virtual DOM is null
16+
// See http://facebook.github.io/react/docs/component-specs.html#render
17+
return null;
18+
}
19+
1320
return <div dangerouslySetInnerHTML={{__html: content}} />;
1421
}
1522
}
@@ -18,8 +25,12 @@ Template.propTypes = {
1825
template: React.PropTypes.oneOfType([
1926
React.PropTypes.string,
2027
React.PropTypes.func
21-
]).isRequired,
22-
templateHelpers: React.PropTypes.object,
28+
]),
29+
defaultTemplate: React.PropTypes.oneOfType([
30+
React.PropTypes.string,
31+
React.PropTypes.func
32+
]),
33+
config: React.PropTypes.object.isRequired,
2334
transformData: React.PropTypes.func,
2435
data: React.PropTypes.object
2536
};

decorators/headerFooter.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ function headerFooter(ComposedComponent) {
99
render() {
1010
return (
1111
<div className={cx(this.props.cssClasses.root)}>
12-
<Template template={this.props.templates.header} />
12+
<Template template={this.props.templates.header} defaultTemplate={this.props.defaultTemplates.header} config={this.props.templatesConfig} />
1313
<ComposedComponent {...this.props} />
14-
<Template template={this.props.templates.footer} />
14+
<Template template={this.props.templates.footer} defaultTemplate={this.props.defaultTemplates.footer} config={this.props.templatesConfig} />
1515
</div>
1616
);
1717
}
@@ -28,6 +28,17 @@ function headerFooter(ComposedComponent) {
2828
React.PropTypes.func
2929
])
3030
}),
31+
defaultTemplates: React.PropTypes.shape({
32+
header: React.PropTypes.oneOfType([
33+
React.PropTypes.string,
34+
React.PropTypes.func
35+
]),
36+
footer: React.PropTypes.oneOfType([
37+
React.PropTypes.string,
38+
React.PropTypes.func
39+
])
40+
}),
41+
templatesConfig: React.PropTypes.object.isRequired,
3142
cssClasses: React.PropTypes.shape({
3243
root: React.PropTypes.oneOfType([
3344
React.PropTypes.string,
@@ -37,7 +48,7 @@ function headerFooter(ComposedComponent) {
3748
};
3849

3950
HeaderFooter.defaultProps = {
40-
templates: {
51+
defaultTemplates: {
4152
header: '',
4253
footer: ''
4354
},

example/app.js

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var search = instantsearch({
66
apiKey: '6be0576ff61c053d5f9a3225e2a90f76',
77
indexName: 'instant_search'
88
});
9+
search.templatesConfig.options.sectionTags = [{o: '_refined', c: 'refined'}];
910

1011
search.addWidget(
1112
instantsearch.widgets.urlSync({
@@ -102,6 +103,10 @@ search.addWidget(
102103
templates: {
103104
header: '<div class="panel-heading">Shipping</div>',
104105
body: require('./templates/free-shipping.html')
106+
},
107+
transformData: function(data) {
108+
data._refined = data.isRefined;
109+
return data;
105110
}
106111
})
107112
);

example/templates/free-shipping.html

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<label>
33
<input type="checkbox" {{#isRefined}}checked{{/isRefined}} />
44
{{label}}
5+
{{_refined}}!{{/refined}}
56
</label>
67
<span class="badge pull-right">{{count}}</span>
78
</div>

lib/InstantSearch.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ Usage: instantsearch({
3030
this.indexName = indexName;
3131
this.searchParameters = searchParameters || {};
3232
this.widgets = [];
33-
this.templateHelpers = {
34-
formatNumber(number) {
35-
return Number(number).toLocaleString(numberLocale);
36-
}
33+
this.templatesConfig = {
34+
helpers: {
35+
formatNumber(number) {
36+
return Number(number).toLocaleString(numberLocale);
37+
}
38+
},
39+
options: {}
3740
};
3841
}
3942

@@ -83,7 +86,7 @@ Usage: instantsearch({
8386
return;
8487
}
8588
widget.render({
86-
templateHelpers: this.templateHelpers,
89+
templatesConfig: this.templatesConfig,
8790
results,
8891
state,
8992
helper
@@ -94,9 +97,9 @@ Usage: instantsearch({
9497
_init(state, helper) {
9598
forEach(this.widgets, function(widget) {
9699
if (widget.init) {
97-
widget.init(state, helper);
100+
widget.init(state, helper, this.templatesConfig);
98101
}
99-
});
102+
}, this);
100103
}
101104
}
102105

lib/utils.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,37 @@ function isDomElement(o) {
2222
return o instanceof HTMLElement || o && o.nodeType > 0;
2323
}
2424

25-
function renderTemplate({template, templateHelpers, data}) {
25+
function renderTemplate({template, defaultTemplate, config, data}) {
2626
var hogan = require('hogan.js');
2727
var forEach = require('lodash/collection/forEach');
28+
var options = config.options;
2829
var content;
2930

31+
if (template === null || typeof template === 'undefined') {
32+
if (defaultTemplate === null || typeof defaultTemplate === 'undefined') {
33+
throw new Error('No template provided and no defaultTemplate');
34+
}
35+
// If template isn't set, we reset the options since our defaultTemplates
36+
// don't expect options to be set
37+
options = {};
38+
template = defaultTemplate;
39+
}
40+
41+
if (template === '') {
42+
return null;
43+
}
44+
3045
// We add all our template helper methods to the template as lambdas. Note
3146
// that lambdas in Mustache are supposed to accept a second argument of
3247
// `render` to get the rendered value, not the literal `{{value}}`. But
3348
// this is currently broken (see
3449
// https://github.com/twitter/hogan.js/issues/222).
3550
function addTemplateHelpersToData(templateData) {
3651
templateData.helpers = {};
37-
forEach(templateHelpers, (method, name) => {
52+
forEach(config.helpers, (method, name) => {
3853
data.helpers[name] = () => {
3954
return (value) => {
40-
return method(hogan.compile(value).render(templateData));
55+
return method(hogan.compile(value, options).render(templateData));
4156
};
4257
};
4358
});
@@ -55,7 +70,7 @@ function renderTemplate({template, templateHelpers, data}) {
5570
if (typeof template === 'string') {
5671
data = addTemplateHelpersToData(data);
5772

58-
content = hogan.compile(template).render(data);
73+
content = hogan.compile(template, options).render(data);
5974
}
6075

6176
return content;

scripts/lint.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ set -e # exit when error
44

55
printf "\nLint\n"
66

7-
eslint . --quiet
7+
eslint . --quiet --no-color

test/InstantSearch/lifecycle.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ test('InstantSearch: lifecycle', function(t) {
8383
);
8484
t.deepEqual(
8585
widget.init.args[0],
86-
[helper.state, helper],
87-
'widget.init(helper.state, helper)'
86+
[helper.state, helper, search.templatesConfig],
87+
'widget.init(helper.state, helper, templatesConfig)'
8888
);
8989
t.ok(widget.render.notCalled, 'widget.render not yet called');
9090

@@ -98,7 +98,7 @@ test('InstantSearch: lifecycle', function(t) {
9898
results: results,
9999
state: helper.state,
100100
helper: helper,
101-
templateHelpers: search.templateHelpers
101+
templatesConfig: search.templatesConfig
102102
}],
103103
'widget.render(results, state, helper)'
104104
);

widgets/hits.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function hits({
1515

1616
return {
1717
getConfiguration: () => ({hitsPerPage}),
18-
render: function({results, helper}) {
18+
render: function({results, helper, templatesConfig}) {
1919
React.render(
2020
<Hits
2121
hits={results.hits}
@@ -26,6 +26,7 @@ function hits({
2626
hideWhenNoResults={hideWhenNoResults}
2727
hasResults={results.hits.length > 0}
2828
hitTemplate={templates.hit}
29+
templatesConfig={templatesConfig}
2930
hitTransformData={transformData.hit}
3031
/>,
3132
containerNode

0 commit comments

Comments
 (0)