Created
November 26, 2019 17:10
-
-
Save romellem/a49a09a6775726eb48e160d95ff77674 to your computer and use it in GitHub Desktop.
expandAttributes Handlebars (HBS) Helper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* List of valid attributes that can live on HTML elements. This can be trimmed down however you like. | |
* @note I've remove `class` and `data-*` since class is usually defined elsewhere, and `data-*` uses different logic to filter them in. | |
* | |
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#Attribute_list | |
*/ | |
const attribute_white_list = ['accept', 'accept-charset', 'accesskey', 'action', 'align', 'allow', 'alt', 'async', 'autocapitalize', 'autocomplete', 'autofocus', 'autoplay', 'background', 'bgcolor', 'border', 'buffered', 'challenge', 'charset', 'checked', 'cite', /* 'class', */ 'code', 'codebase', 'color', 'cols', 'colspan', 'content', 'contenteditable', 'contextmenu', 'controls', 'coords', 'crossorigin', 'csp', 'data', /* 'data-*', */ 'datetime', 'decoding', 'default', 'defer', 'dir', 'dirname', 'disabled', 'download', 'draggable', 'dropzone', 'enctype', 'enterkeyhint', 'for', 'form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'http-equiv', 'icon', 'id', 'importance', 'integrity', 'intrinsicsize', 'inputmode', 'ismap', 'itemprop', 'keytype', 'kind', 'label', 'lang', 'language', 'loading', 'list', 'loop', 'low', 'manifest', 'max', 'maxlength', 'minlength', 'media', 'method', 'min', 'multiple', 'muted', 'name', 'novalidate', 'open', 'optimum', 'pattern', 'ping', 'placeholder', 'poster', 'preload', 'radiogroup', 'readonly', 'referrerpolicy', 'rel', 'required', 'reversed', 'rows', 'rowspan', 'sandbox', 'scope', 'scoped', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'spellcheck', 'src', 'srcdoc', 'srclang', 'srcset', 'start', 'step', 'style', 'summary', 'tabindex', 'target', 'title', 'translate', 'type', 'usemap', 'value', 'width', 'wrap']; | |
const attribute_lookup_white_list = attribute_white_list.reduce((obj, key) => { | |
obj[key] = true; | |
return obj; | |
}, {}); | |
/** | |
* General function to allow partials and partial-blocks to take in any | |
* HTML attributes and then output them like we have in HTML. | |
* | |
* So say our template calls a partial block `linkPartial` like this: | |
* | |
* {{> linkPartial href="my/file.pdf" data-tracking="tag" data-js-attr=null icon="icon.png" title="<b>Download</b> the PDF" pruneAttributes="icon,title"}} | |
* | |
* And our `linkPartial`'s source looks like this: | |
* | |
* {{! linkPartial.hbs }} | |
* <a class="special-link" {{{expandAttributes this pruneAttributes}}}> | |
* <img src="{{icon}}"> | |
* {{{title}}} | |
* </a> | |
* | |
* Then when we render the template, our resulting HTML will look like | |
* | |
* <a class="special-link" href="my/file.pdf" data-tracking="tag" data-js-attr> | |
* <img src="icon.png"> | |
* <b>Download</b> the PDF | |
* </a> | |
* | |
* Note that through the use of `pruneAttributes`, we can select attributes | |
* that are getting passed in via context (or otherwise) that _should not_ | |
* be rendered as an attribute, but will be used in some other way (in this | |
* case, `icon` and `title`). | |
* | |
* This does not have to be a hash param, but they can be passed inline. Using the | |
* variable here is for illustrative purposes. | |
* | |
* @note should be called with triple curlies, otherwise the content gets escaped. | |
* | |
* @param {Object} attributes - Object with `keys` being the attribute name, and its `value` being the value to render. | |
* @param {String} prune - String of **comma-separated** attributes that we _don't_ want to render. | |
* When calling `expandAttributes` within our partial, it not only | |
* takes in the hash params, but also the full context. So if we have a | |
* variable named `title`, then if we don't include in this `prune` list, | |
* it'll get rendered as an attribute. | |
* @returns {String} | |
*/ | |
const expandAttributes = (attributes, prune = '') => { | |
// Verify that `attributes` is an object | |
if (!(attributes && attributes.constructor === Object)) { | |
return ''; | |
} | |
let attribute_keys = Object.keys(attributes); | |
let prune_list = String(prune).split(','); | |
let prune_list_lookup = {}; | |
prune_list.forEach(prune_attr => (prune_list_lookup[prune_attr] = true)); | |
let attributes_output = []; | |
attribute_keys.forEach(attribute => { | |
let is_valid_attribute = attribute_lookup_white_list[attribute]; | |
let is_data_attribute = attribute.indexOf('data-') === 0; | |
let should_be_pruned = prune_list_lookup[attribute]; | |
if ((is_valid_attribute || is_data_attribute) && !should_be_pruned) { | |
/** | |
* If our value is `null` or `undefined`, then output the attribute without an equals sign. | |
* Otherwise, use the equals and a quoted attribute value. | |
*/ | |
let output = attributes[attribute] == null ? attribute : `${attribute}="${attributes[attribute]}"`; | |
attributes_output.push(output); | |
} | |
}); | |
return attributes_output.join(' '); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Link to working Handlebars Example.