index.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. 'use strict';
  2. var hasOwn = Object.prototype.hasOwnProperty;
  3. function noop() {
  4. return '';
  5. }
  6. function getStack(context) {
  7. return context.$$layoutStack || (
  8. context.$$layoutStack = []
  9. );
  10. }
  11. function applyStack(context) {
  12. var stack = getStack(context);
  13. while (stack.length) {
  14. stack.shift()(context);
  15. }
  16. }
  17. function getActions(context) {
  18. return context.$$layoutActions || (
  19. context.$$layoutActions = {}
  20. );
  21. }
  22. function getActionsByName(context, name) {
  23. var actions = getActions(context);
  24. return actions[name] || (
  25. actions[name] = []
  26. );
  27. }
  28. function applyAction(val, action) {
  29. var context = this;
  30. function fn() {
  31. return action.fn(context, action.options);
  32. }
  33. switch (action.mode) {
  34. case 'append': {
  35. return val + fn();
  36. }
  37. case 'prepend': {
  38. return fn() + val;
  39. }
  40. case 'replace': {
  41. return fn();
  42. }
  43. default: {
  44. return val;
  45. }
  46. }
  47. }
  48. function mixin(target) {
  49. var arg, key,
  50. len = arguments.length,
  51. i = 1;
  52. for (; i < len; i++) {
  53. arg = arguments[i];
  54. if (!arg) {
  55. continue;
  56. }
  57. for (key in arg) {
  58. // istanbul ignore else
  59. if (hasOwn.call(arg, key)) {
  60. target[key] = arg[key];
  61. }
  62. }
  63. }
  64. return target;
  65. }
  66. /**
  67. * Generates an object of layout helpers.
  68. *
  69. * @type {Function}
  70. * @param {Object} handlebars Handlebars instance.
  71. * @return {Object} Object of helpers.
  72. */
  73. function layouts(handlebars) {
  74. var helpers = {
  75. /**
  76. * @method extend
  77. * @param {String} name
  78. * @param {?Object} customContext
  79. * @param {Object} options
  80. * @param {Function(Object)} options.fn
  81. * @param {Object} options.hash
  82. * @return {String} Rendered partial.
  83. */
  84. extend: function (name, customContext, options) {
  85. // Make `customContext` optional
  86. if (arguments.length < 3) {
  87. options = customContext;
  88. customContext = null;
  89. }
  90. options = options || {};
  91. var fn = options.fn || noop,
  92. context = mixin({}, this, customContext, options.hash),
  93. data = handlebars.createFrame(options.data),
  94. template = handlebars.partials[name];
  95. // Partial template required
  96. if (template == null) {
  97. throw new Error('Missing partial: \'' + name + '\'');
  98. }
  99. // Compile partial, if needed
  100. if (typeof template !== 'function') {
  101. template = handlebars.compile(template);
  102. }
  103. // Add overrides to stack
  104. getStack(context).push(fn);
  105. // Render partial
  106. return template(context, { data: data });
  107. },
  108. /**
  109. * @method embed
  110. * @param {String} name
  111. * @param {?Object} customContext
  112. * @param {Object} options
  113. * @param {Function(Object)} options.fn
  114. * @param {Object} options.hash
  115. * @return {String} Rendered partial.
  116. */
  117. embed: function () {
  118. var context = mixin({}, this || {});
  119. // Reset context
  120. context.$$layoutStack = null;
  121. context.$$layoutActions = null;
  122. // Extend
  123. return helpers.extend.apply(context, arguments);
  124. },
  125. /**
  126. * @method block
  127. * @param {String} name
  128. * @param {Object} options
  129. * @param {Function(Object)} options.fn
  130. * @return {String} Modified block content.
  131. */
  132. block: function (name, options) {
  133. options = options || {};
  134. var fn = options.fn || noop,
  135. data = handlebars.createFrame(options.data),
  136. context = this || {};
  137. applyStack(context);
  138. return getActionsByName(context, name).reduce(
  139. applyAction.bind(context),
  140. fn(context, { data: data })
  141. );
  142. },
  143. /**
  144. * @method content
  145. * @param {String} name
  146. * @param {Object} options
  147. * @param {Function(Object)} options.fn
  148. * @param {Object} options.hash
  149. * @param {String} options.hash.mode
  150. * @return {String} Always empty.
  151. */
  152. content: function (name, options) {
  153. options = options || {};
  154. var fn = options.fn,
  155. data = handlebars.createFrame(options.data),
  156. hash = options.hash || {},
  157. mode = hash.mode || 'replace',
  158. context = this || {};
  159. applyStack(context);
  160. // Getter
  161. if (!fn) {
  162. return name in getActions(context);
  163. }
  164. // Setter
  165. getActionsByName(context, name).push({
  166. options: { data: data },
  167. mode: mode.toLowerCase(),
  168. fn: fn
  169. });
  170. }
  171. };
  172. return helpers;
  173. }
  174. /**
  175. * Registers layout helpers on a Handlebars instance.
  176. *
  177. * @method register
  178. * @param {Object} handlebars Handlebars instance.
  179. * @return {Object} Object of helpers.
  180. * @static
  181. */
  182. layouts.register = function (handlebars) {
  183. var helpers = layouts(handlebars);
  184. handlebars.registerHelper(helpers);
  185. return helpers;
  186. };
  187. module.exports = layouts;