├── .gitattributes ├── test.js ├── .travis.yml ├── .gitignore ├── templates └── scaffold │ ├── assemblefile.js │ └── README.md ├── .editorconfig ├── generator.js ├── docs ├── examples.md ├── 01-app-cache-data.md ├── 02-render-locals.md ├── 06-customizing.md ├── 03-view-locals.md ├── 04-view-data.md └── 05-helper-locals.md ├── LICENSE ├── package.json ├── assemblefile.js ├── examples ├── 01-app-cache-data │ └── assemblefile.js ├── 02-render-locals │ └── assemblefile.js ├── 03-view-locals │ └── assemblefile.js ├── 04-view-data │ └── assemblefile.js ├── 05-helper-locals │ └── assemblefile.js └── 06-customizing │ └── assemblefile.js ├── .verb.md ├── .eslintrc.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text eol=lf 3 | 4 | # binaries 5 | *.ai binary 6 | *.psd binary 7 | *.jpg binary 8 | *.gif binary 9 | *.png binary 10 | *.jpeg binary 11 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | var assert = require('assert'); 5 | 6 | describe('context-workshop', function() { 7 | it('should run', function() { 8 | assert(true); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "6" 5 | - "5" 6 | - "4" 7 | - "0.12" 8 | - "0.10" 9 | matrix: 10 | fast_finish: true 11 | allow_failures: 12 | - node_js: "4" 13 | - node_js: "0.10" 14 | - node_js: "0.12" 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # always ignore files 2 | *.DS_Store 3 | *.sublime-* 4 | 5 | # test related, or directories generated by tests 6 | test/actual 7 | actual 8 | coverage 9 | 10 | # npm 11 | node_modules 12 | npm-debug.log 13 | 14 | # misc 15 | _gh_pages 16 | benchmark 17 | bower_components 18 | vendor 19 | temp 20 | tmp 21 | TODO.md 22 | -------------------------------------------------------------------------------- /templates/scaffold/assemblefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assemble = require('assemble'); 4 | var app = assemble(); 5 | 6 | /** 7 | * [Add example docs here] 8 | * 9 | * To run this example: 10 | * 11 | * ```sh 12 | * $ assemble <%= name %> 13 | * ``` 14 | * 15 | * @name <%= name %> 16 | * @api public 17 | */ 18 | 19 | app.task('<%= name %>', function(cb) { 20 | console.log('Running', this.name); 21 | cb(); 22 | }); 23 | 24 | app.task('default', ['<%= name %>']); 25 | 26 | module.exports = app; 27 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | insert_final_newline = false 15 | 16 | [**/{actual,fixtures,expected}/**] 17 | trim_trailing_whitespace = false 18 | insert_final_newline = false 19 | 20 | [**/templates/**] 21 | trim_trailing_whitespace = false 22 | insert_final_newline = false 23 | -------------------------------------------------------------------------------- /generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | 4 | module.exports = function(app) { 5 | app.task('example', function(cb) { 6 | app.engine('*', require('engine-base')); 7 | app.question('name', 'What is the name of the example?'); 8 | app.ask('name', {save: false}, function(err, answers) { 9 | if (err) return cb(err); 10 | if (answers && answers.name) { 11 | var dest = app.option('dest') || path.join('examples', answers.name); 12 | app.src(['templates/scaffold/**/*']) 13 | .pipe(app.renderFile('*', {name: answers.name})) 14 | .pipe(app.conflicts(dest)) 15 | .pipe(app.dest(dest)) 16 | .once('error', cb) 17 | .once('end', cb); 18 | return; 19 | } 20 | cb(new Error('Expected a name for the example')); 21 | }); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /templates/scaffold/README.md: -------------------------------------------------------------------------------- 1 | {%= apidocs('./examples/<%= name %>/assemblefile.js') %} 2 | 3 | To run this example: 4 | 5 | ```sh 6 | $ assemble <%= name %> 7 | ``` 8 | 9 | Code snippet from example [assemblefile.js](./examples/<%= name %>/assemblefile.js) 10 | 11 | ```js 12 | // add app-cache-data 13 | app.data({title: 'Site Title'}); 14 | 15 | // create a simple "button" partial 16 | app.partial('button', {content: 'button: {{ title }}'}); 17 | 18 | // create a simple "home" page containing 3 "button" partials 19 | app.page('home', { 20 | content: [ 21 | 'title: {{ title }}', 22 | 'one: {{ partial("button") }}', 23 | 'two: {{ partial("button") }}', 24 | 'three: {{ partial("button") }}' 25 | ].join('\n') 26 | }); 27 | 28 | // render the "home" page with no additional data 29 | var home = app.pages.getView('home'); 30 | home.render(function(err, res) { 31 | if (err) return console.error(err); 32 | console.log(res.content); 33 | }); 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | ### Installing 2 | 3 | Clone this project and install the npm modules to run the examples: 4 | 5 | ```sh 6 | # clone the project 7 | $ git clone https://github.com/assemble/context-workshop 8 | # cd into the folder 9 | $ cd context-workshop 10 | # install npm modules 11 | $ npm install 12 | # install assemble globally if not already installed 13 | $ npm install --global assemble 14 | ``` 15 | 16 | ### Running 17 | 18 | Each example may be run by using `assemble`: 19 | 20 | ```sh 21 | $ assemble 22 | ``` 23 | 24 | To view a list of examples run the default assemble command: 25 | 26 | ```sh 27 | $ assemble 28 | ``` 29 | 30 | To interactively choose an example to run use the `-i` option: 31 | 32 | ```sh 33 | $ assemble -i 34 | ``` 35 | 36 | {%= doc('01-app-cache-data.md') %} 37 | {%= doc('02-render-locals.md') %} 38 | {%= doc('03-view-locals.md') %} 39 | {%= doc('04-view-data.md') %} 40 | {%= doc('05-helper-locals.md') %} 41 | {%= doc('06-customizing.md') %} 42 | -------------------------------------------------------------------------------- /docs/01-app-cache-data.md: -------------------------------------------------------------------------------- 1 | {%= apidocs('./examples/01-app-cache-data/assemblefile.js') %} 2 | 3 | To run this example: 4 | 5 | ```sh 6 | $ assemble app-cache-data 7 | ``` 8 | 9 | ![image](https://cloud.githubusercontent.com/assets/995160/16308022/6e9e6ef0-3931-11e6-82d2-a595b0f798fc.png) 10 | 11 | 12 | Code snippet from example [assemblefile.js](./examples/01-app-cache-data/assemblefile.js) 13 | 14 | ```js 15 | // add app-cache-data 16 | app.data({title: 'Site Title'}); 17 | 18 | // create a simple "button" partial 19 | app.partial('button', {content: 'button: <%= title %>'}); 20 | 21 | // create a simple "home" page containing 3 "button" partials 22 | app.page('home', { 23 | content: [ 24 | 'title: <%= title %>', 25 | 'one: <%= partial("button") %>', 26 | 'two: <%= partial("button") %>', 27 | 'three: <%= partial("button") %>' 28 | ].join('\n') 29 | }); 30 | 31 | // render the "home" page with no additional data 32 | var home = app.pages.getView('home'); 33 | home.render(function(err, res) { 34 | if (err) return console.error(err); 35 | console.log(res.content); 36 | }); 37 | ``` 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016, Jon Schlinkert. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/02-render-locals.md: -------------------------------------------------------------------------------- 1 | {%= apidocs('./examples/02-render-locals/assemblefile.js') %} 2 | 3 | To run this example: 4 | 5 | ```sh 6 | $ assemble render-locals 7 | ``` 8 | 9 | ![image](https://cloud.githubusercontent.com/assets/995160/16308107/b3287bce-3931-11e6-8c56-2676d4515c24.png) 10 | 11 | Code snippet from example [assemblefile.js](./examples/02-render-locals/assemblefile.js) 12 | 13 | ```js 14 | // add app-cache-data 15 | app.data({title: 'Site Title'}); 16 | 17 | // create a simple "button" partial 18 | app.partial('button', {content: 'button: <%= title %>'}); 19 | 20 | // create a simple "home" page containing 3 "button" partials 21 | app.page('home', { 22 | content: [ 23 | 'title: <%= title %>', 24 | 'one: <%= partial("button") %>', 25 | 'two: <%= partial("button") %>', 26 | 'three: <%= partial("button") %>' 27 | ].join('\n') 28 | }); 29 | 30 | // render the "home" page with no additional data 31 | var home = app.pages.getView('home'); 32 | home.render(function(err, res) { 33 | if (err) return console.log(err); 34 | console.log(res.content); 35 | 36 | home.render({title: 'Render Locals Title'}, function(err, res) { 37 | if (err) return console.log(err); 38 | console.log(res.content); 39 | }); 40 | }); 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/06-customizing.md: -------------------------------------------------------------------------------- 1 | {%= apidocs('./examples/06-customizing/assemblefile.js') %} 2 | 3 | To run this example: 4 | 5 | ```sh 6 | $ assemble customizing 7 | ``` 8 | 9 | ![image](https://cloud.githubusercontent.com/assets/995160/16308576/88c480b0-3933-11e6-8873-2e40ddaefaf5.png) 10 | 11 | Code snippet from example [assemblefile.js](./examples/06-customizing/assemblefile.js) 12 | 13 | ```js 14 | // Add a context option 15 | app.option('context', function(view, locals) { 16 | // override all the other data with the "render locals" 17 | return extend({}, this.cache.data, view.context(), locals); 18 | }); 19 | 20 | // add app-cache-data 21 | app.data({title: 'Site Title'}); 22 | 23 | // create a simple "button" partial 24 | app.partial('button', {content: 'button: <%= title %>'}); 25 | 26 | // create a simple "home" page containing 3 "button" partials 27 | app.page('home', { 28 | content: [ 29 | 'title: <%= title %>', 30 | 'one: <%= partial("button") %>', 31 | 'two: <%= partial("button") %>', 32 | 'three: <%= partial("button") %>' 33 | ].join('\n') 34 | }); 35 | 36 | // render the "home" page with no additional data 37 | var home = app.pages.getView('home'); 38 | home.render(function(err, res) { 39 | if (err) return console.error(err); 40 | console.log(res.content); 41 | }); 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/03-view-locals.md: -------------------------------------------------------------------------------- 1 | {%= apidocs('./examples/03-view-locals/assemblefile.js') %} 2 | 3 | To run this example: 4 | 5 | ```sh 6 | $ assemble view-locals 7 | ``` 8 | 9 | ![image](https://cloud.githubusercontent.com/assets/995160/16308141/cf0ef08e-3931-11e6-9bbf-008ad3efcbc8.png) 10 | 11 | Code snippet from example [assemblefile.js](./examples/03-view-locals/assemblefile.js) 12 | 13 | ```js 14 | // add app-cache-data 15 | app.data({title: 'Site Title'}); 16 | 17 | // Add a "button" partial with view locals data. 18 | // This data is only overridden by "render locals" if the button is rendered directly with `.render` and "render locals" are passed into `.render`. 19 | 20 | app.partial('button', { 21 | content: 'button: <%= title %>', 22 | locals: {title: 'Button Locals Title'} 23 | }); 24 | 25 | // Add a "home" page with view locals data that includes the 3 "button" partials. 26 | app.page('home', { 27 | content: [ 28 | 'title: <%= title %>', 29 | 'one: <%= partial("button") %>', 30 | 'two: <%= partial("button") %>', 31 | 'three: <%= partial("button") %>' 32 | ].join('\n'), 33 | locals: {title: 'Page Locals Title'} 34 | }); 35 | 36 | var home = app.pages.getView('home'); 37 | home.render(function(err, res) { 38 | if (err) return console.error(err); 39 | console.log(res.content); 40 | 41 | home.render({title: 'Render Locals Title'}, function(err, res) { 42 | if (err) return console.error(err); 43 | console.log(res.content); 44 | }); 45 | }); 46 | ``` 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "context-workshop", 3 | "description": "One of assemble's biggest strengths is granular control over `context`. This workshop explains how context is created, as well as where, when and why the context works the way it does at each point in the render cycle.", 4 | "version": "0.1.1", 5 | "homepage": "https://github.com/assemble/context-workshop", 6 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)", 7 | "repository": "assemble/context-workshop", 8 | "bugs": { 9 | "url": "https://github.com/assemble/context-workshop/issues" 10 | }, 11 | "license": "MIT", 12 | "files": [ 13 | "examples/", 14 | "assemblefile.js" 15 | ], 16 | "main": "assemblefile.js", 17 | "engines": { 18 | "node": ">=0.10.0" 19 | }, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "dependencies": {}, 24 | "devDependencies": { 25 | "assemble": "^0.14.0", 26 | "base-questions": "^0.6.6", 27 | "engine-base": "^0.1.2", 28 | "extend-shallow": "^2.0.1", 29 | "gulp-format-md": "*", 30 | "log-utils": "^0.1.4", 31 | "mocha": "^2.5.3" 32 | }, 33 | "keywords": [], 34 | "verb": { 35 | "toc": true, 36 | "layout": "default", 37 | "tasks": [ 38 | "readme" 39 | ], 40 | "plugins": [ 41 | "gulp-format-md" 42 | ], 43 | "reflinks": [ 44 | "base-data", 45 | "verb", 46 | "verb-readme-generator", 47 | "assemble" 48 | ], 49 | "lint": { 50 | "reflinks": true 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/04-view-data.md: -------------------------------------------------------------------------------- 1 | {%= apidocs('./examples/04-view-data/assemblefile.js') %} 2 | 3 | To run this example: 4 | 5 | ```sh 6 | $ assemble view-data 7 | ``` 8 | 9 | ![image](https://cloud.githubusercontent.com/assets/995160/16308172/eac00a7a-3931-11e6-8006-152eb63185a3.png) 10 | 11 | Code snippet from example [assemblefile.js](./examples/04-view-data/assemblefile.js) 12 | 13 | ```js 14 | // add app-cache-data 15 | app.data({title: 'Site Title'}); 16 | 17 | // Add a "button" partial with view locals data and view data. 18 | // The view data will override `app.cache.data`, "render locals", and "view locals". 19 | app.partial('button', { 20 | content: 'button: <%= title %>', 21 | locals: {title: 'Button Locals Title'}, 22 | data: {title: 'Button Data Title'} 23 | }); 24 | 25 | // Add a "home" page with view locals data and view data that includes the 3 "button" partials. 26 | app.page('home', { 27 | content: [ 28 | 'title: <%= title %>', 29 | 'one: <%= partial("button") %>', 30 | 'two: <%= partial("button") %>', 31 | 'three: <%= partial("button") %>' 32 | ].join('\n'), 33 | locals: {title: 'Page Locals Title'}, 34 | data: {title: 'Page Data Title'} 35 | }); 36 | 37 | var home = app.pages.getView('home'); 38 | home.render(function(err, res) { 39 | if (err) return console.error(err); 40 | console.log(res.content); 41 | 42 | home.render({title: 'Render Locals Title'}, function(err, res) { 43 | if (err) return console.error(err); 44 | console.log(res.content); 45 | }); 46 | }); 47 | ``` 48 | -------------------------------------------------------------------------------- /assemblefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var examples = fs.readdirSync('examples'); 5 | 6 | var assemble = require('assemble'); 7 | var questions = require('base-questions'); 8 | var app = assemble(); 9 | app.use(questions()); 10 | 11 | /** 12 | * Register each example as a task to make it available to run from the root. 13 | */ 14 | 15 | examples.forEach(function(example) { 16 | app.task(example, {silent: true}, function(cb) { 17 | var exampleApp = require('./examples/' + example + '/assemblefile.js'); 18 | exampleApp.build('default', cb); 19 | }); 20 | app.task(example.slice(3), {silent: true}, [example]); 21 | }); 22 | 23 | /** 24 | * List each example that available to run 25 | */ 26 | 27 | app.task('default', {silent: true}, function(cb) { 28 | if (app.option('i')) { 29 | app.choices('choose-example', 'Choose an example to run:', examples.map(function(example) { 30 | return example.slice(3); 31 | })); 32 | 33 | app.ask('choose-example', {save: false}, function(err, answers) { 34 | if (err) return cb(err); 35 | var tasks = arrayify(answers && answers['choose-example']); 36 | if(tasks.length === 0) { 37 | return cb(); 38 | } 39 | app.build(tasks, cb); 40 | }); 41 | return; 42 | } 43 | 44 | console.log(` 45 | Specify an example to run: 46 | 47 | $ assemble 48 | 49 | Available examples: 50 | 51 | ${examples.map(function(example) { 52 | return `$ assemble ${example.slice(3)}`; 53 | }).join('\n ')} 54 | 55 | Interactively choose an example to run with \`-i\`: 56 | 57 | $ assemble -i 58 | 59 | `); 60 | cb(); 61 | }); 62 | 63 | function arrayify(val) { 64 | return val ? (Array.isArray(val) ? val : [val]) : []; 65 | } 66 | 67 | module.exports = app; 68 | -------------------------------------------------------------------------------- /examples/01-app-cache-data/assemblefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assemble = require('assemble'); 4 | var utils = require('log-utils'); 5 | var app = assemble(); 6 | 7 | /** 8 | * Assemble will use `app.cache.data` when rendering views (pages). 9 | * To add data to `app.cache.data` use the `app.data()` api. See [base-data][] for all the available options for `app.data()`. 10 | * 11 | * @name app-cache-data 12 | * @api public 13 | */ 14 | 15 | app.task('app-cache-data', function(cb) { 16 | console.log(); 17 | console.log(); 18 | app.engine('txt', require('engine-base')); 19 | app.option('engine', 'txt'); 20 | 21 | app.create('partials'); 22 | app.create('pages'); 23 | 24 | /** 25 | * Add "global" data to `app.cache.data` through the `app.data` method: 26 | */ 27 | 28 | app.data({title: utils.cyan('Site Title')}); 29 | 30 | /** 31 | * Add a simple "button" partial with no other data. 32 | */ 33 | 34 | app.partial('button', {content: 'button: <%= title %>'}); 35 | 36 | /** 37 | * Add a simple "home" page with no other data that includes the 3 "button" partials. 38 | */ 39 | 40 | app.page('home', { 41 | content: [ 42 | 'title: <%= title %>', 43 | 'one: <%= partial("button") %>', 44 | 'two: <%= partial("button") %>', 45 | 'three: <%= partial("button") %>' 46 | ].join('\n') 47 | }); 48 | 49 | var home = app.pages.getView('home'); 50 | console.log('Rendering "home" page with default `app.cache.data`:\n'); 51 | 52 | /** 53 | * Render the "home" page using all the defaults. 54 | * This will show that the `title` property from `app.cache.data` is used throughout the "home" page and all the "button" partials. 55 | */ 56 | 57 | home.render(function(err, res) { 58 | if (err) { 59 | console.log(err, '\n\n'); 60 | return cb(); 61 | } 62 | console.log(res.content, '\n\n'); 63 | cb(); 64 | }); 65 | }); 66 | 67 | app.task('default', ['app-cache-data']); 68 | 69 | module.exports = app; 70 | -------------------------------------------------------------------------------- /docs/05-helper-locals.md: -------------------------------------------------------------------------------- 1 | {%= apidocs('./examples/05-helper-locals/assemblefile.js') %} 2 | 3 | To run this example: 4 | 5 | ```sh 6 | $ assemble helper-locals 7 | ``` 8 | 9 | ![image](https://cloud.githubusercontent.com/assets/995160/16308201/0bf8ee1e-3932-11e6-81e8-9eae38234e95.png) 10 | 11 | Code snippet from example [assemblefile.js](./examples/05-helper-locals/assemblefile.js) 12 | 13 | ```js 14 | // add app-cache-data 15 | app.data({title: utils.cyan('Site Title')}); 16 | 17 | // Add a "button" partial with view locals data and view data. 18 | // The view data will override `app.cache.data`, "render locals", and "view locals". 19 | // When "helper locals" is passed to the "partial" helper, all data on the view will be overridden. 20 | app.partial('button', { 21 | content: 'button: <%= title %>', 22 | locals: {title: 'Button Locals Title'}, 23 | data: {title: 'Button Data Title'} 24 | }); 25 | 26 | // Add a "home" page with view locals data and view data that includes the 3 "button" partials. 27 | // Button "one" will be rendered without passing any helper locals. 28 | // Button "two" will be rendered with the "home" page's data passed as the helper locals. 29 | // Button "three" will be rendered with a "custom" property from the "render locals" passed as the helper locals. 30 | app.page('home', { 31 | content: [ 32 | 'title: <%= title %>', 33 | 'one: <%= partial("button") %>', 34 | 'two: <%= partial("button", obj) %>', // "obj" is the built-in global object from engine-base 35 | `three: <%= partial("button", {title: 'Helper Locals Title'}) %>` 36 | ].join('\n'), 37 | locals: {title: 'Page Locals Title'}, 38 | data: {title: 'Page Data Title'} 39 | }); 40 | 41 | var home = app.pages.getView('home'); 42 | home.render(function(err, res) { 43 | if (err) return console.error(err); 44 | console.log(res.content); 45 | 46 | home.render({title: 'Render Locals Title'}, function(err, res) { 47 | if (err) return console.error(err); 48 | console.log(res.content); 49 | }); 50 | }); 51 | ``` 52 | -------------------------------------------------------------------------------- /examples/02-render-locals/assemblefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assemble = require('assemble'); 4 | var utils = require('log-utils'); 5 | var app = assemble(); 6 | 7 | /** 8 | * Render locals is the data object that is passed into the `.render()` method when rendering views. 9 | * The following example will show how the render locals will override data from `app.cache.data` when the context is created. 10 | * 11 | * @name render-locals 12 | * @api public 13 | */ 14 | 15 | app.task('render-locals', function(cb) { 16 | console.log(); 17 | console.log(); 18 | app.engine('txt', require('engine-base')); 19 | app.option('engine', 'txt'); 20 | 21 | app.create('partials'); 22 | app.create('pages'); 23 | 24 | /** 25 | * Add "global" data to `app.cache.data` through the `app.data` method. 26 | * This will be overridden by the render locals when the page is rendered. 27 | */ 28 | 29 | app.data({title: utils.cyan('Site Title')}); 30 | 31 | /** 32 | * Add a simple "button" partial with no other data. 33 | */ 34 | 35 | app.partial('button', {content: 'button: <%= title %>'}); 36 | 37 | /** 38 | * Add a simple "home" page with no other data that includes the 3 "button" partials. 39 | */ 40 | 41 | app.page('home', { 42 | content: [ 43 | 'title: <%= title %>', 44 | 'one: <%= partial("button") %>', 45 | 'two: <%= partial("button") %>', 46 | 'three: <%= partial("button") %>' 47 | ].join('\n') 48 | }); 49 | 50 | var home = app.pages.getView('home'); 51 | console.log('Rendering "home" page with default `app.cache.data`:\n'); 52 | 53 | /** 54 | * Render the "home" page using all the defaults. 55 | * This will show that the `title` property from `app.cache.data` is used throughout the "home" page and all the "button" partials. 56 | */ 57 | 58 | home.render(function(err, res) { 59 | if (err) { 60 | console.log(err, '\n\n'); 61 | return cb(); 62 | } 63 | console.log(res.content, '\n\n'); 64 | 65 | console.log('Rendering "home" page with "render locals" that will override data from `app.cache.data`:\n'); 66 | 67 | /** 68 | * Render the "home" page using render locals. 69 | * This will show that the `title` property from `app.cache.data` is used throughout the "home" page and all the "button" partials. 70 | */ 71 | 72 | home.render({title: utils.yellow('Render Locals Title')}, function(err, res) { 73 | if (err) { 74 | console.log(err, '\n\n'); 75 | return cb(); 76 | } 77 | console.log(res.content, '\n\n'); 78 | cb(); 79 | }); 80 | }); 81 | }); 82 | 83 | app.task('default', ['render-locals']); 84 | 85 | module.exports = app; 86 | -------------------------------------------------------------------------------- /examples/03-view-locals/assemblefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assemble = require('assemble'); 4 | var utils = require('log-utils'); 5 | var app = assemble(); 6 | 7 | /** 8 | * View locals is the data object that is on view objects that will be used to override `app.cache.data`. 9 | * The following example will show how the view locals will override data from `app.cache.data`, but is overridden by "render locals" when the context is created. 10 | * 11 | * @name view-locals 12 | * @api public 13 | */ 14 | 15 | app.task('view-locals', function(cb) { 16 | console.log(); 17 | console.log(); 18 | app.engine('txt', require('engine-base')); 19 | app.option('engine', 'txt'); 20 | 21 | app.create('partials'); 22 | app.create('pages'); 23 | 24 | /** 25 | * Add "global" data to `app.cache.data` through the `app.data` method. 26 | * This will be overridden by the render and view locals when the page is rendered. 27 | */ 28 | 29 | app.data({title: utils.cyan('Site Title')}); 30 | 31 | /** 32 | * Add a "button" partial with view locals data. 33 | * This data is only overridden by "render locals" if the button is rendered directly with `.render` and "render locals" are passed into `.render`. 34 | */ 35 | 36 | app.partial('button', { 37 | content: 'button: <%= title %>', 38 | locals: {title: utils.blue('Button Locals Title')} 39 | }); 40 | 41 | /** 42 | * Add a "home" page with view locals data that includes the 3 "button" partials. 43 | */ 44 | 45 | app.page('home', { 46 | content: [ 47 | 'title: <%= title %>', 48 | 'one: <%= partial("button") %>', 49 | 'two: <%= partial("button") %>', 50 | 'three: <%= partial("button") %>' 51 | ].join('\n'), 52 | locals: {title: utils.blue('Page Locals Title')} 53 | }); 54 | 55 | var home = app.pages.getView('home'); 56 | console.log('Rendering "home" page with default `app.cache.data`.\nView locals will override `app.cache.data`:\n'); 57 | 58 | /** 59 | * Render the "home" page using all the defaults. 60 | * This will show that the `title` property from the view locals is used throughout the "home" page and all the "button" partials. 61 | */ 62 | 63 | home.render(function(err, res) { 64 | if (err) { 65 | console.log(err, '\n\n'); 66 | return cb(); 67 | } 68 | console.log(res.content, '\n\n'); 69 | 70 | console.log('Rendering "home" page with "render locals" that will override data from `app.cache.data`\nView locals will override "render locals":\n'); 71 | 72 | /** 73 | * Render the "home" page using render locals. 74 | * This will show that the `title` property from the render locals is in the "home" page, but the "view locals" is used in all of the "button" partials. 75 | */ 76 | 77 | home.render({title: utils.yellow('Render Locals Title')}, function(err, res) { 78 | if (err) { 79 | console.log(err, '\n\n'); 80 | return cb(); 81 | } 82 | console.log(res.content, '\n\n'); 83 | cb(); 84 | }); 85 | }); 86 | }); 87 | 88 | app.task('default', ['view-locals']); 89 | 90 | module.exports = app; 91 | -------------------------------------------------------------------------------- /examples/04-view-data/assemblefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assemble = require('assemble'); 4 | var utils = require('log-utils'); 5 | var app = assemble(); 6 | 7 | /** 8 | * View data is the data object that is on view objects that will be used to override `app.cache.data`. 9 | * The following example will show how the view data will override data from `app.cache.data` and "render locals" when the context is created. 10 | * 11 | * @name view-data 12 | * @api public 13 | */ 14 | 15 | app.task('view-data', function(cb) { 16 | console.log(); 17 | console.log(); 18 | app.engine('txt', require('engine-base')); 19 | app.option('engine', 'txt'); 20 | 21 | app.create('partials'); 22 | app.create('pages'); 23 | 24 | /** 25 | * Add "global" data to `app.cache.data` through the `app.data` method. 26 | * This will be overridden by the render locals and view data when the page is rendered. 27 | */ 28 | 29 | app.data({title: utils.cyan('Site Title')}); 30 | 31 | /** 32 | * Add a "button" partial with view locals data and view data. 33 | * The view data will override `app.cache.data`, "render locals", and "view locals". 34 | */ 35 | 36 | app.partial('button', { 37 | content: 'button: <%= title %>', 38 | locals: {title: utils.blue('Button Locals Title')}, 39 | data: {title: utils.green('Button Data Title')} 40 | }); 41 | 42 | /** 43 | * Add a "home" page with view locals data and view data that includes the 3 "button" partials. 44 | */ 45 | 46 | app.page('home', { 47 | content: [ 48 | 'title: <%= title %>', 49 | 'one: <%= partial("button") %>', 50 | 'two: <%= partial("button") %>', 51 | 'three: <%= partial("button") %>' 52 | ].join('\n'), 53 | locals: {title: utils.blue('Page Locals Title')}, 54 | data: {title: utils.green('Page Data Title')} 55 | }); 56 | 57 | var home = app.pages.getView('home'); 58 | console.log('Rendering "home" page with default `app.cache.data`.\nView data will override `app.cache.data`:\n'); 59 | 60 | /** 61 | * Render the "home" page using all the defaults. 62 | * This will show that the `title` property from the view data is used throughout the "home" page and all the "button" partials. 63 | */ 64 | 65 | home.render(function(err, res) { 66 | if (err) { 67 | console.log(err, '\n\n'); 68 | return cb(); 69 | } 70 | console.log(res.content, '\n\n'); 71 | 72 | console.log('Rendering "home" page with "render locals" that will override data from `app.cache.data`\nView data will override "render locals":\n'); 73 | 74 | /** 75 | * Render the "home" page using render locals. 76 | * This will show that the `title` property from the view data is in the "home" page and the "button" partials will use their own view data. 77 | */ 78 | 79 | home.render({title: utils.yellow('Render Locals Title')}, function(err, res) { 80 | if (err) { 81 | console.log(err, '\n\n'); 82 | return cb(); 83 | } 84 | console.log(res.content, '\n\n'); 85 | cb(); 86 | }); 87 | }); 88 | }); 89 | 90 | app.task('default', ['view-data']); 91 | 92 | module.exports = app; 93 | -------------------------------------------------------------------------------- /examples/05-helper-locals/assemblefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assemble = require('assemble'); 4 | var utils = require('log-utils'); 5 | var app = assemble(); 6 | 7 | /** 8 | * Helper locals is the data object that is passed into the built-in view helpers. This data will override all other data for that specific view. 9 | * The following example will show how the helper locals will override all other data when rendering a partial view. 10 | * 11 | * @name helper-locals 12 | * @api public 13 | */ 14 | 15 | app.task('helper-locals', function(cb) { 16 | console.log(); 17 | console.log(); 18 | app.engine('txt', require('engine-base')); 19 | app.option('engine', 'txt'); 20 | 21 | app.create('partials'); 22 | app.create('pages'); 23 | 24 | /** 25 | * Add "global" data to `app.cache.data` through the `app.data` method. 26 | * This will be overridden by the render locals and view data when the page is rendered. 27 | */ 28 | 29 | app.data({title: utils.cyan('Site Title')}); 30 | 31 | /** 32 | * Add a "button" partial with view locals data and view data. 33 | * The view data will override `app.cache.data`, "render locals", and "view locals". 34 | * When "helper locals" is passed to the "partial" helper, all data on the view will be overridden. 35 | */ 36 | 37 | app.partial('button', { 38 | content: 'button: <%= title %>', 39 | locals: {title: utils.blue('Button Locals Title')}, 40 | data: {title: utils.green('Button Data Title')} 41 | }); 42 | 43 | /** 44 | * Add a "home" page with view locals data and view data that includes the 3 "button" partials. 45 | * Button "one" will be rendered without passing any helper locals. 46 | * Button "two" will be rendered with the "home" page's data passed as the helper locals. 47 | * Button "three" will be rendered with a "custom" property from the "render locals" passed as the helper locals. 48 | */ 49 | 50 | app.page('home', { 51 | content: [ 52 | 'title: <%= title %>', 53 | 'one: <%= partial("button") %>', 54 | 'two: <%= partial("button", obj) %>', // "obj" is the built-in global object from engine-base 55 | `three: <%= partial("button", {title: '${utils.red('Helper Locals Title')}'}) %>` 56 | ].join('\n'), 57 | locals: {title: utils.blue('Page Locals Title')}, 58 | data: {title: utils.cyan('Page Data Title')} 59 | }); 60 | 61 | var home = app.pages.getView('home'); 62 | console.log('Rendering "home" page with default `app.cache.data`.\nView data will override `app.cache.data`.\nHelper locals will override all other data:\n'); 63 | 64 | /** 65 | * Render the "home" page using all the defaults. 66 | * This will show that the `title` property from the view data is used throughout the "home" page and the "button" partials without helper locals. 67 | */ 68 | 69 | home.render(function(err, res) { 70 | if (err) { 71 | console.log(err, '\n\n'); 72 | return cb(); 73 | } 74 | console.log(res.content, '\n\n'); 75 | 76 | console.log('Rendering "home" page with "render locals" that will override data from `app.cache.data`\nView data will override "render locals".\nHelper locals will override all other data:\n'); 77 | 78 | /** 79 | * Render the "home" page using render locals. 80 | * This will show that the `title` property from the view data is in the "home" page and the "button" partials will use their own view data. 81 | */ 82 | 83 | home.render({title: utils.yellow('Render Locals Title')}, function(err, res) { 84 | if (err) { 85 | console.log(err, '\n\n'); 86 | return cb(); 87 | } 88 | console.log(res.content, '\n\n'); 89 | cb(); 90 | }); 91 | }); 92 | }); 93 | 94 | app.task('default', ['helper-locals']); 95 | 96 | module.exports = app; 97 | -------------------------------------------------------------------------------- /examples/06-customizing/assemblefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assemble = require('assemble'); 4 | var utils = require('log-utils'); 5 | var app = assemble(); 6 | 7 | /** 8 | * Context is customizable by adding optional functions to the `app.options` object. 9 | * This examples shows the ways to customize the context. 10 | * 11 | * @name customizing 12 | * @api public 13 | */ 14 | 15 | app.task('customizing', function(cb) { 16 | console.log(); 17 | console.log(); 18 | var extend = require('extend-shallow'); 19 | app.engine('txt', require('engine-base')); 20 | app.option('engine', 'txt'); 21 | 22 | app.create('partials'); 23 | app.create('pages'); 24 | 25 | /** 26 | * Add a context option 27 | * This will override all the other data with the "render locals". 28 | */ 29 | 30 | app.option('context', function(view, locals) { 31 | return extend({}, this.cache.data, view.context(), locals); 32 | }); 33 | 34 | /** 35 | * Add "global" data to `app.cache.data` through the `app.data` method. 36 | * This will be overridden by the render locals and view data when the page is rendered. 37 | */ 38 | 39 | app.data({title: utils.cyan('Site Title')}); 40 | 41 | /** 42 | * Add a "button" partial with view locals data and view data. 43 | * Since we're using a custom context option, the "render locals" will override the view data. 44 | * When "helper locals" is passed to the "partial" helper, all data on the view will be overridden. 45 | */ 46 | 47 | app.partial('button', { 48 | content: 'button: <%= title %>', 49 | locals: {title: utils.blue('Button Locals Title')}, 50 | data: {title: utils.green('Button Data Title')} 51 | }); 52 | 53 | /** 54 | * Add a "home" page with view locals data and view data that includes the 3 "button" partials. 55 | * Button "one" will be rendered without passing any helper locals. 56 | * Button "two" will be rendered with the "home" page's data passed as the helper locals. 57 | * Button "three" will be rendered with a "custom" property from the "render locals" passed as the helper locals. 58 | */ 59 | 60 | app.page('home', { 61 | content: [ 62 | 'title: <%= title %>', 63 | 'one: <%= partial("button") %>', 64 | 'two: <%= partial("button", obj) %>', // "obj" is the built-in global object from engine-base 65 | `three: <%= partial("button", {title: '${utils.red('Helper Locals Title')}'}) %>` 66 | ].join('\n'), 67 | locals: {title: utils.blue('Page Locals Title')}, 68 | data: {title: utils.cyan('Page Data Title')} 69 | }); 70 | 71 | var home = app.pages.getView('home'); 72 | console.log('Rendering "home" page with default `app.cache.data`.\nView data will override `app.cache.data`.\nHelper locals will override all other data:\n'); 73 | 74 | /** 75 | * Render the "home" page using all the defaults. 76 | * This will show that the `title` property from the view data is used throughout the "home" page and the "button" partials without helper locals. 77 | */ 78 | 79 | home.render(function(err, res) { 80 | if (err) { 81 | console.log(err, '\n\n'); 82 | return cb(); 83 | } 84 | console.log(res.content, '\n\n'); 85 | 86 | console.log('Rendering "home" page with "render locals" that will override data from `app.cache.data`\n"Render locals" will override view data due to custom context option.\nHelper locals will override all other data:\n'); 87 | 88 | /** 89 | * Render the "home" page using render locals. 90 | * This will show that the `title` property from the view data is in the "home" page and the "button" partials will use their own view data. 91 | */ 92 | 93 | home.render({title: utils.yellow('Render Locals Title')}, function(err, res) { 94 | if (err) { 95 | console.log(err, '\n\n'); 96 | return cb(); 97 | } 98 | console.log(res.content, '\n\n'); 99 | cb(); 100 | }); 101 | }); 102 | }); 103 | 104 | app.task('default', ['customizing']); 105 | 106 | module.exports = app; 107 | -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 | ## What is context? 2 | 3 | Context is an object that is created in-memory for rendering templates. Context is made up of other data objects that are created and modified throughout the render cycle. Below we'll discuss [which data objects](#what-objects-are-used-to-create-the-context) are used, where they come from and the [order in which they're merged](#how-are-the-objects-merged). We're also going to learn how to [customize the context object](#customizing-context) and how to use the context in your own [custom helpers](#context-and-helpers). 4 | 5 | This repository also contains [examples](./examples) that may be run from the command line with [assemble][]. [See below](#examples) for more information on installing and running the examples. 6 | 7 | --- 8 | 9 | ## What objects are used to create the context? 10 | 11 | - `app.cache.data` (from `app.data()`) 12 | * main data object that is useful for "global" data. 13 | * usually includes properties like `site` (this is controlled by the user) 14 | - `view.locals` 15 | * individual view "local" data 16 | * overrides `app.cache.data` at the individual view level 17 | * added through middleware or when creating a view with `views.addView()` 18 | - `view.data` (front-matter) 19 | * individual view data object 20 | * overrides `app.cache.data` and `view.locals` 21 | * may be specified as view "front-matter" that is parsed in `onLoad` middleware 22 | * usually includes properties like `title` and `layout` to override "global" data at the view (page) level 23 | - `render` locals 24 | * local data object specified when calling the `.render()` method. 25 | * useful for specifying data that may not exist on `view.locals` or `view.data` 26 | - `helper` locals 27 | * local data object specified when calling a view helper in another template 28 | * works with the built-in "singular" view helper (e.g. `\{{partial "foo" locals}}`) 29 | * will override all other data. 30 | 31 | --- 32 | 33 | ## How are the objects merged? 34 | 35 | There is a default order of operations when it comes to merging the data context. The order is [customizable](#customizing-context) by the user. 36 | 37 | - `app.cache.data` 38 | - `view.locals` 39 | - `render.locals` 40 | - `view.data` 41 | - `helper-locals` 42 | 43 | The context is created by merging the objects in the specified order through the various methods discribed in [Customizing context](#customizing-context): 44 | 45 | ```js 46 | // merges the view context first 47 | // e.g.: `merge(view.locals, locals, view.data)` 48 | var context = view.context(locals); 49 | 50 | // merges the `app.cache.data` 51 | context = merge({}, app.cache.data, context); 52 | ``` 53 | 54 | In addition to the main context, helpers may use the `this.ctx()` method to merge in helper locals that are passed. 55 | The built-in singular helpers like `\{{partial}}` use this method to ensure helper locals are used. 56 | 57 | ```handlebars 58 | \{{partial "button" locals}} 59 | ``` 60 | 61 | This will result in the `locals` object being merged onto the context when rendering the "button" partial. 62 | The default behaviour for merging the helper context is: 63 | 64 | ```js 65 | // merge the current "view" front-matter with current context built above 66 | context = merge({}, context, page.data); 67 | // merge in the partial locals and front-matter 68 | context = merge({}, context, button.locals, button.data); 69 | // merge in helper locals and options.hash 70 | context = merge({}, context, locals, options.hash); 71 | ``` 72 | 73 | --- 74 | 75 | ## Customizing context 76 | 77 | Customize how the context object is created. 78 | 79 | - `view.context` 80 | * method that takes optional `locals` object 81 | * merges data by doing `return merge(view.locals, locals, view.data)` 82 | * may override directly to change the behaviour 83 | - `app.context` 84 | * method that takes `view` and optional `locals` object 85 | * calls the `view.context` before merging data 86 | * merges data by doing `return merge({}, this.cache.data, view.context(locals))` 87 | - `options.context`: Customize how the context object is created. 88 | * may override functionality through the `context` option: 89 | 90 | ```js 91 | app.option('context', function(view, locals) { 92 | // this is the app 93 | return merge({}, this.cache.data, view.context(), locals); 94 | }); 95 | ``` 96 | - `options.helperContext`: Custom how the helper context is created. 97 | * may override the functionality used in the `this.ctx()` method in helpers through the `helperContext` option: 98 | 99 | ```js 100 | app.option('helperContext', function(view, locals, options) { 101 | return merge({}, view.context(), locals); 102 | }); 103 | ``` 104 | 105 | ## Examples 106 | {%= docs('examples.md') %} 107 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "modules": true, 4 | "experimentalObjectRestSpread": true 5 | }, 6 | "env": { 7 | "browser": false, 8 | "es6": true, 9 | "node": true, 10 | "mocha": true 11 | }, 12 | "globals": { 13 | "document": false, 14 | "navigator": false, 15 | "window": false 16 | }, 17 | "rules": { 18 | "accessor-pairs": 2, 19 | "arrow-spacing": [ 20 | 2, 21 | { 22 | "before": true, 23 | "after": true 24 | } 25 | ], 26 | "block-spacing": [ 27 | 2, 28 | "always" 29 | ], 30 | "brace-style": [ 31 | 2, 32 | "1tbs", 33 | { 34 | "allowSingleLine": true 35 | } 36 | ], 37 | "comma-dangle": [ 38 | 2, 39 | "never" 40 | ], 41 | "comma-spacing": [ 42 | 2, 43 | { 44 | "before": false, 45 | "after": true 46 | } 47 | ], 48 | "comma-style": [ 49 | 2, 50 | "last" 51 | ], 52 | "constructor-super": 2, 53 | "curly": [ 54 | 2, 55 | "multi-line" 56 | ], 57 | "dot-location": [ 58 | 2, 59 | "property" 60 | ], 61 | "eol-last": 2, 62 | "eqeqeq": [ 63 | 2, 64 | "allow-null" 65 | ], 66 | "generator-star-spacing": [ 67 | 2, 68 | { 69 | "before": true, 70 | "after": true 71 | } 72 | ], 73 | "handle-callback-err": [ 74 | 2, 75 | "^(err|error)$" 76 | ], 77 | "indent": [ 78 | 2, 79 | 2, 80 | { 81 | "SwitchCase": 1 82 | } 83 | ], 84 | "key-spacing": [ 85 | 2, 86 | { 87 | "beforeColon": false, 88 | "afterColon": true 89 | } 90 | ], 91 | "keyword-spacing": [ 92 | 2, 93 | { 94 | "before": true, 95 | "after": true 96 | } 97 | ], 98 | "new-cap": [ 99 | 2, 100 | { 101 | "newIsCap": true, 102 | "capIsNew": false 103 | } 104 | ], 105 | "new-parens": 2, 106 | "no-array-constructor": 2, 107 | "no-caller": 2, 108 | "no-class-assign": 2, 109 | "no-cond-assign": 2, 110 | "no-const-assign": 2, 111 | "no-control-regex": 2, 112 | "no-debugger": 2, 113 | "no-delete-var": 2, 114 | "no-dupe-args": 2, 115 | "no-dupe-class-members": 2, 116 | "no-dupe-keys": 2, 117 | "no-duplicate-case": 2, 118 | "no-empty-character-class": 2, 119 | "no-eval": 2, 120 | "no-ex-assign": 2, 121 | "no-extend-native": 2, 122 | "no-extra-bind": 2, 123 | "no-extra-boolean-cast": 2, 124 | "no-extra-parens": [ 125 | 2, 126 | "functions" 127 | ], 128 | "no-fallthrough": 2, 129 | "no-floating-decimal": 2, 130 | "no-func-assign": 2, 131 | "no-implied-eval": 2, 132 | "no-inner-declarations": [ 133 | 2, 134 | "functions" 135 | ], 136 | "no-invalid-regexp": 2, 137 | "no-irregular-whitespace": 2, 138 | "no-iterator": 2, 139 | "no-label-var": 2, 140 | "no-labels": 2, 141 | "no-lone-blocks": 2, 142 | "no-mixed-spaces-and-tabs": 2, 143 | "no-multi-spaces": 2, 144 | "no-multi-str": 2, 145 | "no-multiple-empty-lines": [ 146 | 2, 147 | { 148 | "max": 1 149 | } 150 | ], 151 | "no-native-reassign": 0, 152 | "no-negated-in-lhs": 2, 153 | "no-new": 2, 154 | "no-new-func": 2, 155 | "no-new-object": 2, 156 | "no-new-require": 2, 157 | "no-new-wrappers": 2, 158 | "no-obj-calls": 2, 159 | "no-octal": 2, 160 | "no-octal-escape": 2, 161 | "no-proto": 0, 162 | "no-redeclare": 2, 163 | "no-regex-spaces": 2, 164 | "no-return-assign": 2, 165 | "no-self-compare": 2, 166 | "no-sequences": 2, 167 | "no-shadow-restricted-names": 2, 168 | "no-spaced-func": 2, 169 | "no-sparse-arrays": 2, 170 | "no-this-before-super": 2, 171 | "no-throw-literal": 2, 172 | "no-trailing-spaces": 0, 173 | "no-undef": 2, 174 | "no-undef-init": 2, 175 | "no-unexpected-multiline": 2, 176 | "no-unneeded-ternary": [ 177 | 2, 178 | { 179 | "defaultAssignment": false 180 | } 181 | ], 182 | "no-unreachable": 2, 183 | "no-unused-vars": [ 184 | 2, 185 | { 186 | "vars": "all", 187 | "args": "none" 188 | } 189 | ], 190 | "no-useless-call": 0, 191 | "no-with": 2, 192 | "one-var": [ 193 | 0, 194 | { 195 | "initialized": "never" 196 | } 197 | ], 198 | "operator-linebreak": [ 199 | 0, 200 | "after", 201 | { 202 | "overrides": { 203 | "?": "before", 204 | ":": "before" 205 | } 206 | } 207 | ], 208 | "padded-blocks": [ 209 | 0, 210 | "never" 211 | ], 212 | "quotes": [ 213 | 2, 214 | "single", 215 | "avoid-escape" 216 | ], 217 | "radix": 2, 218 | "semi": [ 219 | 2, 220 | "always" 221 | ], 222 | "semi-spacing": [ 223 | 2, 224 | { 225 | "before": false, 226 | "after": true 227 | } 228 | ], 229 | "space-before-blocks": [ 230 | 2, 231 | "always" 232 | ], 233 | "space-before-function-paren": [ 234 | 2, 235 | "never" 236 | ], 237 | "space-in-parens": [ 238 | 2, 239 | "never" 240 | ], 241 | "space-infix-ops": 2, 242 | "space-unary-ops": [ 243 | 2, 244 | { 245 | "words": true, 246 | "nonwords": false 247 | } 248 | ], 249 | "spaced-comment": [ 250 | 0, 251 | "always", 252 | { 253 | "markers": [ 254 | "global", 255 | "globals", 256 | "eslint", 257 | "eslint-disable", 258 | "*package", 259 | "!", 260 | "," 261 | ] 262 | } 263 | ], 264 | "use-isnan": 2, 265 | "valid-typeof": 2, 266 | "wrap-iife": [ 267 | 2, 268 | "any" 269 | ], 270 | "yoda": [ 271 | 2, 272 | "never" 273 | ] 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # context-workshop [![NPM version](https://img.shields.io/npm/v/context-workshop.svg?style=flat)](https://www.npmjs.com/package/context-workshop) [![NPM downloads](https://img.shields.io/npm/dm/context-workshop.svg?style=flat)](https://npmjs.org/package/context-workshop) [![Build Status](https://img.shields.io/travis/assemble/context-workshop.svg?style=flat)](https://travis-ci.org/assemble/context-workshop) 2 | 3 | One of assemble's biggest strengths is granular control over `context`. This workshop explains how context is created, as well as where, when and why the context works the way it does at each point in the render cycle. 4 | 5 | ## Table of Contents 6 | 7 | - [Install](#install) 8 | - [What is context?](#what-is-context) 9 | - [What objects are used to create the context?](#what-objects-are-used-to-create-the-context) 10 | - [How are the objects merged?](#how-are-the-objects-merged) 11 | - [Customizing context](#customizing-context) 12 | - [Examples](#examples) 13 | - [About](#about) 14 | * [Related projects](#related-projects) 15 | * [Contributing](#contributing) 16 | * [Building docs](#building-docs) 17 | * [Running tests](#running-tests) 18 | * [Author](#author) 19 | * [License](#license) 20 | 21 | _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ 22 | 23 | ## Install 24 | 25 | Install with [npm](https://www.npmjs.com/): 26 | 27 | ```sh 28 | $ npm install --save context-workshop 29 | ``` 30 | 31 | ## What is context? 32 | 33 | Context is an object that is created in-memory for rendering templates. Context is made up of other data objects that are created and modified throughout the render cycle. Below we'll discuss [which data objects](#what-objects-are-used-to-create-the-context) are used, where they come from and the [order in which they're merged](#how-are-the-objects-merged). We're also going to learn how to [customize the context object](#customizing-context) and how to use the context in your own [custom helpers](#context-and-helpers). 34 | 35 | This repository also contains [examples](./examples) that may be run from the command line with [assemble](https://github.com/assemble/assemble). [See below](#examples) for more information on installing and running the examples. 36 | 37 | *** 38 | 39 | ## What objects are used to create the context? 40 | 41 | * `app.cache.data` (from `app.data()`) 42 | - main data object that is useful for "global" data. 43 | - usually includes properties like `site` (this is controlled by the user) 44 | 45 | * `view.locals` 46 | - individual view "local" data 47 | - overrides `app.cache.data` at the individual view level 48 | - added through middleware or when creating a view with `views.addView()` 49 | 50 | * `view.data` (front-matter) 51 | - individual view data object 52 | - overrides `app.cache.data` and `view.locals` 53 | - may be specified as view "front-matter" that is parsed in `onLoad` middleware 54 | - usually includes properties like `title` and `layout` to override "global" data at the view (page) level 55 | 56 | * `render` locals 57 | - local data object specified when calling the `.render()` method. 58 | - useful for specifying data that may not exist on `view.locals` or `view.data` 59 | 60 | * `helper` locals 61 | - local data object specified when calling a view helper in another template 62 | - works with the built-in "singular" view helper (e.g. `{{partial "foo" locals}}`) 63 | - will override all other data. 64 | 65 | *** 66 | 67 | ## How are the objects merged? 68 | 69 | There is a default order of operations when it comes to merging the data context. The order is [customizable](#customizing-context) by the user. 70 | 71 | * `app.cache.data` 72 | * `view.locals` 73 | * `render.locals` 74 | * `view.data` 75 | * `helper-locals` 76 | 77 | The context is created by merging the objects in the specified order through the various methods discribed in [Customizing context](#customizing-context): 78 | 79 | ```js 80 | // merges the view context first 81 | // e.g.: `merge(view.locals, locals, view.data)` 82 | var context = view.context(locals); 83 | 84 | // merges the `app.cache.data` 85 | context = merge({}, app.cache.data, context); 86 | ``` 87 | 88 | In addition to the main context, helpers may use the `this.ctx()` method to merge in helper locals that are passed. 89 | The built-in singular helpers like `{{partial}}` use this method to ensure helper locals are used. 90 | 91 | ```handlebars 92 | {{partial "button" locals}} 93 | ``` 94 | 95 | This will result in the `locals` object being merged onto the context when rendering the "button" partial. 96 | The default behaviour for merging the helper context is: 97 | 98 | ```js 99 | // merge the current "view" front-matter with current context built above 100 | context = merge({}, context, page.data); 101 | // merge in the partial locals and front-matter 102 | context = merge({}, context, button.locals, button.data); 103 | // merge in helper locals and options.hash 104 | context = merge({}, context, locals, options.hash); 105 | ``` 106 | 107 | *** 108 | 109 | ## Customizing context 110 | 111 | Customize how the context object is created. 112 | 113 | * `view.context` 114 | - method that takes optional `locals` object 115 | - merges data by doing `return merge(view.locals, locals, view.data)` 116 | - may override directly to change the behaviour 117 | 118 | * `app.context` 119 | - method that takes `view` and optional `locals` object 120 | - calls the `view.context` before merging data 121 | - merges data by doing `return merge({}, this.cache.data, view.context(locals))` 122 | 123 | * `options.context`: Customize how the context object is created. 124 | - may override functionality through the `context` option: 125 | 126 | ```js 127 | app.option('context', function(view, locals) { 128 | // this is the app 129 | return merge({}, this.cache.data, view.context(), locals); 130 | }); 131 | ``` 132 | 133 | * `options.helperContext`: Custom how the helper context is created. 134 | - may override the functionality used in the `this.ctx()` method in helpers through the `helperContext` option: 135 | 136 | ```js 137 | app.option('helperContext', function(view, locals, options) { 138 | return merge({}, view.context(), locals); 139 | }); 140 | ``` 141 | 142 | ## Examples 143 | 144 | ### Installing 145 | 146 | Clone this project and install the npm modules to run the examples: 147 | 148 | ```sh 149 | # clone the project 150 | $ git clone https://github.com/assemble/context-workshop 151 | # cd into the folder 152 | $ cd context-workshop 153 | # install npm modules 154 | $ npm install 155 | # install assemble globally if not already installed 156 | $ npm install --global assemble 157 | ``` 158 | 159 | ### Running 160 | 161 | Each example may be run by using `assemble`: 162 | 163 | ```sh 164 | $ assemble 165 | ``` 166 | 167 | To view a list of examples run the default assemble command: 168 | 169 | ```sh 170 | $ assemble 171 | ``` 172 | 173 | To interactively choose an example to run use the `-i` option: 174 | 175 | ```sh 176 | $ assemble -i 177 | ``` 178 | 179 | ### [app-cache-data](examples/01-app-cache-data/assemblefile.js#L15) 180 | 181 | Assemble will use `app.cache.data` when rendering views (pages). 182 | To add data to `app.cache.data` use the `app.data()` api. See [base-data](https://github.com/node-base/base-data) for all the available options for `app.data()`. 183 | 184 | To run this example: 185 | 186 | ```sh 187 | $ assemble app-cache-data 188 | ``` 189 | 190 | ![image](https://cloud.githubusercontent.com/assets/995160/16308022/6e9e6ef0-3931-11e6-82d2-a595b0f798fc.png) 191 | 192 | Code snippet from example [assemblefile.js](./examples/01-app-cache-data/assemblefile.js) 193 | 194 | ```js 195 | // add app-cache-data 196 | app.data({title: 'Site Title'}); 197 | 198 | // create a simple "button" partial 199 | app.partial('button', {content: 'button: <%= title %>'}); 200 | 201 | // create a simple "home" page containing 3 "button" partials 202 | app.page('home', { 203 | content: [ 204 | 'title: <%= title %>', 205 | 'one: <%= partial("button") %>', 206 | 'two: <%= partial("button") %>', 207 | 'three: <%= partial("button") %>' 208 | ].join('\n') 209 | }); 210 | 211 | // render the "home" page with no additional data 212 | var home = app.pages.getView('home'); 213 | home.render(function(err, res) { 214 | if (err) return console.error(err); 215 | console.log(res.content); 216 | }); 217 | ``` 218 | 219 | ### [render-locals](examples/02-render-locals/assemblefile.js#L15) 220 | 221 | Render locals is the data object that is passed into the `.render()` method when rendering views. 222 | The following example will show how the render locals will override data from `app.cache.data` when the context is created. 223 | 224 | To run this example: 225 | 226 | ```sh 227 | $ assemble render-locals 228 | ``` 229 | 230 | ![image](https://cloud.githubusercontent.com/assets/995160/16308107/b3287bce-3931-11e6-8c56-2676d4515c24.png) 231 | 232 | Code snippet from example [assemblefile.js](./examples/02-render-locals/assemblefile.js) 233 | 234 | ```js 235 | // add app-cache-data 236 | app.data({title: 'Site Title'}); 237 | 238 | // create a simple "button" partial 239 | app.partial('button', {content: 'button: <%= title %>'}); 240 | 241 | // create a simple "home" page containing 3 "button" partials 242 | app.page('home', { 243 | content: [ 244 | 'title: <%= title %>', 245 | 'one: <%= partial("button") %>', 246 | 'two: <%= partial("button") %>', 247 | 'three: <%= partial("button") %>' 248 | ].join('\n') 249 | }); 250 | 251 | // render the "home" page with no additional data 252 | var home = app.pages.getView('home'); 253 | home.render(function(err, res) { 254 | if (err) return console.log(err); 255 | console.log(res.content); 256 | 257 | home.render({title: 'Render Locals Title'}, function(err, res) { 258 | if (err) return console.log(err); 259 | console.log(res.content); 260 | }); 261 | }); 262 | ``` 263 | 264 | ### [view-locals](examples/03-view-locals/assemblefile.js#L15) 265 | 266 | View locals is the data object that is on view objects that will be used to override `app.cache.data`. 267 | The following example will show how the view locals will override data from `app.cache.data`, but is overridden by "render locals" when the context is created. 268 | 269 | To run this example: 270 | 271 | ```sh 272 | $ assemble view-locals 273 | ``` 274 | 275 | ![image](https://cloud.githubusercontent.com/assets/995160/16308141/cf0ef08e-3931-11e6-9bbf-008ad3efcbc8.png) 276 | 277 | Code snippet from example [assemblefile.js](./examples/03-view-locals/assemblefile.js) 278 | 279 | ```js 280 | // add app-cache-data 281 | app.data({title: 'Site Title'}); 282 | 283 | // Add a "button" partial with view locals data. 284 | // This data is only overridden by "render locals" if the button is rendered directly with `.render` and "render locals" are passed into `.render`. 285 | 286 | app.partial('button', { 287 | content: 'button: <%= title %>', 288 | locals: {title: 'Button Locals Title'} 289 | }); 290 | 291 | // Add a "home" page with view locals data that includes the 3 "button" partials. 292 | app.page('home', { 293 | content: [ 294 | 'title: <%= title %>', 295 | 'one: <%= partial("button") %>', 296 | 'two: <%= partial("button") %>', 297 | 'three: <%= partial("button") %>' 298 | ].join('\n'), 299 | locals: {title: 'Page Locals Title'} 300 | }); 301 | 302 | var home = app.pages.getView('home'); 303 | home.render(function(err, res) { 304 | if (err) return console.error(err); 305 | console.log(res.content); 306 | 307 | home.render({title: 'Render Locals Title'}, function(err, res) { 308 | if (err) return console.error(err); 309 | console.log(res.content); 310 | }); 311 | }); 312 | ``` 313 | 314 | ### [view-data](examples/04-view-data/assemblefile.js#L15) 315 | 316 | View data is the data object that is on view objects that will be used to override `app.cache.data`. 317 | The following example will show how the view data will override data from `app.cache.data` and "render locals" when the context is created. 318 | 319 | To run this example: 320 | 321 | ```sh 322 | $ assemble view-data 323 | ``` 324 | 325 | ![image](https://cloud.githubusercontent.com/assets/995160/16308172/eac00a7a-3931-11e6-8006-152eb63185a3.png) 326 | 327 | Code snippet from example [assemblefile.js](./examples/04-view-data/assemblefile.js) 328 | 329 | ```js 330 | // add app-cache-data 331 | app.data({title: 'Site Title'}); 332 | 333 | // Add a "button" partial with view locals data and view data. 334 | // The view data will override `app.cache.data`, "render locals", and "view locals". 335 | app.partial('button', { 336 | content: 'button: <%= title %>', 337 | locals: {title: 'Button Locals Title'}, 338 | data: {title: 'Button Data Title'} 339 | }); 340 | 341 | // Add a "home" page with view locals data and view data that includes the 3 "button" partials. 342 | app.page('home', { 343 | content: [ 344 | 'title: <%= title %>', 345 | 'one: <%= partial("button") %>', 346 | 'two: <%= partial("button") %>', 347 | 'three: <%= partial("button") %>' 348 | ].join('\n'), 349 | locals: {title: 'Page Locals Title'}, 350 | data: {title: 'Page Data Title'} 351 | }); 352 | 353 | var home = app.pages.getView('home'); 354 | home.render(function(err, res) { 355 | if (err) return console.error(err); 356 | console.log(res.content); 357 | 358 | home.render({title: 'Render Locals Title'}, function(err, res) { 359 | if (err) return console.error(err); 360 | console.log(res.content); 361 | }); 362 | }); 363 | ``` 364 | 365 | ### [helper-locals](examples/05-helper-locals/assemblefile.js#L15) 366 | 367 | Helper locals is the data object that is passed into the built-in view helpers. This data will override all other data for that specific view. 368 | The following example will show how the helper locals will override all other data when rendering a partial view. 369 | 370 | To run this example: 371 | 372 | ```sh 373 | $ assemble helper-locals 374 | ``` 375 | 376 | ![image](https://cloud.githubusercontent.com/assets/995160/16308201/0bf8ee1e-3932-11e6-81e8-9eae38234e95.png) 377 | 378 | Code snippet from example [assemblefile.js](./examples/05-helper-locals/assemblefile.js) 379 | 380 | ```js 381 | // add app-cache-data 382 | app.data({title: utils.cyan('Site Title')}); 383 | 384 | // Add a "button" partial with view locals data and view data. 385 | // The view data will override `app.cache.data`, "render locals", and "view locals". 386 | // When "helper locals" is passed to the "partial" helper, all data on the view will be overridden. 387 | app.partial('button', { 388 | content: 'button: <%= title %>', 389 | locals: {title: 'Button Locals Title'}, 390 | data: {title: 'Button Data Title'} 391 | }); 392 | 393 | // Add a "home" page with view locals data and view data that includes the 3 "button" partials. 394 | // Button "one" will be rendered without passing any helper locals. 395 | // Button "two" will be rendered with the "home" page's data passed as the helper locals. 396 | // Button "three" will be rendered with a "custom" property from the "render locals" passed as the helper locals. 397 | app.page('home', { 398 | content: [ 399 | 'title: <%= title %>', 400 | 'one: <%= partial("button") %>', 401 | 'two: <%= partial("button", obj) %>', // "obj" is the built-in global object from engine-base 402 | `three: <%= partial("button", {title: 'Helper Locals Title'}) %>` 403 | ].join('\n'), 404 | locals: {title: 'Page Locals Title'}, 405 | data: {title: 'Page Data Title'} 406 | }); 407 | 408 | var home = app.pages.getView('home'); 409 | home.render(function(err, res) { 410 | if (err) return console.error(err); 411 | console.log(res.content); 412 | 413 | home.render({title: 'Render Locals Title'}, function(err, res) { 414 | if (err) return console.error(err); 415 | console.log(res.content); 416 | }); 417 | }); 418 | ``` 419 | 420 | ### [customizing](examples/06-customizing/assemblefile.js#L15) 421 | 422 | Context is customizable by adding optional functions to the `app.options` object. 423 | This examples shows the ways to customize the context. 424 | 425 | To run this example: 426 | 427 | ```sh 428 | $ assemble customizing 429 | ``` 430 | 431 | ![image](https://cloud.githubusercontent.com/assets/995160/16308576/88c480b0-3933-11e6-8873-2e40ddaefaf5.png) 432 | 433 | Code snippet from example [assemblefile.js](./examples/06-customizing/assemblefile.js) 434 | 435 | ```js 436 | // Add a context option 437 | app.option('context', function(view, locals) { 438 | // override all the other data with the "render locals" 439 | return extend({}, this.cache.data, view.context(), locals); 440 | }); 441 | 442 | // add app-cache-data 443 | app.data({title: 'Site Title'}); 444 | 445 | // create a simple "button" partial 446 | app.partial('button', {content: 'button: <%= title %>'}); 447 | 448 | // create a simple "home" page containing 3 "button" partials 449 | app.page('home', { 450 | content: [ 451 | 'title: <%= title %>', 452 | 'one: <%= partial("button") %>', 453 | 'two: <%= partial("button") %>', 454 | 'three: <%= partial("button") %>' 455 | ].join('\n') 456 | }); 457 | 458 | // render the "home" page with no additional data 459 | var home = app.pages.getView('home'); 460 | home.render(function(err, res) { 461 | if (err) return console.error(err); 462 | console.log(res.content); 463 | }); 464 | ``` 465 | 466 | ## About 467 | 468 | ### Contributing 469 | 470 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). 471 | 472 | ### Building docs 473 | 474 | _(This document was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme) (a [verb](https://github.com/verbose/verb) generator), please don't edit the readme directly. Any changes to the readme must be made in [.verb.md](.verb.md).)_ 475 | 476 | To generate the readme and API documentation with [verb](https://github.com/verbose/verb): 477 | 478 | ```sh 479 | $ npm install -g verb verb-generate-readme && verb 480 | ``` 481 | 482 | ### Running tests 483 | 484 | Install dev dependencies: 485 | 486 | ```sh 487 | $ npm install -d && npm test 488 | ``` 489 | 490 | ### Author 491 | 492 | **Jon Schlinkert** 493 | 494 | * [github/jonschlinkert](https://github.com/jonschlinkert) 495 | * [twitter/jonschlinkert](http://twitter.com/jonschlinkert) 496 | 497 | ### License 498 | 499 | Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert). 500 | Released under the [MIT license](https://github.com/assemble/context-workshop/blob/master/LICENSE). 501 | 502 | *** 503 | 504 | _This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 18, 2016._ --------------------------------------------------------------------------------