Using ejs as template-engine within express.js default configuration can be very annoying – you have to pass a dedicated variable set to each response.render() call. But for a lot of tasks it is required to use some kind of global variables in your templates, e.g. page title, resources and much more.

The most reliable solution is a custom template renderer which invokes ejs in the way you want.

Custom Template Engine/Renderer Function#

const _ejs = require('ejs');

// example: global config
const _config = require('../config.json');

// custom ejs render function
module.exports = function render(filename, payload={}, cb){
    // some default page vars
    payload.page = payload.page || {};
    payload.page.slogan = payload.page.slogan || _config.slogan;
    payload.page.title = payload.page.title || _config.title;
    payload.page.brandname = payload.page.brandname || _config.name;

    // resources
    payload.resources = payload.resources || {};

    // render file
    // you can also pass some ejs lowlevel options
    _ejs.renderFile(filename, payload, {
        
    }, cb);
}

Usage#

const _express = require('express');
const _webapp = _express();
const _path = require('path');
const _tplengine = require('./my-template-engine');

// set the view engine to ejs
_webapp.set('views', _path.join(__dirname, '../views'));
_webapp.engine('ejs', _tplengine);
_webapp.set('view engine', 'ejs');

// your controller
_webapp.get('/', function(req, res){
   // render the view using additional variables
   res.render('myview', {
     x: 1,
     y: 2
   });
});

 

Use EnllighterJS with marked

markdown, gfm, javascript, nodejs

marked is one of the most popular markdown parsers written in javascript. It’s quite easy to integrate EnlighterJS within, just pass a custom highlight function as option.

Promised based highlighting#

File: markdown.js

const _marked = require('marked');
const _renderer = new _marked.Renderer();

// escape html specialchars
function escHtml(s){
    return s.replace(/&/g, '&')
            .replace(/"/g, '"')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
}

// EnlighterJS Codeblocks
_renderer.code = function(code, lang){
    return `<pre data-enlighter-language="${lang}">${escHtml(code)}</pre>`;
};

const _options = {
    // gfm style line breaks
    breaks: true,

    // custom renderer
    renderer: _renderer
};

// promise proxy
function render(content){
    return new Promise(function(resolve, reject){
        // async rendering
        _marked(content, _options, function(e, html){
            if (e){
                reject(e);
            }else{
                resolve(html);
            }
        });
    });
}

module.exports = {
    render: render
};

 

Usage#

const _markdown = require('markdown');

// fetch markdown based content
const rawCode = getMarkdownContent(..);

// render content
const html = await _markdown.render(rawCode);

 

Comparing the content of two directories binary-safe is a common used feature especially for data synchronization tasks. You can easily implement a simple compare algorithm by generating the sha256 checksums of each file – this is not a high-performance solution but even works on large files!

const _fs = require('fs-magic');

// compare directoy contents based on sha256 hash tables
async function compareDirectories(dir1, dir2){
    // fetch file lists
    const [files1, dirs1] = await _fs.scandir(dir1, true, true);
    const [files2, dirs2] = await _fs.scandir(dir2, true, true);

    // num files, directories equal ?
    if (files1.length != files2.length){
        throw new Error('The directories containing a different number of files ' + files1.length + '/' + files2.length);
    }
    if (dirs1.length != dirs2.length){
        throw new Error('The directories containing a different number of subdirectories ' + dirs1.length + '/' + dirs2.length);
    }

    // generate file checksums
    const hashes1 = await Promise.all(files1.map(f => _fs.sha256file(f)));
    const hashes2 = await Promise.all(files2.map(f => _fs.sha256file(f)));

    // convert arrays to objects filename=>hash
    const lookup = {};
    for (let i=0;i<hashes2.length;i++){
        // normalized filenames
        const f2 = files2[i].substr(dir2.length);
        
        // assign
        lookup[f2] = hashes2[i];
    }

    // compare dir1 to dir2
    for (let i=0;i<hashes1.length;i++){
        // normalized filenames
        const f1 = files1[i].substr(dir1.length);

        // exists ?
        if (!lookup[f1]){
            throw new Error('File <' + files1[i] + '> does not exist in <' + dir2 + '>');
        }

        // hash valid ?
        if (lookup[f1] !== hashes1[i]){
            throw new Error('File Checksum of <' + files1[i] + '> does not match <' + files2[i] + '>');
        }
    }

    return true;
}

await compareDirectories('/tmp/data0', '/tmp/data1');

 

Node.js: Log static file requests with expressjs serve-static middleware

nodejs, express, static, logfile, analytics, statistics, download counter

In most cases, every web-application requires some kind of request logging. Especially package downloads will be counted for statistic purpose. By using expressjs, static content is served by the middleware module serve-static.

To count the successfull requests handled by this module, you can hook into the setHeaders callback which is invoked each time a file is ready for delivering (file exists, file is accessible).

Example#

// utility
const _path = require('path');

// expressjs
const _express = require('express');
let _webapp = _express();

// your statistic module
const _downloadStats = require('./download-counter');

// serve static package files
_webapp.use('/downloads', _express.static(_path.join(__dirname, 'downloads'), {
    // setHeaders is only called on success (stat available/file found)
    setHeaders: function(res, path, stat){
        // count request: full-path, file-stats, client-ip
        _downloadStats(path, stat, res.req.connection.remoteAddress);
    }
}));

 

Sometimes it can be very useful to have magical constants like __FILENAME__ or __LINE__ available within your sourcecode – especially for debugging or in merged files. Unfortunately, such feature is missing in javascript but it is possible to implement it by yourself using a file-postprocessing filter in your gulp build script. Thanks to gulp-concat-util, it’s […]

Prevent Errors from breaking Gulp watch

gulp-plumber, custom error handler, gulp-prettyerror

As an intermediate javascript developer, you may using gulp these days – a great and straightforward streaming build system with a lot of advantages compared to grunt. For example, i’ve switched from a bunch of custom, ANT based scripts to gulp for the next EnlighterJS major version and it saves a lot of time! Especially […]

Node.js Simple Command Line Confirm Messages

user confirmation, terminal actions, yes, no

Sometime, special terminal commands can be dangerous for your users. To ensure that they are really want to run the command the proven “best practise” is to wait for an explicit user confirmation by typing yes/no into the terminal.

Install “prompt” using NPM#

First of all, you have to install prompt – a powerfull package for command line prompts & user interactions. The “–save” option will add this package to your package.json file.

npm install prompt --save

Confirm Dialog#

After installing the prompt package you can use the following code to show a confirm dialog to your users.

var _prompt = require('prompt');

// user confirmation required!
_prompt.start();

// disable prefix message & colors
_prompt.message = '';
_prompt.delimiter = '';
_prompt.colors = false;

// wait for user confirmation
_prompt.get({
    properties: {
        
        // setup the dialog
        confirm: {
            // allow yes, no, y, n, YES, NO, Y, N as answer
            pattern: /^(yes|no|y|n)$/gi,
            description: 'Do you really want to format the filesystem and delete all file ?',
            message: 'Type yes/no',
            required: true,
            default: 'no'
        }
    }
}, function (err, result){
    // transform to lower case
    var c = result.confirm.toLowerCase();

    // yes or y typed ? otherwise abort
    if (c!='y' && c!='yes'){
        console.log('ABORT');
        return;
    }
    
    // your code
    console.log('Action confirmed');
    
});

 

Create Static Social-Media “Share” Buttons without Javascript

no external resources required, corporate privacy compliance

Social-Media Share Buttons are everywhere, but most of them are working with external hosted javascript resources which are loaded on each page request. Depending on the servers cache settings this can cause multiple additional http request for your visitors. Additionally it can raise some privacy issues becaue of the possibility that your users can be […]

MooTools: A modern Element.highlight() implementation

using CSS3 Transisitons instead of Fx.Tween

Use the following code as Element.highlight() replacement:

Element.implement({
    /**
     * Custom Element Highlighting Function
     * @param color
     */
    highlight: function(color){
        // get current background color
        var originalColor = this.getStyle('background-color');

        // set new background color
        this.setStyle('background-color', color);

        // restore background color after 300ms
        (function(){
            this.setStyle('background-color', originalColor);
        }).delay(300, this);
    }
});

It will only change the background color to the given value and reverse this after a time of 300ms. To get a fading-effect you need to add the following css, matching the elements you wish to apply the highlight() method:

.autocomplete-input{
    transition: background-color 200ms;
}

That’s it ;)

Are you using a Yubikey and want to create your custom Keyserver written in Node.js ? In this case this piece of code might be useful :)

/**
 * Convert the Yubico MODHEX encoded Strings to hex
 * @param modhex String
 * @returns hex String
 */
var modhex2hex = function(modhex){
    // strip whitespaces and string cleanup - all non matching characters are 0x00 (c in modhex)
    modhex = modhex.replace(/\s*/g, '').replace(/[^cbdefghijklnrtuv]/g, 'c');

    // even length ?
    if (modhex.length%2 !== 0){
        return null;
    }

    // modhex mapping base; c.....v => 0x0 ... 0xF
    var modhexBase = 'cbdefghijklnrtuv'.split('');

    // tmp
    var output = '';

    // convert
    for (var i=0;i<modhex.length;i++){
        // convert index to hex
        output += modhexBase.indexOf(modhex.charAt(i)).toString(16);
    }

    return output;
};

console.log(modhex2hex('te vt hh fg ue dk gv rt lv hb lu gf nk ge ng cv'));