HTMLElement.prototype.$template = function (template) { const $host = $(document).template(template, this); return document.readyState === "loaded" ? Promise.resolve($host) : new Promise(resolve => { $host.ready(() => resolve($host)); }); }; HTMLElement.prototype.$templateSources = function (...sources) { return $templateSources(this, ...sources).then(template => this.$template(template)); } jQuery.fn.template = function (template, webComponent) { if (webComponent.shadowRoot) { return $(webComponent.shadowRoot.host); } let $template = $(template); $template = $template.prop('tagName') !== 'TEMPLATE' ? $('') : $template; const node = $template.get(0).content.cloneNode(true); const shadowRoot = webComponent.attachShadow({ mode: 'open' }); shadowRoot.appendChild(node); return $(shadowRoot.host); }; jQuery.fn.slot = function (queryOrElem, slotName) { const shadowRoot = this.get(0).shadowRoot; if (shadowRoot) { throw new Error('Current element doesn\'t have a shadowroot!'); } if (queryOrElem instanceof jQuery) { if (slotName) { queryOrElem = queryOrElem.attr("slot", slotName); } return this.append(queryOrElem.clone()); } else { const slots = shadowRoot.querySelectorAll(queryOrElem || 'slot'); const allNodes = []; slots.forEach(slot => { slot.assignedNodes().forEach(node => { allNodes.push(node); }); }); return $(allNodes); } }; (function (jQuery) { const $find = jQuery.fn.find; jQuery.fn.extend({ find: function () { let result = $find.apply(this, arguments); if (result.length === 0) { const shadowRoot = this.get(0).shadowRoot; if (shadowRoot) { const shadowResult = shadowRoot.querySelectorAll(...arguments); result = $(shadowResult); } } return result; } }); })(jQuery); /** * Takes the sources and concatenates them into one and then cache the result * into element's constuctor. (serving as a static "class" property for future element instances). * * @param {HTMLElement} element HTMLElement * @param {...String | jQuery} sources File sources or JQuery element. * @returns Promise or resolved promise if result is already cached. */ function $templateSources(element, ...sources) { if (!(element instanceof HTMLElement)) { throw new Error("element must a HTMLElement"); } const ct = element.constructor; if (!ct.cachedTemplateXHR) { const fetchSrc = src => { let resultPromise; if (src instanceof jQuery) { resultPromise = Promise.resolve($('
').append(src.clone()).html()); } else { resultPromise = fetch(src) .then(response => { let result; if (response.ok) { result = response.text(); if (response.headers.get('Content-Type').toLocaleLowerCase().includes('text/css')) { //wrap with style tags; result = Promise.allSettled(['']).then(data => { return data.map(d => d.value).join('\n'); }); } } else { result = Promise.reject(response); } return result; }); } return resultPromise; }; ct.cachedTemplateXHR = Promise.allSettled(sources.map(fetchSrc)).then(results => { return results.map(r => r.value).join('\n'); }); } return ct.cachedTemplateXHR; } function $wc(htmElement = HTMLElement) { if (htmElement instanceof HTMLElement) { throw new Error("argmuent must be a HTMLElement"); } const builder = { template: undefined, templateSources: undefined, onCreate: function () { }, connectedCallback: function ($host) { }, disconnectedCallback: function ($host) { }, watchAttr: [], attributeChangedCallback: function ($host, changed) { }, adoptedCallback: function ($host) { }, extend: {}, }; return { onCreate: function (onCreate) { builder.onCreate = onCreate; return this; }, connectedCallback: function (connectedCallback) { builder.connectedCallback = connectedCallback; return this; }, disconnectedCallback: function (disconnectedCallback) { builder.disconnectedCallback = disconnectedCallback; return this; }, attributeChangedCallback: function (attributeChangedCallback, watchAttr = []) { builder.attributeChangedCallback = attributeChangedCallback; builder.watchAttr = watchAttr; return this; }, adoptedCallback: function (adoptedCallback) { builder.adoptedCallback = adoptedCallback; return this; }, template: function (template) { builder.template = template; return this; }, templateSources: function (templateSources) { builder.templateSources = templateSources; return this; }, extend: function (extend) { builder.extend = extend; return this; }, define: function (name, options) { if (customElements.get(name)) { throw new Error(`$name is already defined as web component`); } const WC = (class extends htmElement { constructor() { super(); if (builder.templateSources) { this.$host = this.$templateSources(...templateSources); } else if (builder.template) { this.$host = this.$template(builder.template); } else { this.$host = Promise.resolve($(this)); } builder.onCreate.bind(this)(); } connectedCallback() { this.$host.then(builder.connectedCallback.bind(this)); } disconnectedCallback() { this.$host.then(builder.disconnectedCallback.bind(this)); } attributeChangedCallback(name, oldValue, newValue) { this.$host.then($host => { builder.attributeChangedCallback.call(this, $host, { name, oldValue, newValue }); }); } adoptedCallback() { this.$host.then(builder.adoptedCallback.bind(this)); } }); if (builder.watchAttr.length > 0) { Object.defineProperty(WC, "observedAttributes", { get: () => builder.watchAttr }); } for (const [k, v] of Object.entries(builder.extend)) { WC.prototype[k] = function () { return v.apply(this, arguments); }; if(!jQuery.fn[k]){ jQuery.fn[k] = function(){ return this.get(0)[k](...arguments); }; } } optionsObj = options ? { extends : options} : null; customElements.define(name, WC, optionsObj); return customElements.whenDefined(name); } }; } jQuery.fn.$host = function(){ const wc = this.get(0); return wc.$host || Promise.resolve(wc.shadowRoot.host); }; jQuery.fn.$shr = function(){ return this.get(0).shadowRoot; };