Skip to content

Instantly share code, notes, and snippets.

@enjikaka
Created September 11, 2019 20:21
Show Gist options
  • Save enjikaka/58329d90064020efe19115f67846a711 to your computer and use it in GitHub Desktop.
Save enjikaka/58329d90064020efe19115f67846a711 to your computer and use it in GitHub Desktop.
Nightcore-App Web.js
class TemplateStore {
constructor (node) {
if (!(node instanceof HTMLElement)) {
throw new Error('DOM Node for template storage is not set up. Please pass in an HTMLElement to the constructor.');
}
this.parentDOMNode = node;
}
/**
* Takes a string of content to be cached inside a
* <template> element.
*
* @param {string} id
* @param {string} templateContent
*/
add (id, templateContent) {
if (this.parentDOMNode.querySelector(`#${id}`)) {
return;
}
const template = document.createElement('template');
template.setAttribute('id', id);
template.innerHTML = templateContent;
this.parentDOMNode.appendChild(template);
}
get (id) {
if (!this.parentDOMNode.querySelector(`#${id}`)) {
throw new Error(`Template with id #${id} does not exist.`);
}
const templateElement = this.parentDOMNode.querySelector(`#${id}`);
if (templateElement instanceof HTMLTemplateElement) {
const templateContent = templateElement.content;
return document.importNode(templateContent, true);
}
return undefined;
}
/**
* Takes a string of content to be cached inside a
* <template> element.
*
* @param {string} id
*/
remove (id) {
const template = this.parentDOMNode.querySelector(`#${id}`);
if (template) {
this.parentDOMNode.removeChild(template);
}
}
}
const templateStore = new TemplateStore(document.body);
export class Component extends HTMLElement {
async fetchResources () {
// @ts-ignore
if (!this.resources && !this.render && this.path) {
// @ts-ignore
const pathname = new URL(this.path).pathname;
const file = pathname.split('/').pop();
if (file === 'web.js') {
return;
}
const filename = file.split('.js')[0];
const path = pathname.split(file)[0];
this.resources = [
{ path: path + filename + '.css' },
{ path: path + filename + '.html' }
];
}
const resourceLoadPromises = this.resources.map(({ path }) => fetch(path));
const fetches = await Promise.all(resourceLoadPromises);
const responses = await Promise.all(fetches.filter(r => r.ok).map(r => r.text()));
this.resources = this.resources.map((resource, i) => {
resource.content = responses[i];
return resource;
});
}
async buildTemplate () {
if (!this.resources) {
return;
}
const stylesheet = this.resources.filter(r => r.path.indexOf('css') !== -1).map(r => r.content).join('\n');
const markup = this.resources.filter(r => r.path.indexOf('html') !== -1).map(r => r.content).join('\n');
const templateContent = `
<style>
${stylesheet}
</style>
${markup}
`;
// @ts-ignore
templateStore.add(this.constructor.is, templateContent);
}
connectedCallback () {
this.sDOM = this.attachShadow({ mode: 'closed' });
this.renderToShadowDOM();
}
get props () {
return mapObservedAttributesToObject(this);
}
/**
* @param {string} selectors
* @return {Element | undefined}
*/
$ (selectors) {
if (this.sDOM) {
return this.sDOM.querySelector(selectors);
}
return undefined;
}
/**
* @param {string} selectors
* @return {NodeListOf<Element> | undefined}
*/
$$ (selectors) {
if (this.sDOM) {
return this.sDOM.querySelectorAll(selectors);
}
return undefined;
}
async renderToShadowDOM () {
// @ts-ignore
if (this.render) {
// @ts-ignore
this.sDOM.innerHTML = this.render();
} else {
await this.fetchResources();
await this.buildTemplate();
// @ts-ignore
const templateContent = await templateStore.get(this.constructor.is);
this.sDOM.appendChild(templateContent);
}
requestAnimationFrame(() => {
requestAnimationFrame(() => {
// @ts-ignore
if (this.postRender) {
// @ts-ignore
this.postRender();
}
});
});
}
static get is () {
console.error('You need to set a name for the component.');
return undefined;
}
}
export function register (StaticComponent) {
const hasName = Boolean(StaticComponent.is);
if (!hasName) {
console.error('Passed in stuff does not have name on static get is property.');
return;
}
const alreadyDefined = customElements.get(StaticComponent.is);
if (alreadyDefined) {
console.debug(`<${StaticComponent.is}> already defined. Skipping.`);
return;
}
customElements.define(StaticComponent.is, StaticComponent);
}
function snakeToCamel (s) {
return s.replace(/(-\w)/g, m => m[1].toUpperCase());
}
export function mapObservedAttributesToObject (componentInstance) {
const props = {};
// Map attributes to props
componentInstance.constructor.observedAttributes
.filter(attr => componentInstance.hasAttribute(attr))
.filter(attr => componentInstance.getAttribute(attr) !== undefined)
.forEach(attribute => {
props[snakeToCamel(attribute)] = componentInstance.getAttribute(attribute);
});
return props;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment