Skip to content

Instantly share code, notes, and snippets.

@wangziling
Created February 23, 2023 05:20
Show Gist options
  • Save wangziling/1dcff3275dcebb377f6bd0d27f6aec0a to your computer and use it in GitHub Desktop.
Save wangziling/1dcff3275dcebb377f6bd0d27f6aec0a to your computer and use it in GitHub Desktop.
Vue2 v-html directive. Supports HTML contents.
import { DirectiveOptions } from 'vue';
import _ from 'lodash';
import $ from 'jquery';
const _toString = Object.prototype.toString;
/**
* Copied from vue2 source code.
* @param val {*}
* @return {string}
*/
function vToString (val: any) {
return val == null
? ''
: Array.isArray(val) || (_.isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val);
}
/**
* Update the inner HTML content.
* @param el {Element}
* @param binding {*}
*/
const updateInnerHTMLContent: DirectiveOptions['inserted'] = function (el, binding) {
if (!el) {
return;
}
const isElMountedOnOwnerDoc = $.contains(el.ownerDocument.documentElement, el);
const $el = $(el);
// Refer to `v-html`.
const bindingValue = vToString(binding.value);
// Remove old children, make sure that old children will not be remained unexpectedly.
$el
.empty()
.append(bindingValue);
// If the element hadn't been mounted yet.
// The JQuery will not execute the inner scripts.
if (!isElMountedOnOwnerDoc) {
// We create a fake style element and prepend to the el.
const fakeStyleEle = document.createElement('style');
fakeStyleEle.innerHTML = '.__fake_style__ { color: inherit; }';
function refreshDOMContent () {
// OK. do it again.
$el
.empty()
.append(bindingValue);
}
// If IE. browser.
if (typeof el.ownerDocument.contains !== 'function') {
/** @see https://developer.mozilla.org/en-US/docs/Web/API/MutationEvent **/
// Deprecate event. But useful for IE.
el.addEventListener('DOMNodeInserted', function DOMNodeInserted (e: Event) {
// If inserting the fakeStyleEl.
if (e.target === fakeStyleEle) {
// Refresh.
refreshDOMContent();
// Remove listener.
el.removeEventListener('DOMNodeInserted', DOMNodeInserted);
}
});
} else {
// When it is 'onload', ok, means the whole el is mounted on the doc.
fakeStyleEle.onload = function elOnload () {
// Refresh.
refreshDOMContent();
// Remove listener.
fakeStyleEle.onload = null;
};
}
/** @see https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement **/
// Prepend to observe the el mounted event.
// $el.prepend(fakeStyleEle);
el.insertAdjacentElement('afterbegin', fakeStyleEle);
}
};
// Export.
export const htmlDirectiveOptions: DirectiveOptions = {
inserted: function (el, binding) {
// @ts-ignore
updateInnerHTMLContent(...arguments);
},
// This hook will be triggered when component and sub-component updated.
componentUpdated: function (el, binding) {
// If value changed.
if (
// Forcibly.
_.get(binding, 'modifiers.forceUpdate') ||
binding.value !== binding.oldValue
) {
// @ts-ignore
updateInnerHTMLContent(...arguments);
}
}
};
@wangziling
Copy link
Author

Usage:

  // Register.
  Vue.directive('s-html', htmlDirectiveOptions);
<div v-s-html="someContents" />

This directive supports excutable <script /> elements and will execute them by following their order.

E.g.

The someContents is:

<script src="remote_1.js"></script>
<script src="remote_2.js"></script>
<script type="text/javascript">
alert('Hello World');
</script>

You will see the Hello World alert after the two remote js files all loaded or failed to fetch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment