Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

twig-layout

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

twig-layout - npm Package Compare versions

Comparing version 0.1.2 to 0.1.3

.npmignore

262

block.js
/*!
* twig-layout
*
* Copyright(c) 2018 Metais Fabien
* Copyright(c) 2019 Metais Fabien
* MIT Licensed

@@ -9,2 +9,7 @@ */

/**
*
* @typedef {Object} BlockConfig
* @property {Array} blocks Children blocks to add
*/
/**
* Block Object

@@ -19,27 +24,104 @@ *

*
* @param {string} name name
* @param {string} html html template
* @param {Object} config block config
* @param {string} parent block parent name
* @param {Layout} layout layout instance
* @param {Layout} layout Layout instance
* @param {Object} options Block options
* @param {string} options.name Block name
* @param {string} options.html Block html
* @param {BlockConfig} options.config Block config
* @param {string} options.parent Block parnet name
* @param {string} options.template Block tamplate path
* @param {Cache} options.cache Cache instance
*/
constructor (name, html, config, parent, layout) {
//layout instance
constructor (layout, options) {
/**
* Layout instance
* @type {Layout}
*/
this.layout = layout
//block parent name overwritted by the config
this.parent = config.parent ? config.parent : parent
/**
* template path
* @type {string}
*/
this.template = options.template
//block name
this.name = name
/**
* Config object
* @type {Object}
*/
this.config = options.config
/**
* block parent name take from config or options
* @type {string}
*/
this.parent = this.config.parent ? this.config.parent : options.parent
//html template
this.html = html
this.config = config
/**
* Block name
* @type {string}
*/
this.name = options.name
/**
* Block html
* @type {string}
*/
this.html = options.html
/**
* Block data object, it's the object passed to twig for render
* @type {string}
*/
this.data = {}
this.blocks = []
this.page = config.page ? config.page : null
this.extend()
//bind block in data
this.data.this = this;
/**
* Store children blocks from the config to add them after this block
* @private
*/
this._blocks = []
/**
* Block page path
* @type {string|null}
*/
this.page = this.config.page ? this.config.page : null
/**
* Cache instance
* @type {Cache}
* @private
*/
this._cache = options.cache
/**
* Cache render key
* @type {string|null}
* @private
*/
this._cacheRenderKey = null
/**
* Cache render key prefix
* @type {string}
* @private
*/
this._cacheRenderKeyPrefix = 'layout:block.render:'
/**
* Store the html cached of the block
* @type {string}
* @private
*/
this._cacheRender = null
/**
* Cache render ttl
* @type {int|null}
* @private
*/
this._cacheRenderTtl = null
//add children block
if (this.config.blocks) {

@@ -54,16 +136,75 @@ this.addBlocks(this.config.blocks)

*/
async init () {}
async init() {}
/**
* AfterLoad call back
* afterLoadChrildren hook
* called after all blocks are loaded
*/
async afterLoad () {}
async afterInitChildren() {}
/**
* Before render callback
* AfterLoad hook
* called after all blocks are loaded
*/
async beforeRender () {}
async afterLoad() {}
/**
* beforeRender hook
* Call before the render
*/
async beforeRender() {}
/**
* Check if the block render is cached
* @returns {Boolean}
*/
async isCached() {
if (!this._cache || !this.cache) {
return false
} else {
return this._getCacheRender().then(cacheRender => {
return cacheRender !== null ? true : false
})
}
}
/**
* renturn the render from the cache
* @returns {String}
* @private
*/
async _getCacheRender() {
if (this._cacheRender === null) {
//get the cache
const cacheRender = await this._cache.get(this._getCacheRenderKey())
//if is in cache
if (cacheRender !== null && cacheRender !== undefined) {
this._cacheRender = cacheRender
return cacheRender
}
return null
}
return this._cacheRender
}
/**
* Return the render cache key
*
* @returns {String}
* @private
*/
_getCacheRenderKey() {
if (!this._cacheRenderKey) {
if (!this.template) {
throw new Error ('A block has no cacheRenderKey and no template')
} else {
return this._cacheRenderKeyPrefix + this.template
}
} else {
return this._cacheRenderKeyPrefix + this._cacheRenderKey
}
}
/**
* Add many blocks

@@ -74,3 +215,3 @@ *

addBlocks(blocks) {
for (var key in blocks) {
for (const key in blocks) {
this.addBlock(blocks[key])

@@ -83,11 +224,29 @@ }

* All blocks are loaded by the layout
* after this block instance is created
* after this block init method is called
*
* @param {Object} block Block config
* @private
*/
addBlock(block) {
this.blocks.push(block)
this._blocks.push(block)
}
/**
* return the children block to load after this block init
*/
getChildrenBlock() {
return this._blocks
}
/**
* Get a block instance by name
*
* @param {string} name Block name
* @return {Block} Block instance
*/
getBlock(name) {
return this.layout.getBlock(name)
}
/**
* Render the block

@@ -97,22 +256,45 @@ *

*
* @return {String}
* @return {string} Block html rendered
*/
async render () {
await this.beforeRender()
this.data.blocks = await this.layout.renderBlocks(this.name)
return this.layout.renderHtml(this.html, this.data)
//if cache is disable render html
if (!this._cache || !this.cache) {
return this._getHtml()
} else {
//try to get cache render
const cacheRender = await this._getCacheRender()
//if render is cached return it
if (cacheRender !== null) {
return cacheRender
} else {
//render html and cache it
const html = await this._getHtml()
let opts = {}
//set ttl
if (this._cacheRenderTtl) {
opts.ttl = this._cacheRenderTtl
}
//save render in cache
await this._cache.set(this._getCacheRenderKey(), html, opts)
return html
}
}
}
/**
* Extend the block Object from the layout config
* Extend the template data Object from the layout config
*
* Used to define methods to use them in the Block or in the html template
* Render block and return html
*
* @returns {string} Block html rendered
* @private
*/
extend () {
var extend = this.layout.options.extendBlock || {}
Object.assign(this, extend)
var extend = this.layout.options.extendTemplate || {}
Object.assign(this.data, extend)
async _getHtml() {
if (!this.html) {
return ''
}
return this.layout.renderHtml(this.html, this.data).catch((error) => {
this.layout.emit('error', error, { block: this.name, html: this.html })
return ''
})
}

@@ -119,0 +301,0 @@ }

806

layout.js

@@ -8,67 +8,214 @@ /*!

var twig = require('twig')
var path = require('path')
var fs = require('fs')
const twig = require('twig')
const stringHash = require('string-hash')
var Block = require('./block')
const EventEmitter = require('events')
const {promisify} = require('util')
const path = require('path')
const fs = require('fs')
const utils = require('@midgar/utils')
const Block = require('./block')
/**
* Layout Object
*
* Load the blocks and render them
* Cache object use standard functions you'd expect in most caches
*
* @typedef {Object} Cache
* @property {function(string, *):undefined} set Set something in cache
* @property {function(string):*} get Get something from cache
* @property {function(string):*} detl Delete from cache
*/
class Layout {
constructor (options) {
/**
* Layout Class
* Manage blocks
*/
class Layout extends EventEmitter {
/**
* @param {Object} options Layout options
* @param {Cache} options.cache Cache instance
* @param {string} options.tmpDir temp dir to write block in file and require them
* @param {string} options.views Path to the views dir
*/
constructor(options) {
super()
//options
this.options = options || {}
this._options = Object.assign({
cache: false,
tmpDir: null,
sourceMap: false,
mode: 'tmpfile',
views: '',
}, options)
//store the block instance by name
if (!this._options.tmpDir)
throw new Error('No temp dir set')
//
/**
* Store blocks instance by name
* @type {Object}
*/
this.blocks = {}
//store the block instance by parent
/**
* Store the block instance by parent
* @type {Object}
* @private
*/
this._blocks = {}
//template loading state
this.isLoad = false
/**
* Define if render a page or juste template block
* @type {boolen}
*/
this.renderPage = true
/**
* Template block instance
* @type {Block}
* @private
*/
this._templateBlock = null
/**
* Cache instance
* @type {Cache}
* @private
*/
this._cache = options.cache
/**
* Cache key prefix
* @type {string}
* @private
*/
this._cacheFilePrefix = 'layout:'
}
/**
* load a template file
* Extend twig and init cache
*/
async init() {
await Promise.all([this._extendTwig(), this._initCache()])
}
/**
* Extend twig with options extendFilter and extendFunction
* @private
*/
async _extendTwig() {
const extendFilters = this._options.extendFilters || {}
const extendFunctions = this._options.extendFunctions || {}
await Promise.all([this.extendTwigFilters(extendFilters),this.extendTwigFunctions(extendFunctions)])
}
/**
* Extend twig filters
*
* @param {Object} extendFilters filters object {name: filter function, ...}
*/
async extendTwigFilters(extendFilters) {
//list filers
await utils.asyncMap(extendFilters, async (filter, name) => {
//add filter
this.extendTwigFilter (name, filter)
})
}
/**
* Extend twig functions
*
* @param {Object} extendFilters filters object {name: filter function, ...}
*/
async extendTwigFunctions(extendFunctions) {
//list functions
await utils.asyncMap(extendFunctions, async (fn, name) => {
//add function
this.extendTwigFunction (name, fn)
})
this.extendTwigFunction ('getBlockHtml', (name) => this.getBlockHtml(name))
}
/**
* Extend twig filter
*
* @param {String} name filter name
* @param {Function} filter filter function
*/
extendTwigFilter (name, filter) {
twig.extendFilter(name, filter)
}
/**
* Extend twig function
*
* @param {String} name function name
* @param {Function} fn function
*/
extendTwigFunction (name, fn) {
twig.extendFunction(name, fn)
}
/**
* Check if the cache dir exist and create it
* @private
*/
async _initCache() {
//if cache
//check if cache dir exist
const exists = await utils.asyncFileExists(this._options.tmpDir)
if (!exists) {
//create cache dir
await utils.asyncMkdir(this._options.tmpDir)
}
}
/**
* Load a template file
*
* @param blocks
* @param {string} template Template path relative to the views dir
* @param {Object} config Block config
* @param {Object} config.script Block script path
*/
async loadTemplate (template, config) {
config = config || {}
async loadTemplate(template, config = {}) {
//reset blocks arrays
this._blocks = {}
this.blocks = {}
this.blocks = {}
//path of the block script
let script = config.script ? config.script : null
//Load the template block
var block = await this._loadBlock('', template, config, null, null)
const block = await this._loadBlock('', template, config, script, null)
this._templateBlock = block
if (!block) {
throw new Error('Cannot load the main template: ' + template)
}
// Check if the page is defined
if (!block.page) {
throw new Error(
'Page is not defined in the layout config for the block ' + template)
if (this.renderPage && !block.page) {
throw new Error('Page is not defined in the layout config for the block ' + template)
}
//load the page block
this._pageBlock = await this._loadBlock('page', block.page, {}, null,
'root')
if (this.renderPage) {
//load the page block
const pageBlock = await this._loadBlock('page', block.page, {}, null, 'root')
//call after load callbacks
await this._afterLoad()
if (!pageBlock) {
throw new Error('Cannot load the page template: ' + block.page)
}
// execute the page block actions
if (this._pageBlock.config.actions) {
this._processActions(this._pageBlock.config.actions)
this._pageBlock = pageBlock
}
// execute the blocks actions
if (block.config.actions) {
this._processActions(block.config.actions)
}
//set the template is loaded
this.isLoad = true
await this._afterLoad()
}

@@ -78,4 +225,2 @@

* Call the afterLoad Callbacks on all the blocks
*
* @return {<void>}
* @private

@@ -85,10 +230,10 @@ */

//promises array
var afterLoads = []
const afterLoads = []
//add the promises
for (var name in this.blocks)
for (const name in this.blocks)
afterLoads.push(this.blocks[name].afterLoad())
//wait the end
await Promise.all(afterLoads)
//wait the endprivate
return Promise.all(afterLoads)
}

@@ -101,14 +246,13 @@

* @param {string} parent block name
*
* @private
*/
_loadBlocks (blocks, parent) {
var loadBlocks = []
for (var key in blocks) {
var config = blocks[key]
async _loadBlocks(blocks, parent) {
const loadBlocks = []
for (let key in blocks) {
const config = blocks[key]
/*
if (!config.name) {
throw new Error('A block have no name: ' + JSON.stringify(config))
}
*/
if (this.blocks[config.name]) {

@@ -130,3 +274,3 @@ throw new Error('The block ' + config.name + ' is already defined')

//path of the block script
var script = config.script ? config.script : null
let script = config.script ? config.script : null

@@ -153,16 +297,35 @@ //load the block

*/
async _loadBlock (name, template, config, script, parent) {
//if the block have no name and a template
//get the name of the template file for name
if (!name && template) {
name = template.replace(path.extname(template), '')
async _loadBlock(name, template, config, script, parent) {
//check if the block have no name and no template connot load the block
if (!name && !template) {
let error = new Error('A block have no name and no template')
this.emit('error', error, {})
return null
}
//check if the block have a name
if (!name) {
throw new Error('A block have no name and no template')
let block = null
try {
//create block
block = await this.createBlock({ name: name, template: template, config: config, script: script, parent: parent })
} catch (error) {
const params = {}
if (error.code != 'ENOENT') {
if (name) params.block = name
if (template) params.template = template
if (script) params.script = script
} else {
if (parent) params.block = parent
}
this.emit('error', error, params)
return null
}
//create the block instance
var block = await this.createBlock({name: name, template: template,config: config, script: script, parent: parent})
if (!block)
return null
if (!block.name) {
this.emit('error', 'A block have no name', {template: template, script: script})
return null
}

@@ -177,8 +340,2 @@ if (this._blocks[block.parent] == undefined) {

//load the children blocks define in the block config
if (block.config.children != undefined &&
Array.isArray(block.config.children)) {
this._loadBlocks(block.config.children, block.name)
}
return block

@@ -190,46 +347,141 @@ }

*
* @param {string} script block file path
* @param {Object} options block options
* @param {string} template template file path
*
* @return {*}
*
* @return {Block}
* @private
*/
_loadBlockClass(script) {
var filePath = script + '.js'
async _loadBlockClass(options, template) {
let BlockClass = null
const html = template && template.html ? template.html : null
if (!template || !template.script) {
//if the template contain no block class try to use the script
if (options.script) {
BlockClass = await utils.asyncRequire(options.script)
} else {
//if no block class found use a Block class
BlockClass = Block
}
} else {
BlockClass = template.script
}
//the block is in the block directory or in the path define in this.options.blocks
var dirs = []
if (this.options.blocks) dirs.push(this.options.blocks)
dirs.push(__dirname + '/blocks')
//if the file exist require the file and return it
return this._createBlockInstance(BlockClass, options, html)
}
for (var key in dirs) {
var dir = dirs[key]
/**
* Create the block instance
*
* @param {constructor} BlockClass block constructor
* @param {Object} options Block options
* @param {string} options.name Block name
* @param {string} options.parent Block parent name
* @param {string} options.template Block template path
* @param {String} html
* @private
*/
async _createBlockInstance(BlockClass, options, html) {
try {
//create the block instance
const block = new BlockClass(this, {
name: options.name,
html: html,
config: options.config || {},
parent: options.parent,
template: options.template || null,
cache: this._cache
})
//if the file exist require the file and return it
if (fs.existsSync(path.join(dir, filePath))) {
return require(path.join(dir, filePath))
try {
await block.init()
} catch (error) {
const params = {}
if (options.name) params.block = options.name
if (options.template) params.template = options.template
if (options.script) params.script = options.script
this.emit('error', 'connot init block')
this.emit('error', error, params)
return null
}
await this._afterInitBlock(block)
//if block have no html and no template in his option
//and have a temple on his instance load it
if (!block.html && !options.template && block.template) {
const template = await this._loadTemplate(block.template)
if (template.html)
block.html = template.html
}
return block
} catch (error) {
console.log(error)
throw error
}
}
//if no file is found
throw new Error('Invalid block script ' + script)
/**
* Call afterInit callback and load child block
*
* @param {*} block
*/
async _afterInitBlock(block) {
if (!block)
return null
const childrenBlock = block.getChildrenBlock()
//load children block
if (childrenBlock && childrenBlock.length) {
await this._loadBlocks(childrenBlock, block.name)
}
//after init children hook
await block.afterInitChildren()
}
/**
* load a template file and return the html and the Block class
*
* @param {string} template template path
*
* @return {Array}
* @private
* Put the html and the script in a cache file
* @param {*} template
* @param {*} content
*/
_loadTemplate (template) {
var html = ''
var BlockClass = null
//get the template content
var templateContent = this.getTemplateContent(template)
async _cacheTemplate (template, content) {
if (!content)
return null
const promises = []
if (content.script) {
promises.push(this._cacheFile(template + '.js', content.script))
}
if (content.html) {
promises.push(this._cacheFile(template + '.html', content.html))
}
await Promise.all(promises)
}
/**
* Open a template file
* search for template and script tags
*
* return and object with html and scipt
*
* @param {String} template tamplate path
*
* @returns {Object}
*/
async _loadFileTemplate(templateContent) {
let html = ''
let script = null
if (!templateContent)
return null
//search for the <template> part
var templateTags = templateContent.match(/<template>([\s\S]*?)<\/template>/g)
const templateTags = templateContent.match(/<template>([\s\S]*?)<\/template>/g)
//if there are a <template> tag

@@ -239,125 +491,222 @@ if (templateTags && templateTags[0]) {

html = templateTags[0].replace(/<\/?template>/g, '')
//remove the <template> part of the template content
var scriptContent = templateContent.substring(templateTags[0].length)
templateContent = templateContent.substring(templateTags[0].length)
//search for a <script> part
var scriptTags = scriptContent.match(/<script>([\s\S]*?)<\/script>/g)
const scriptTags = templateContent.match(/<script(?:[\s\S]*?)>([\s\S]*?)<\/script>/g)
if (scriptTags && scriptTags[0]) {
//get the script class and eval it
var script = scriptTags[0].replace(/<\/?script>/g, '')
BlockClass = eval(script)
script = scriptTags[0].replace(/<\/?script(?:[\s\S]*?)>/g, '')
}
}
//if no template tag and no script tag
if (!html && !script) {
return {html: templateContent}
} else {
//if there a no <template> part get all the content
html = templateContent
return {html, script}
}
return [html, BlockClass]
}
/**
* Create a Block instance
* load a template file and return the html and the Block class
*
* @param {Object} options block params
* @param {string} template template path
*
* @return Block
* @return {Array}
* @private
*/
async createBlock (options) {
var html = ''
var BlockClass = null
async _loadTemplate(template) {
let Block = null;
if (this._options.mode == 'tmpfile') {
//if use cache load template from cache if it cached
Block = await this._loadTmpBlock(template)
}
//if the block have a templete
if (options.template) {
var tpl = this._loadTemplate(options.template)
html = tpl[0]
BlockClass = tpl[1]
let html = null
if (this._options.cache) {
html = await this._getCacheFile(template)
}
if (html == null || Block === null) {
if (!BlockClass) {
//if the template contain no block class try to use the script
if (options.script) {
BlockClass = this._loadBlockClass(options.script)
} else {
//if no block class found use a Block class
BlockClass = Block
//get the template content
let templateContent = await this.getTemplateContent(template)
const content = await this._loadFileTemplate(templateContent)
html = content.html
if (Block !== null) {
content.script = Block
} else if (content.script) {
content.script = await this._createTmpBlock(template, templateContent, content.script)
}
if (this._options.cache && html !== null)
await this._cacheFile(template, html)
return content
} else {
return {html, script: Block}
}
}
//create the instance
var block = new BlockClass(options.name, html, options.config || {}, options.parent, this)
await block.init()
/**
* Create the js file in the temp folder
*
* @param {*} filePath
* @param {*} templateContent
* @param {*} content
* @private
*/
async _createTmpBlock(filePath, templateContent, content) {
//get a hash of the file path
const hash = stringHash(filePath)
//temp file path
const newFilePath = path.join(this._options.tmpDir, hash.toString() + '.js')
// load blocks
if (block.blocks) {
await this._loadBlocks(block.blocks, block.name)
//if source map is enable generate it and update content to load sourcemap
if (this._options.sourceMap) {
let sourcemap = await this._getSourceMap(templateContent, content, filePath, newFilePath)
await utils.asyncWriteFile(newFilePath + '.map', sourcemap)
// s = Buffer.from(s).toString('base64')
content = "require('source-map-support').install({environment: 'node', hookRequire: true});" + content +"\n//# sourceMappingURL=" + newFilePath + '.map' //data:application/json;" + s
}
return block
let Block = null
if (this._options.mode == 'tmpfile') {
//create tmp file
await utils.asyncWriteFile(newFilePath, content)
try {
Block = require(newFilePath)
} catch (error) {
console.log(error)
}
} else if (this._options.mode == 'eval') {
try {
const nodeEval = require('node-eval');
Block = nodeEval(content, filePath)
} catch (error) {
console.log(error)
}
}
return Block
}
/**
* Return a Block instance by name
*
* @param name
*
* @returns Block
* Create a source map file for the block scripts
*
* @param {string} templateContent Original template content
* @param {string} content Script tag content
* @param {string} filePath Template file path
* @param {string} newFilePath Temp script file path
* @private
*/
getBlock (name) {
if (this.blocks[name] != undefined) {
return this.blocks[name]
} else {
throw new Error('The block ' + name + ' doesn\'t exist')
}
async _getSourceMap(templateContent, content, filePath, newFilePath) {
//get start js part index
const index = templateContent.indexOf(content)
//get non js part
const tempString = templateContent.substring(0, index)
const lineNumber = tempString.split('\n').length
const esprima = require('esprima');
const SourceMapGenerator = require('source-map').SourceMapGenerator;
const map = new SourceMapGenerator({
file: filePath
})
const tokens = esprima.tokenize(content, { loc: true })
let offset = lineNumber - 1
tokens.forEach(function(token) {
const loc = token.loc.start
map.addMapping({
source: filePath,
//add lineNumber offset
original: {line: loc.line + offset, column: loc.column},
generated: {line: loc.line, column: loc.column}
})
})
return map.toString()
}
/**
* Exec the actions of a layout config
* Try to require a temp block module
*
* read the config object and exec actions
*
* @param config
*
* @param {string} filePath Original file path
* @private
*/
_processActions (config) {
for (var name in this.blocks) {
var block = this.blocks[name]
if (config[block.name] != undefined) {
this._execActions(block.name, config[block.name])
async _loadTmpBlock(filePath) {
//get a hash of the file path
const hash = stringHash(filePath)
let Block = null
try {
Block = require(path.join(this._options.tmpDir, hash + '.js'))
} catch (error) {
//Prevent error if the file not exists
if(error.code != 'MODULE_NOT_FOUND') {
throw error
}
}
return Block
}
/**
* Exec the actions of a block
*
* @param {*} filename
*/
async _getCacheFile(filename) {
return this._cache.get(this._cacheFilePrefix + filename)
}
/**
*
* @param {*} filename
* @param {*} content
*/
async _cacheFile(filename, content) {
return this._cache.set(this._cacheFilePrefix + filename, content)
}
/**
* Create a Block instance
*
* @param blockName
* @param actions
* @param {Object} options block params
*
* @private
* @return Block
*/
_execActions (blockName, actions) {
//get the block instance
var block = this.getBlock(blockName)
for (var key in actions) {
var action = actions[key]
async createBlock(options) {
//if the block have a templete
if (options.template) {
const template = await this._loadTemplate(options.template)
return this._loadBlockClass(options, template)
}
//if the action have no method
if (!action.method) {
throw new Error('Invalid action in the block ' + blockName)
}
return this._loadBlockClass(options)
}
//check of the block have the method defined
if (block[action.method] == undefined) {
throw new Error(
'The block ' + blockName + ' (' + block.script + ') have no method ' + action.method)
}
//call the method
if (action.args != undefined) {
block[action.method].apply(block, action.args)
} else {
block[action.method]()
}
/**
* Return a Block instance by name
*
* @param {Sting} name block name
*
* @returns Block
*/
getBlock(name) {
if (this.blocks[name] != undefined) {
return this.blocks[name]
} else {
throw new Error('The block ' + name + ' doesn\'t exist')
}

@@ -374,23 +723,32 @@ }

*/
async renderBlocks (parent) {
//promises array
var renders = []
async renderBlocks(parent) {
const renders = []
for (let key in this._blocks[parent]) {
const block = this._blocks[parent][key]
renders[block.name] = block.render().catch(async (error) => {
this.emit('error', error, { block: block.name })
//return { key: block.name, value: ''} //continue other blocks
})
}
return utils.objectPromises(renders)
}
//Add the render proomise for each child blocks
for (var key in this._blocks[parent])
renders.push(this._blocks[parent][key].render())
/**
* Return the block html
*
* @param {Sting} name block name
*
* @returns {String}
*/
async getBlockHtml(name) {
try {
const block = this.getBlock(name)
return block.render().catch(async (error) => {
this.emit('error', error, { block: block.name })
//return { key: block.name, value: ''} //continue other blocks
})
} catch (error) {
//wait the result
var result = await Promise.all(renders)
var blocks = {}
var i = 0
//add the html in an object with the block name for key
for (var key in this._blocks[parent]) {
var block = this._blocks[parent][key]
blocks[block.name] = result[i]
i++
}
return blocks
}

@@ -406,9 +764,11 @@

*/
renderHtml (html, data) {
async renderHtml(html, data) {
try {
return twig.twig({data: html}).render(data)
} catch (e) {
console.log(e)
console.log(html)
return ''
return twig.twig({
data: html,
rethrow: true,
allow_async: true
}).renderAsync(data)
} catch (error) {
this.emit('error', error, { html: html })
}

@@ -425,9 +785,10 @@ }

*/
renderFile (file, data) {
try {
//get the template content and render it
return this.renderHtml(this.getTemplateContent(file), data)
} catch (e) {
throw new Error('Error in template ' + file)
async renderFile(file, data) {
const html = await this.getTemplateContent(file)
if (!html) {
return ''
}
//get the template content and render it
return this.renderHtml(html, data)
}

@@ -443,4 +804,10 @@

*/
getTemplateContent (file) {
return fs.readFileSync(path.join(this.options.views, file), 'utf8')
async getTemplateContent(file) {
const filePath = path.join(this._options.views, file);
const exists = await utils.asyncFileExists(filePath)
if (exists) {
return promisify(fs.readFile)(filePath, 'utf8')
} else {
throw new Error ('file not found: ' + file)
}
}

@@ -453,5 +820,22 @@

*/
render () {
if (!this.isLoad) throw new Error ('Layout is not loaded')
return this._pageBlock.render()
async render() {
//call before render hook
await utils.asyncMap(this.blocks, async block => {
if (block.beforeRender) {
const isCached = await block.isCached()
if (!isCached) {
try {
await block.beforeRender()
} catch (error) {
this.emit('error', error, { block: block.name })
}
}
}
})
if (this.renderPage) {
return this._pageBlock.render()
} else {
return this._templateBlock.render()
}
}

@@ -458,0 +842,0 @@ }

{
"name": "twig-layout",
"description": "A layout system",
"author": {

@@ -8,7 +6,20 @@ "name": "Metais Fabien",

},
"version": "0.1.2",
"repository": {
"type": "git",
"url": "https://github.com/metaisfabien/node-twig-layout.git"
"bugs": {
"url": "https://github.com/metaisfabien/node-twig-layout/issues"
},
"contributors": [],
"dependencies": {
"@midgar/utils": "^1.0.0-alpha.1.2",
"node-eval": "^2.0.0",
"string-hash": "^1.1.3",
"twig": "^1.13.2"
},
"description": "A layout system build on top of twig js",
"devDependencies": {
"ink-docstrap": "^1.3.2"
},
"engines": {
"node": ">= 0.8.0"
},
"homepage": "https://github.com/metaisfabien/node-twig-layout#readme",
"keywords": [

@@ -23,14 +34,14 @@ "twig",

],
"license": "MIT",
"main": "layout.js",
"contributors": [],
"maintainers": [],
"license": "MIT",
"dependencies": {
"twig": "^1.12.0"
"name": "twig-layout",
"repository": {
"type": "git",
"url": "git+https://github.com/metaisfabien/node-twig-layout.git"
},
"devDependencies": {},
"engines": {
"node": ">= 0.8.0"
"scripts": {
"build-docs": "jsdoc -c ./jsdoc.js -t ./node_modules/ink-docstrap/template -r ."
},
"scripts": {}
"version": "0.1.3"
}
## twig-layout
! In Dev dont use this !
twig-layout is a layout system based on twig.

@@ -25,19 +27,20 @@

```js
var express = require('express')
var layout = require('express-twig-layout')
var app = express()
const express = require('express')
const layout = require('express-twig-layout')
const app = express()
//set the views directory
app.set('view', './views')
app.use(layout())
app.use(layout({
extendFilter: {},
extendFunction:{}
}))
//home route
app.get('/home', function (req, res) {
app.get('/home', async (req, res) => {
//load the layout from the file home.html
req.layout.loadTemplate('home.html').then(() => {
await req.layout.loadTemplate('home.html')
//send the layout html
req.layout.render().then((html) => {
res.send(html)
})
})
const html = await req.layout.render()
res.send(html)
})

@@ -48,10 +51,8 @@

//load the layout from the file test.html
req.layout.loadTemplate('test.html').then(() => {
//Set the title of the block head
req.layout.getBlock('head').data.title = 'Test page'
//send the layout html
req.layout.render().then((html) => {
res.send(html)
})
})
await req.layout.loadTemplate('test.html')
//Set the title of the block head
req.layout.getBlock('head').data.title = 'Test page'
//send the layout html
const html = await req.layout.render()
res.send(html)
})

@@ -76,3 +77,3 @@

<!-- block head -->
{{blocks.head}}
{{ getBlockHtml('head') }}
<body>

@@ -93,4 +94,6 @@

<main role="main">
<!-- block content -->
{{blocks.content}}
<h1>{{ this.getSomething() }}</h1>
<!-- block content -->
{{ getBlockHtml('content') }}
<!-- /block content -->
</main>

@@ -106,7 +109,7 @@

//Require the block dependency
var Block = require('node-twig-layout/block')
const Block = require('node-twig-layout/block')
//Block for the page
class Default extends Block {
init () {
async init () {
//set the name of the block

@@ -121,9 +124,16 @@ //the name of the block can be define in this way or for other block it can be defined in the config

//to use block with no html temple use type
this.addBlock({name: 'content', script: 'container'})
this.addBlock({name: 'content', script: 'twig-layout/scripts/container'})
}
/**
* A method called in the template
*/
async getSomething() {
return 'something';
}
/**
* before render callback
*/
beforeRender () {
async beforeRender () {
//Add a css file

@@ -160,3 +170,3 @@ this.layout.getBlock('head').addCss('https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css', -10)

//requite the block object
var Block = require('node-twig-layout/block')
const Block = require('node-twig-layout/block')

@@ -168,3 +178,3 @@ //A Test class for the test page

*/
init() {
async init() {
//unsorted array

@@ -180,5 +190,5 @@ this._css = []

//add css files
addCss (cssFiles, weight = 0) {
async addCss (cssFiles, weight = 0) {
if (Array.isArray(cssFiles)) {
for (var key in cssFiles) {
for (let key in cssFiles) {
this._css.push({weight: weight, file: cssFiles[key]})

@@ -194,5 +204,5 @@ }

//add js files to the data object
addJs (jsFiles) {
async addJs (jsFiles) {
if (Array.isArray(jsFiles)) {
for (var key in jsFiles) {
for (let key in jsFiles) {
this._js.push({weight: weight, file: jsFiles[key]})

@@ -210,4 +220,4 @@ }

*/
beforeRender() {
var sort = function(a, b) {
async beforeRender() {
const sort = function(a, b) {
return a.weight - b.weight

@@ -239,7 +249,7 @@ }

//requite the block object
var Block = require('node-twig-layout/block')
const Block = require('node-twig-layout/block')
//A Block class for the home page
class Home extends Block {
init () {
async init () {
this.page ='page/default.html'

@@ -269,3 +279,3 @@ //name of the parent block of this block

//requite the block object
var Block = require('node-twig-layout/block')
const Block = require('node-twig-layout/block')

@@ -272,0 +282,0 @@ //A Test class for the test page

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc