Ghost/core/server/helpers/get.js
Hannah Wolfe 3e40637cd4 The {{#get}} helper
closes #4439

- adds basic get helper which works with the current API
- allows theme developers to make requests against the API
- supports block params and @error message
- includes 100% test coverage using posts

----

The `{{#get}}` helper is an asynchronous block helper which allows for making
requests for data from the API. This allows theme developers to customise the
data which can be shown on a particular page of a blog.

Requests can be made to the posts, tags or users API endpoints:

```
{{#get "posts" limit="3"}}
  {{#foreach posts}}
     <a href="{{url}}">{{title}}</a>
  {{/foreach}}
{{/get}}
```

The `{{#get}}` helper must be used as a block helper, it supports `{{else}}`
logic, for when no data matching the request is available or if an error has
occurred:

```
{{#get "posts" tag="photo"}}
  ...
{{else}}
  {{#if @error}}
    <p>Something went wrong: {{@error}}</p>
  {{else}}
    <p>No posts found</p>
  {{/if}}
{{/get}}
```

The helper also supports block params, meaning the data it outputs can be
given a different name:

```
{{#get "posts" featured="true" as |featured|}}
  {{#foreach featured}}
    ...
  {{/foreach}}
{{/get}}
```

Please Note: At present asynchronous helpers cannot be nested.
2015-10-11 16:51:12 +01:00

102 lines
2.6 KiB
JavaScript

// # Get Helper
// Usage: `{{#get "posts" limit="5"}}`, `{{#get "tags" limit="all"}}`
// Fetches data from the API
var _ = require('lodash'),
hbs = require('express-hbs'),
Promise = require('bluebird'),
errors = require('../errors'),
api = require('../api'),
resources,
get;
// Endpoints that the helper is able to access
resources = ['posts', 'tags', 'users'];
/**
* ## Is Browse
* Is this a Browse request or a Read request?
* @param {Object} context
* @param {Object} options
* @returns {boolean}
*/
function isBrowse(context, options) {
var browse = true;
if (options.id || options.slug) {
browse = false;
}
return browse;
}
/**
* ## Parse Options
* Ensure options passed in make sense
*
* @param {Object} data
* @param {Object} options
* @returns {*}
*/
function parseOptions(data, options) {
if (_.isArray(options.tag)) {
options.tag = _.pluck(options.tag, 'slug').join(',');
}
if (_.isObject(options.author)) {
options.author = options.author.slug;
}
return options;
}
/**
* ## Get
* @param {Object} context
* @param {Object} options
* @returns {Promise}
*/
get = function get(context, options) {
options = options || {};
options.hash = options.hash || {};
options.data = options.data || {};
var self = this,
data = hbs.handlebars.createFrame(options.data),
apiOptions = _.omit(options.hash, 'context'),
apiMethod;
if (!options.fn) {
data.error = 'Get helper must be called as a block';
errors.logWarn(data.error);
return Promise.resolve();
}
if (!_.contains(resources, context)) {
data.error = 'Invalid resource given to get helper';
errors.logWarn(data.error);
return Promise.resolve(options.inverse(self, {data: data}));
}
// Determine if this is a read or browse
apiMethod = isBrowse(context, apiOptions) ? api[context].browse : api[context].read;
// Parse the options we're going to pass to the API
apiOptions = parseOptions(this, apiOptions);
return apiMethod(apiOptions).then(function success(result) {
result = _.merge(self, result);
if (_.isEmpty(result[context])) {
return options.inverse(self, {data: data});
}
return options.fn(result, {
data: data,
blockParams: [result[context]]
});
}).catch(function error(err) {
data.error = err.message;
return options.inverse(self, {data: data});
});
};
module.exports = get;