diff --git a/components/Stats.js b/components/Stats.js index 1b58fd605b..ed348f05a3 100644 --- a/components/Stats.js +++ b/components/Stats.js @@ -5,6 +5,7 @@ var Template = require('./Template'); class Stats extends React.Component { render() { var template = this.props.template; + var templateHelpers = this.props.templateHelpers; var data = { nbHits: this.props.nbHits, hasNoResults: this.props.nbHits === 0, @@ -17,6 +18,7 @@ class Stats extends React.Component { ); } @@ -28,7 +30,8 @@ Stats.propTypes = { template: React.PropTypes.oneOfType([ React.PropTypes.func, React.PropTypes.string - ]).isRequired + ]).isRequired, + templateHelpers: React.PropTypes.object }; module.exports = Stats; diff --git a/components/Template.js b/components/Template.js index 647b8b780e..145b5b35b2 100644 --- a/components/Template.js +++ b/components/Template.js @@ -4,7 +4,11 @@ var renderTemplate = require('../lib/utils').renderTemplate; class Template { render() { - var content = renderTemplate(this.props.template, this.props.data); + var content = renderTemplate({ + template: this.props.template, + templateHelpers: this.props.templateHelpers, + data: this.props.data + }); return
; } @@ -15,6 +19,7 @@ Template.propTypes = { React.PropTypes.string, React.PropTypes.func ]).isRequired, + templateHelpers: React.PropTypes.object, data: React.PropTypes.object }; diff --git a/example/app.js b/example/app.js index ae44797a08..14b6f997fc 100644 --- a/example/app.js +++ b/example/app.js @@ -3,7 +3,8 @@ var instantsearch = require('../'); var search = instantsearch({ appId: 'latency', apiKey: '6be0576ff61c053d5f9a3225e2a90f76', - indexName: 'instant_search' + indexName: 'instant_search', + numberLocale: 'fr-FR' }); search.addWidget( diff --git a/lib/InstantSearch.js b/lib/InstantSearch.js index cf222e1780..6164acf1ef 100644 --- a/lib/InstantSearch.js +++ b/lib/InstantSearch.js @@ -10,6 +10,7 @@ class InstantSearch { appId = null, apiKey = null, indexName = null, + numberLocale = 'en-EN', searchParameters = {} }) { if (appId === null || apiKey === null || indexName === null) { @@ -22,7 +23,6 @@ Usage: instantsearch({ throw new Error(usage); } - var client = algoliasearch(appId, apiKey); this.client = client; @@ -30,6 +30,11 @@ Usage: instantsearch({ this.indexName = indexName; this.searchParameters = searchParameters || {}; this.widgets = []; + this.templateHelpers = { + formatNumber(number) { + return Number(number).toLocaleString(numberLocale); + } + }; } addWidget(widgetDefinition) { @@ -67,10 +72,16 @@ Usage: instantsearch({ _render(helper, results, state) { forEach(this.widgets, function(widget) { - if (widget.render) { - widget.render(results, state, helper); + if (!widget.render) { + return; } - }); + widget.render({ + templateHelpers: this.templateHelpers, + results, + state, + helper + }); + }, this); } _init(state, helper) { diff --git a/lib/utils.js b/lib/utils.js index 86c19ca0d6..2a34d7fc4e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -14,18 +14,35 @@ function isDomElement(o) { return o instanceof HTMLElement || o && o.nodeType > 0; } -function renderTemplate(template, data) { +function renderTemplate({template, templateHelpers, data}) { var hogan = require('hogan.js'); + var forEach = require('lodash/collection/forEach'); var content; - if (typeof template === 'string') { - content = hogan.compile(template).render(data); - } else if (typeof template === 'function') { - content = template(data); - } else { + if (typeof template !== 'string' && typeof template !== 'function') { throw new Error('Template must be `string` or `function`'); } + if (typeof template === 'function') { + content = template(data); + } + + if (typeof template === 'string') { + // We add all our template helper methods to the template as lambdas. Note + // that lambdas in Mustache are supposed to accept a second argument of + // `render` to get the rendered value, not the literal `{{value}}`. But + // this is currently broken (see + // https://github.com/twitter/hogan.js/issues/222). + forEach(templateHelpers, function(method, name) { + data['_' + name] = function() { + return function(value) { + return method(hogan.compile(value).render(data)); + }; + }; + }); + content = hogan.compile(this.props.template).render(data); + } + return content; } diff --git a/widgets/hits.js b/widgets/hits.js index 1a15ee2d97..f3da9bcbe3 100644 --- a/widgets/hits.js +++ b/widgets/hits.js @@ -9,7 +9,7 @@ function hits({container = null, templates = {}, hitsPerPage = 20}) { return { getConfiguration: () => ({hitsPerPage}), - render: function(results, state, helper) { + render: function({results, helper}) { React.render(