UNPKG

9.29 kBJavaScriptView Raw
1'use strict';
2
3const Translator = require('./lib/translator');
4const HandlebarsRenderer = require('@bigcommerce/stencil-paper-handlebars');
5
6/**
7* processor is an optional function to apply during template assembly. The
8* templates parameter is a object where the keys are paths and the values are the
9* raw templates. The function returns an object of the same format, possibly changing
10* the values. We use this to precompile templates within the Paper module.
11*
12* @callback processor
13* @param {Object} templates - Object that contains the gathered templates
14*/
15
16/**
17* Assembler.getTemplates assembles all the templates required to render the given
18* top-level template.
19*
20* @callback assemblerGetTemplates
21* @param {string} path - The path to the templates, relative to the templates directory
22* @param {processor} processor - An optional processor to apply to each template during assembly
23* @return {Promise} A promise to return the (optionally processed) templates
24*/
25
26/**
27* Assembler.getTranslations assembles all the translations for the theme.
28*
29* @callback assemblerGetTranslations
30* @return {Promise} A promise to return the translations
31*/
32
33class Paper {
34 /**
35 * Paper constructor. In addition to store settings and theme settings (configuration),
36 * paper expects to be passed an assembler to gather all the templates required to render
37 * the top level template.
38 *
39 * @param {Object} siteSettings - Site settings
40 * @param {Object} themeSettings - Theme settings (configuration)
41 * @param {Object} assembler - Assembler with getTemplates and getTranslations methods.
42 * @param {assemblerGetTemplates} assembler.getTemplates - Method to assemble templates
43 * @param {assemblerGetTranslations} assembler.getTranslations - Method to assemble translations
44 * @param {String} rendererType - One of ['handlebars-v3', 'handlebars-v4']
45 * @param {Object} logger - a console-like logger object
46 * @param {String} logLevel - log level used by handlebars logger (debug, info, warning, error)
47 */
48 constructor(siteSettings, themeSettings, assembler, rendererType, logger = console, logLevel = 'info') {
49 this._assembler = assembler || {};
50
51 // Build renderer based on type
52 switch(rendererType) {
53 case 'handlebars-v4':
54 this.renderer = new HandlebarsRenderer(siteSettings, themeSettings, 'v4', logger, logLevel);
55 break;
56 case 'handlebars-v3':
57 default:
58 this.renderer = new HandlebarsRenderer(siteSettings, themeSettings, 'v3', logger, logLevel);
59 break;
60 }
61
62 this.preProcessor = this.renderer.getPreProcessor();
63
64 this.logger = logger;
65 }
66
67 /**
68 * Get the siteSettings object containing global site settings.
69 *
70 * @return {object} settings An object containing global site settings.
71 */
72 getSiteSettings() {
73 return this.renderer.getSiteSettings();
74 };
75
76 /**
77 * Set the siteSettings object containing global site settings.
78 *
79 * @param {object} settings An object containing global site settings.
80 */
81 setSiteSettings(settings) {
82 this.renderer.setSiteSettings(settings);
83 };
84
85 /**
86 * Get the themeSettings object containing the theme configuration.
87 *
88 * @return {object} settings An object containing the theme configuration.
89 */
90 getThemeSettings() {
91 return this.renderer.getThemeSettings();
92 };
93
94 /**
95 * Set the themeSettings object containing the theme configuration.
96 *
97 * @param {object} settings An object containing the theme configuration.
98 */
99 setThemeSettings(settings) {
100 this.renderer.setThemeSettings(settings);
101 };
102
103 /**
104 * Reset decorator list.
105 */
106 resetDecorators() {
107 this.renderer.resetDecorators();
108 };
109
110 /**
111 * Add a decorator to wrap output during render().
112 *
113 * @param {Function} decorator
114 */
115 addDecorator(decorator) {
116 this.renderer.addDecorator(decorator);
117 };
118
119 /**
120 * Get page content.
121 *
122 * @return {Object} Regions with widgets
123 */
124 getContent() {
125 return this.renderer.getContent();
126 };
127
128 /**
129 * Add content to be rendered in the given regions.
130 *
131 * @param {Object} Regions with widgets
132 */
133 setContent(regions) {
134 this.renderer.setContent(regions);
135 };
136
137 /**
138 * Use the assembler to fetch partials/templates, and translations, then load them
139 * into the renderer.
140 *
141 * @param {String|Array} paths A string or array of strings - the template path(s) to load.
142 * @param {String} acceptLanguage The accept-language header - used to select a locale.
143 * @return {Promise} Promise to load the templates and translations into the renderer.
144 */
145 loadTheme(paths, acceptLanguage) {
146 if (!Array.isArray(paths)) {
147 paths = paths ? [paths] : [];
148 }
149
150 const promises = [];
151 promises.push(this.loadTranslations(acceptLanguage));
152 paths.forEach(path => {
153 promises.push(this.loadTemplates(path));
154 });
155
156 return Promise.all(promises);
157 }
158
159 /**
160 * Use the assembler to fetch partials/templates, then load them
161 * into the renderer.
162 *
163 * @param {String} path The root template path to load. All dependencies will be loaded as well.
164 * @return {Promise} Promise to load the templates into the renderer.
165 */
166 loadTemplates(path) {
167 return this._assembler.getTemplates(path, this.preProcessor).then(templates => {
168 return this.renderer.addTemplates(templates);
169 });
170 }
171
172 /**
173 * Is the given template loaded?
174 *
175 * @param {String} path The path to the template file
176 * @return {Boolean} is the given template loaded?
177 */
178 isTemplateLoaded(path) {
179 return this.renderer.isTemplateLoaded(path);
180 }
181
182 /**
183 * Load translation files and give a translator to renderer.
184 *
185 * @param {String} acceptLanguage The accept-language header, used to select a locale
186 * @return {Promise} Promise to load the translations into the renderer.
187 */
188 loadTranslations(acceptLanguage) {
189 return this._assembler.getTranslations().then(translations => {
190 const translator = Translator.create(acceptLanguage, translations, this.logger);
191 this.renderer.setTranslator(translator);
192 return translations;
193 });
194 };
195
196 /**
197 * Render a string with the given context.
198 *
199 * @param {String} string
200 * @param {Object} context
201 * @return {Promise} A promise to return the rendered text
202 * @throws [CompileError|RenderError]
203 */
204 renderString(string, context) {
205 return this.renderer.renderString(string, context);
206 }
207
208 /**
209 * Renders a template with the given context
210 *
211 * @param {String} path The path to the template
212 * @param {Object} context The context to provide to the template
213 * @return {Promise} A promise to return the rendered template
214 * @throws [TemplateNotFoundError|RenderError|DecoratorError]
215 */
216 render(path, context) {
217 return this.renderer.render(path, context);
218 }
219
220 /**
221 * Theme rendering logic. This is used by Stencil CLI.
222 *
223 * @param {String|Array} templatePath A single template or list of templates to render.
224 * @param {Object} data
225 * @return {Promise} A promise that returns a {String|Object} depending on whether multiple templates were rendered
226 * @throws [TemplateNotFoundError|RenderError|DecoratorError]
227 */
228 renderTheme(templatePath, data) {
229 // Simple case of a single non-ajax template
230 if (!data.remote && !Array.isArray(templatePath)) {
231 return this.render(templatePath, data.context);
232 }
233
234 // If no template path provided, just return the remote data
235 if (!templatePath) {
236 return Promise.resolve({ data: data.remote_data });
237 }
238
239 // If ajax request, merge remote_data into context
240 if (data.remote) {
241 data.context = Object.assign({}, data.context, data.remote_data);
242 }
243
244 const renderPromises = [];
245 let result;
246
247 if (Array.isArray(templatePath)) {
248 // If templatePath is an array (multiple templates using render_with option),
249 // compile all the template required files into an object
250 result = {};
251 for (let i = 0; i < templatePath.length; i++) {
252 const path = templatePath[i];
253 renderPromises.push(this.render(path, data.context).then(html => {
254 result[path] = html;
255 }));
256 }
257 } else {
258 renderPromises.push(this.render(templatePath, data.context).then(html => {
259 result = html;
260 }));
261 }
262
263 return Promise.all(renderPromises).then(() => {
264 // Remote requests get both the remote data & rendered html
265 if (data.remote) {
266 result = {
267 data: data.remote_data,
268 content: result
269 };
270 }
271
272 return result;
273 });
274 }
275}
276
277module.exports = Paper;