{"id":7404,"date":"2019-09-22T09:00:35","date_gmt":"2019-09-22T09:00:35","guid":{"rendered":"http:\/\/putridparrot.com\/blog\/?p=7404"},"modified":"2019-09-22T09:00:35","modified_gmt":"2019-09-22T09:00:35","slug":"javascript-tagged-templates","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/javascript-tagged-templates\/","title":{"rendered":"JavaScript tagged templates"},"content":{"rendered":"<p>If you&#8217;ve seen code such as the one below (taken from https:\/\/www.styled-components.com\/docs\/basics)<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nstyled.section`\r\n  padding: 4em;\r\n  background: papayawhip;\r\n`\r\n<\/pre>\n<p>You might be interested in the styled.section code. This is a function and uses template literals as input via template literal syntax. In this usage its known as a tagged template or tagged template literals.<\/p>\n<p>Let&#8217;s create our own function to show how this works.<\/p>\n<p>function tt(literals: any, &#8230;substitutions: any) {<br \/>\n  console.log(literals);<br \/>\n  console.log(substitutions);<br \/>\n}<\/p>\n<p><em>Note: I&#8217;m using TypeScript, hence the use of the any keyword, but just remove this for JavaScript code.<\/em><\/p>\n<p>If we now run the following code<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst name1 = &quot;Scooby&quot;;\r\nconst name2 = &quot;Doo&quot;;\r\n\r\ntt`Hello ${name1} World ${name2}`\r\n<\/pre>\n<p>The following will be logged to the console<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n&#x5B; 'Hello ', ' World ', '' ]\r\n&#x5B; 'Scooby', 'Doo' ]\r\n<\/pre>\n<p>The first values are an array of the <em>literals<\/em> passed to our function, the second are the <em>substitutions<\/em>. <\/p>\n<p>Literals will always be an array of substitutions.length + 1 in length. Hence in the example above the literals contains an empty string item at the end to ensure this is the case.<\/p>\n<p><em>Note: The last item in the literals array is an empty string but ofcourse if we had a string after the ${name2} then this would be the last item, hence to combine these two arrays into a resultant string would require us to ensure we merge all items.<\/em><\/p>\n<p>We can therefore combine our two arrays to form a single result using a simple loop, like this<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n\r\nfunction tt(literals: any, ...substitutions: any) {\r\n  let s = &quot;&quot;;\r\n\r\n  for (let i = 0; i &lt; substitutions.length; i++) {\r\n    s += literals&#x5B;i] + substitutions&#x5B;i];\r\n  }\r\n\r\n  return s + literals&#x5B;literals.length - 1];\r\n}\r\n<\/pre>\n<p>In the above we&#8217;re simply returning a string representing the merge of the two arrays. Remember literals.length will be substitutions.length + 1, hence we simply append that after looping through the smaller of the arrays.<\/p>\n<p>Ofcourse this it not really that useful, if all we wanted to do was return a string we could just create a template literal. Let&#8217;s look at a couple of ways of enhancing the functionality.<\/p>\n<p>The first obvious requirement is that we should be able to pass functions into the templates. For example if we have something like this<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst t = tt`\r\n firstName: ${name1};\r\n lastName: ${name2};\r\n preferred: ${choice =&gt; (choice ? name1 : name2)};\r\n `;\r\n<\/pre>\n<p>The <em>choice<\/em> value needs to be supplied by the calling code and in this example code there&#8217;s no easy was to pass this data into <em>t<\/em>. So first off we need to wrap the <em>tt<\/em> function within another function and return it, like this<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\"> \r\nfunction tt(literals: any, ...substitutions: any) {\r\n  return function(options: any) {\r\n    let s = &quot;&quot;;\r\n\r\n    for (let i = 0; i &lt; substitutions.length; i++) {\r\n      s += literals&#x5B;i];\r\n      s += typeof substitutions&#x5B;i] === &quot;function&quot;\r\n        ? substitutions&#x5B;i](options)\r\n        : substitutions&#x5B;i];\r\n    }\r\n    return s + literals&#x5B;literals.length - 1];\r\n  };\r\n}\r\n<\/pre>\n<p>In the above we&#8217;ve also added changes to the original <em>tt<\/em> function to detect functions within the substitutions. If a function is found whilst looping then it&#8217;s invoked by passing in the supplied <em>options<\/em>. <\/p>\n<p>This implementation then returns a function which, when invoked by passing in some value (in this case named options), will loop through the literals and substitutions and invoking any functions by forwarding the supplied options.<\/p>\n<p>Hence we can call the new <em>tt<\/em> method like this, for example<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nt({choice: true});\r\n<\/pre>\n<p>This would return a string and would return <\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nfirstName: Scooby;\r\nlastName: Doo;\r\npreferred: Scooby;\r\n<\/pre>\n<p>So now for the next enhancement, let&#8217;s instead of returning a string, return an object &#8211; all we need to do is split on semi-colons to get key\/value items where the key will become the object&#8217;s property and the value obviously the value stored within the property.<\/p>\n<p>We&#8217;ll make a slight change to the code above to this<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nfunction tt(literals: any, ...substitutions: any) {\r\n  return function(options: any) {\r\n    let s = &quot;&quot;;\r\n\r\n    for (let i = 0; i &lt; substitutions.length; i++) {\r\n      s += literals&#x5B;i];\r\n      s += typeof substitutions&#x5B;i] === &quot;function&quot;\r\n        ? substitutions&#x5B;i](options)\r\n        : substitutions&#x5B;i];\r\n    }\r\n\r\n    return toObject(s + literals&#x5B;literals.length - 1]);\r\n  };\r\n}\r\n<\/pre>\n<p>The toObject function has been introduced and it&#8217;s purpose is to&#8230;<\/p>\n<ul>\n<li>Take a string which is semi-colon deliminated for each key\/value pair<\/li>\n<li>Extract each key\/value pair which should be deliminated with colons<\/li>\n<li>For each entry we will create a property with the name taken from left of the colon on an object and the value right of the colon will be assigned to the property as a value<\/li>\n<\/ul>\n<p>Here&#8217;s the code for toObject<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst toObject = (value: any): any =&gt;\r\n  value\r\n    .split(&quot;;&quot;)\r\n    .map(entry =&gt; {\r\n        const e = entry.split(&quot;:&quot;);\r\n        if(e.length == 2) {\r\n            const key = e&#x5B;0].trim();\r\n            const value = e&#x5B;1].trim();\r\n            return &#x5B;key, value];\r\n        }\r\n        return undefined;\r\n    })\r\n    .filter(entry =&gt; entry != undefined)\r\n    .reduce((obj, entry) =&gt; ({ ...obj, &#x5B;entry&#x5B;0]]: entry&#x5B;1]}), {});\r\n<\/pre>\n<p>This is not a complete solution as we&#8217;re not ensuring validity of the key as a property name. For example you&#8217;ll have noticed in styled.component or even React&#8217;s styles, that hyphen keys, i.e. background-color or similar would be converted or expected to be backgroundColor. So a simply change would be to convert line 7 to this following<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst key = ensureValid(e&#x5B;0].trim());\r\n<\/pre>\n<p>and now we introduce a new function to handle all our checks, for now we&#8217;ll just ensure the hyphen&#8217;s or dot&#8217;s are removed and replaced by camelCase<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst ensureValid = (key: string): string =&gt; \r\n key.replace(\/&#x5B;-.]+\/g, c =&gt; c.length &gt; 0 ? c.substr(1).toUpperCase() : '');\r\n<\/pre>\n<p>Obviously this function is quite limited, but you get the idea. It can then be used in the toObject function, i.e. <\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n\/\/ change\r\nconst key = e&#x5B;0].trim();\r\n\/\/ to\r\nconst key = ensureValid(e&#x5B;0].trim());\r\n<\/pre>\n<p><strong>Taking things a little further<\/strong><\/p>\n<p>The code below is based upon what was discussed in this post, but extended a little, to start with here&#8217;s a more complete implementation of the above code<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst ensureValid = (key: string): string =&gt; \r\n    key.replace( \/&#x5B;-.]+(&#x5B;a-z]|&#x5B;0-9])|&#x5B;-.]$\/ig, (_match, character, pos) =&gt; {\r\n        if(pos == 0) {\r\n            return character.toLowerCase();\r\n        }\r\n        else if(character == null) {\r\n            return '';\r\n        }\r\n\r\n        return character.toUpperCase();\r\n    });\r\n\r\nconst toObject = (value: any): any =&gt;\r\n  value\r\n    .split(&quot;;&quot;)\r\n    .map(entry =&gt; {\r\n        const e = entry.split(&quot;:&quot;);\r\n        if(e.length == 2) {\r\n            const key = ensureValid(e&#x5B;0].trim());\r\n            const value = e&#x5B;1].trim();\r\n            return &#x5B;key, value];\r\n        }\r\n        return undefined;\r\n    })\r\n    .filter(entry =&gt; entry != undefined)\r\n    .reduce((obj, entry) =&gt; ({ ...obj, &#x5B;entry&#x5B;0]]: entry&#x5B;1]}), {});\r\n\r\nfunction tt3(literals: any, ...substitutions: any) {\r\n    return function(options: any) {\r\n        let s = &quot;&quot;;\r\n\r\n        for (let i = 0; i &lt; substitutions.length; i++) {\r\n            s += literals&#x5B;i];\r\n            s += typeof substitutions&#x5B;i] === &quot;function&quot;\r\n                ? substitutions&#x5B;i](options)\r\n                : substitutions&#x5B;i];\r\n        }\r\n\r\n        return toObject(s + literals&#x5B;literals.length - 1]);\r\n    };\r\n}\r\n\r\nconst name1 = &quot;Scooby&quot;;\r\nconst name2 = &quot;Doo&quot;;\r\n\r\nconst t = tt3`\r\n -First-6name-: ${name1};\r\n last-Name: ${name2};\r\n preferred: ${options =&gt; (options.choice ? name1 : name2)};\r\n `;\r\n\r\nconsole.log(t({choice: true}));\r\n<\/pre>\n<p>Now let&#8217;s have a bit of fun and refactor things to allow us to extract our object from alternate data representations. We&#8217;ll create an <em>ini<\/em> style way to define our objects<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\nconst camelCase = (key: string): string =&gt; \r\n    key.replace( \/&#x5B;-.]+(&#x5B;a-z]|&#x5B;0-9])|&#x5B;-.]$\/ig, (_match, character, pos) =&gt; {\r\n        if(pos == 0) {\r\n            return character.toLowerCase();\r\n        }\r\n        else if(character == null) {\r\n            return '';\r\n        }\r\n\r\n        return character.toUpperCase();\r\n    });\r\n\r\ntype splitterFunc = (value: any) =&gt; &#x5B;{key: any; value: any}|undefined];\r\n\r\nconst standardSplitter = (value: any):  &#x5B;{key: any; value: any}|undefined] =&gt;\r\n    value\r\n        .split(&quot;;&quot;)\r\n        .map(entry =&gt; {\r\n            const e = entry.split(&quot;:&quot;);\r\n            if(e.length == 2) {\r\n                const key = camelCase(e&#x5B;0].trim());\r\n                const value = e&#x5B;1].trim();\r\n                return &#x5B;key, value];\r\n            }\r\n            return undefined;\r\n        });\r\n\r\nconst iniSplitter = (value: any):  &#x5B;{key: any; value: any}|undefined] =&gt;\r\n        value\r\n            .split(&quot;\\n&quot;)\r\n            .map(entry =&gt; {\r\n                const e = entry.split(&quot;=&quot;);\r\n                if(e.length == 2) {\r\n                    const key = camelCase(e&#x5B;0].trim());\r\n                    const value = e&#x5B;1].trim();\r\n                    return &#x5B;key, value];\r\n                }\r\n                return undefined;\r\n            });\r\n    \r\n\r\nconst toObject = (value: any, splitter: splitterFunc = standardSplitter): any =&gt;\r\n    splitter(value)\r\n      .filter(entry =&gt; entry != undefined)    \r\n      .reduce((obj, entry) =&gt; ({ ...obj, &#x5B;entry!&#x5B;0]]: entry!&#x5B;1]}), {});\r\n\r\n\r\nfunction tt3(literals: any, ...substitutions: any) {\r\n    return function(options: any, splitter: splitterFunc = standardSplitter) {\r\n        let s = &quot;&quot;;\r\n\r\n        for (let i = 0; i &lt; substitutions.length; i++) {\r\n            s += literals&#x5B;i];\r\n            s += typeof substitutions&#x5B;i] === &quot;function&quot;\r\n                ? substitutions&#x5B;i](options)\r\n                : substitutions&#x5B;i];\r\n        }\r\n\r\n        return toObject(s + literals&#x5B;literals.length - 1], splitter);\r\n    };\r\n}\r\n\r\nconst name1 = &quot;Scooby&quot;;\r\nconst name2 = &quot;Doo&quot;;\r\n    \r\nconst t = tt3`\r\n -First-6name-: ${name1};\r\n last-Name: ${name2};\r\n preferred: ${options =&gt; (options.choice ? name1 : name2)};\r\n `;\r\n\r\n const t1 = tt3`\r\n -First-6name- = ${name1}\r\n last-Name = ${name2}\r\n preferred = ${options =&gt; (options.choice ? name1 : name2)}\r\n `;\r\n\r\nconsole.log(t1({choice: true}, iniSplitter));\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>If you&#8217;ve seen code such as the one below (taken from https:\/\/www.styled-components.com\/docs\/basics) styled.section` padding: 4em; background: papayawhip; ` You might be interested in the styled.section code. This is a function and uses template literals as input via template literal syntax. In this usage its known as a tagged template or tagged template literals. Let&#8217;s create [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[45,46],"tags":[],"class_list":["post-7404","post","type-post","status-publish","format-standard","hentry","category-javascript","category-typescript"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7404","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=7404"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7404\/revisions"}],"predecessor-version":[{"id":7453,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/7404\/revisions\/7453"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=7404"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=7404"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=7404"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}