Skip to content

Clean, easily readable conditional statements in template literals/strings for Node.js and browsers. Provides if / elseif / else and switch / case / default syntax options.

License

Notifications You must be signed in to change notification settings

VividVisions/conditional-tag

Repository files navigation

conditional-tag

Clean, easily readable conditional statements in template literals/strings for Node.js and browsers. Provides if / elseif / else and switch / case / default syntax options.

const html = _`<ul>
  ${_if(aVariable === 1)}
  <li>First</li>
  <li>Second</li>
  ${_elseif(aVariable === 2)}
  <li>Third</li>
  <li>Fourth</li>
  ${_else}
  <li>Fifth</li>
  ${_endif}
  <li>Last</li>
 </ul>`;

Table of Contents

Why?

Simple conditional statements in template literals are no problem:

const str = `This is a sentence ${(true) ? 'with' : 'without'} a conditional statement.`;

Things can get messy and hard to read/maintain when you're working with multi-line strings, longer conditional strings and/or more possible conditions and therefore nested literals:

const c = 2;
const str = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. ${(c === 1) ? `Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. ` : (c > 2) ? `Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur.` : `Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`} Alea iacta est.`;

This is where one would likely implement other solutions like splitting up the string or writing a function to handle all cases. But this would render the template literal obsolete or would take parts of the string creation to another place. This library tries to solve these problems:

const str = _`Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. ${_if(c === 1)}Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. ${_elseif(c > 2)}Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur.${_else}Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.${_endif} Alea iacta est.`;

ES module

conditional-tag has been written as an ECMAScript module and all examples will use ES module syntax. If you want to use conditional-tag within a CommonJS application, use dynamic import() instead of require().

Installation

Node.js

npm install conditional-tag

Browser

You'll find a browser-ready, minimized file in the browser directory: conditional-tag.min.js.

<script type="module">
  import { _, _if, _else, _endif } from './conditional-tag.min.js';
  
  console.log(_`This is working in the browser too! ${_if(true)}Yay${_else}Boo${_endif}!`);
</script>

Usage

Tag function _()

conditional-tag provides the tag function _(). If a template string is tagged with this function, all conditional-tag expressions described further below can be used within the string.

import { _ } from 'conditional-tag';

const str = _`This string is tagged and ready for conditional-tag expressions.`;

⚠️ If you forget to tag the template string, the conditional logic will not work. You may see "[object Object]" in your string as a result. A TypeError will likely get thrown ("Cannot convert a Symbol value to a string").

if syntax

conditional-tag provides the following expressions for conditional blocks using if syntax.

_if(condition)

Opens an if-block and evaluates the passed condition. If it produces a truthy result, the strings and (non-if-block) expressions following the ${_if(…)} expression will be rendered until another if-block expression or the end of the template string is encountered.

_elseif(condition)

Evaluates the passed condition if no preceding condition has been met. If the condition produces a truthy result, the strings and (non-if-block) expressions following the ${_elseif(…)} expression will be rendered until another if-block expression or the end of the template string is encountered.

_else

If no preceding condition has been met, the strings and (non-if-block) expressions following the ${_else} expression will be rendered until an ${_endif} expression or the end of the template string is encountered.

_endif

Closes the if-block. Can be omitted if ${_endif} would be at the very end of the template string but it is not recommended, especially with nested conditional-tag blocks.

Note: While _if() and _elseif() are functions, _else and _endif are variables.

A ConditionalTagSyntaxError is thrown when there's something wrong with the syntax, like using _elseif(…) after _else.

Examples

import { _, _if, _elseif, _else, _endif } from 'conditional-tag';

let x = 1;
let str = _`X is ${_if(x === 1)}one${_elseif(x === 2)}two${_else}neither one nor two${_endif}.`;
// Result: 'X is one.'

x = 2;
str = _`X is ${_if(x === 1)}one${_elseif(x === 2)}two${_else}neither one nor two${_endif}.`;
// Result: 'X is two.'

x = 3;
str = _`X is ${_if(x === 1)}one${_elseif(x === 2)}two${_else}neither one nor two${_endif}.`;
// Result: 'X is neither one nor two.'

switch syntax

conditional-tag provides the following expressions for conditional blocks using switch syntax.

⚠️ Attention: Unlike the actual switch(…) statement in JavaScript, there is no fall-through and no break. Each _case(…) will be evaluated independently. However, _case(…) can be passed multiple values, all of which will be compared to the switch-value. The condition is met, when at least one value is equal to it.
Another difference is that _default must come after the _case() expressions.

_switch(value)

Opens a switch-block. Stores the value for further comparison. Does not affect the directly following strings and (non-switch-block) expressions.

_case(value [, value, …])

Tests the passed value(s) and the one stored in ${_switch(…)} for strict equality. If at least one of the values are equal, the strings and (non-switch-block) expressions following the ${_case(…)} expression will be rendered until another switch-block expression or the end of the template string is encountered.

If there's no string or expression between ${_switch(…)} and ${_case(…)}, they can be chained together as one expression: ${_switch(…)._case(…)}.

_default

If no preceding ${_case(…)} expression had values equal to the switch, the strings and (non-switch-block) expressions following the ${_default} expression will be rendered until an ${_endswitch} expression or the end of the template string is encountered.

_endswitch

Closes the switch-block. Can be omitted if ${_endswitch} would be at the very end of the template string but it is not recommended, especially with nested conditional-tag blocks.

Note: While _switch() and _case() are functions, _default and _endswitch are variables.

A ConditionalTagSyntaxError is thrown when there's something wrong with the syntax, like using _case([…]) without a preceding _switch([…]).

Examples

import { _, _switch, _case, _default, _endswitch } from 'conditional-tag';

let x = 1;
let str = _`${_switch(x)}X is ${_case(1)}one${_case(2)}two${_default}neither one nor two${_endswitch}.`;
// Result: 'X is one.

// _case() with multiple values:
x = 1;
str = _`When x is 1, 
${_switch(x)._case(1)}this will be shown. 
${_case(1, 2)}This will be shown when x is 1 or 2.
${_case(3)}Shown when x is 3.
${_default}Shown when x is anything but 1, 2 or 3.`;
// Result:
// 'When x is 1,
// this will be shown.
// This will be shown, when x is 1 or 2.'

_always expression

conditional-tag also provides a special _always expression. Strings and (non-conditional-tag) expressions following ${_always} will always be rendered until a conditional-tag expression or the end of the template string is encountered, even if they are within a block which would otherwise stay unrendered (no matter the nesting depth).

import { _, _switch, _case, _default, _endswitch, _always } from 'conditional-tag';

let x = 1;
let str = _`${_switch(x)}X is ${_case(1)}one${_case(2)}two${_always} (This will be rendered anyway!)${_default}neither one nor two${_endswitch}.`;
// Result: 'X is one (This will be rendered anyway!).' 

Other expression functions

Naturally, you can use non-condtional-tag expressions in tagged template literals.
⚠️ BUT: If you call a function in an expression, the call is triggered regardless of being in a rendered or unerendered conditional-tag block:

import { _, _if } from 'conditional-tag';

function test() {
  return 'called';
}

const str = _`${_if(false)} not rendered ${test()}`;
// Result: '' but test() has been called despite being in an unrendered block.

Prevent unnecessary function calls

To prevent this from happening, just wrap your function call in an arrow function. The tag function will recognize them and only trigger a call in rendered blocks.

import { _, _if } from 'conditional-tag';

function test() {
  return 'called';
}

const str = _`${_if(false)} not rendered ${() => test()}`;
// Result: '' and test() will NOT have been called.

This may make the template literal a bit harder to read but it saves precious CPU time.

Asynchronous functions

If you have to call asynchronous functions in an expression, use the special _async() tag function. It works exactly as the _() tag function but is able to handle Promises. There's no need to change the wrapping arrow function.

import { _async, _if } from 'conditional-tag';

function asyncTest() {
  return Promise.resolve('called');
}

const str = await _async`${_if(true)}${() => asyncTest()}`;
// Result: 'called'

Note: If you use the synchronous tag function _() and call asynchronous functions in expressions, you will get errors.

Nesting

Both, if-blocks and switch-blocks can be nested in other if- or switch-blocks:

import { _, _if, _else, _endif, _switch, _case, _endswitch } from 'conditional-tag';

const x = true;
const y = false;

const str =  _`
${_switch(x)._case(true)}
x is true.
${_if(y === true)}
y is true as well.
${_else}
y is NOT true as well.
${_endif}
${_case(false)}
x is false.
${_endswitch}
`;
// Result: 
// 'x is true.
// y is NOT true as well.'

Be mindful of your ${_endif} and ${_endswitch} expressions because forgetting or misplacing them will lead to confusing results and/or errors!

Whitespace/line trimming

Conditional-tag expressions don't add any whitespace to the resulting string. Furthermore, if a conditional-tag expression is the only expression in a line of a multi-line template string and is only - if at all - surrounded by whitespace characters, the whole line gets trimmed.

import { _, _switch, _case, _default, _endswitch, _always } from 'conditional-tag';

const x = 1;
const html = _`${_switch(x)}
<ul>
  <li>First item</li>
  <li>Second item</li>
  ${_case(1)}
  <li>Third item</li>
  ${_case(2)}
  <li>Forth item</li>
  ${_case(3)}
  <li>Fifth item</li>
  <li>Sixth item</li>
  ${_always}
</ul>
${_endswitch}`;

/*
Result:
`<ul>
  <li>First item</li>
  <li>Second item</li>
  <li>Third item</li>
</ul>`
*/

Importing convenience

All conditional-tag expressions are also available through the tag function itself (without the leading underscore), so they don't have to be imported individually. This could however throw off syntax highlighting in your editor of choice (and also be considered a tad less readable).

import { _ } from 'conditional-tag';

let x = 2;
str = _`X is ${_.if(x === 1)}one${_.elseif(x === 2)}two${_.else}neither one nor two${_.endif}.`;
// Result: 'X is two.'

License

conditional-tag is © 2024 Walter Krivanek and released under the MIT license.

About

Clean, easily readable conditional statements in template literals/strings for Node.js and browsers. Provides if / elseif / else and switch / case / default syntax options.

Resources

License

Stars

Watchers

Forks

Packages

No packages published