Skip to content

Commit

Permalink
remove list-m content option + add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
thipages committed Oct 18, 2024
1 parent 1cbc021 commit 9352c3b
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 76 deletions.
12 changes: 1 addition & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,7 @@
template="a-template"
source="./data.json"
></list-m>
<!-- OR declaring data within list-m content with header row being a list of ordered property (without the t- prefix) followed by a list of data groups -->
<list-m template="a-template">
data1,data2

data11
data12

data21
data22
</list-m>
<!-- output for both will be the same when using data.json below -->
<!-- output using data.json below -->
<list-m>
<a-template t-data1="data11" t-data2="data12"></a-template>
<a-template t-data1="data21" t-data2="data22"></a-template>
Expand Down
1 change: 0 additions & 1 deletion esm-min.js

This file was deleted.

205 changes: 190 additions & 15 deletions esm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,167 @@
import { getAttributes } from 'dry-html';
// Id generator
const fixedId = ('dry-'+Math.random()).replace('.', '');
let count = 1;
const uid = () => fixedId + (count++);
//
const INNER = '#inner#';
// Placeholders matching any ASCII chars and dashed with a t- prefix
const pattern = /\{(?<content>t-\w[\-\w]+)\}/;
/**
* refData is a Map
* key: uniqueId
* value = array of placeholers properties <prop, map, event>
* - prop is either INNER or attributeName
* - map is the placeholder name (t-*)
* - event is the event name (attribute starting with on)
*
* refClone is a clone of the template.
* - it will be cloned for creating new instances
*/
function setup(templateId) {
const template = document.getElementById(templateId);
const refClone = template.content.cloneNode(true);
const refData = computeAttributes(refClone);
computeInnerText(refClone, refData);
const attributes = new Set;
for (const [, props] of refData) {
for (const [type, map, event] of props) {
attributes.add(map);
}
}
return {refClone, refData, attributes:[...attributes]}
}
function checkTemplateIdValidity(templateId) {
if (!templateId) return false
const dashIndex = templateId.indexOf('-');
return dashIndex !==0 & dashIndex !== -1
}
function defineCustomElement(templateId) {
if (!checkTemplateIdValidity(templateId)) return
const {refClone, refData} = setup(templateId);
//
customElements.define(templateId,
class extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.style.display = 'block';
this.tRefs = structuredClone(refData);
this.append(refClone.cloneNode(true));
setData(this);
}
}
);
}
function updateElement(el, prop, value) {
if (prop === INNER) {
el.innerText = value;
} else {
if (prop === 'class') {
el.classList.remove(...el.classList);
el.classList.add(value);
} else {
el.setAttribute(prop, value);
}
}
}
function setData(that) {
const newIds = [];
for (const d of that.tRefs) {
const [id, subset] = d;
const el = that.querySelector('#'+id);
for (const [prop, map, event] of subset) {
const value = that.getAttribute(map);
updateElement(el, prop, value);
}
newIds.push([el, subset, id]);
}
// Renew ids, keeping them separated from refClone and refData
for (const [el, subset, id] of newIds) {
const newId = uid();
el.setAttribute('id', newId);
that.tRefs.set(newId, subset);
that.tRefs.delete(id);
}
}
function computeAttributes(clone) {
const elements = clone.querySelectorAll('*');
const map = new Map;
for (const element of elements) {
const attributes = element.attributes;
for (let i = 0, len = attributes.length; i <len; i++) {
const {nodeName, nodeValue} = attributes.item(i);
const match = nodeValue.match(pattern);
if (match) {
const id = createIdIfNotExisting(element);
if (!map.get(id)) map.set(id, []);
// Event are identified and stored but not (yet) used
const event = nodeName.substring(0,2) === 'on' ? {type: nodeName.substring(2)} : null;
map.get(id).push([nodeName, match.groups.content, event]);
}
}
}
return map
}
function computeInnerText(clone, data) {
walkHtmlElements(clone, function(element) {
if (element.childElementCount === 0) {
const match = element.textContent.match(pattern);
if (match) {
const id = createIdIfNotExisting(element);
if (!data.get(id)) data.set(id, []);
data.get(id).push([INNER, match.groups.content]);
}
}
});
}
function createIdIfNotExisting(node) {
let id = node.getAttribute('id');
if (!id) {
id = uid();
node.setAttribute('id', id);
}
return id
}
function walkHtmlElements(element, callback) {
callback(element);
if (element.firstElementChild) {
walkHtmlElements(element.firstElementChild, callback);
}
if (element.nextElementSibling) {
walkHtmlElements(element.nextElementSibling, callback);
}
}

// a bit terser code than I usually write but it's 10 LOC within 80 cols
// if you are struggling to follow the code you can replace 1-char
// references around with the following one, hoping that helps :-)

// d => descriptors
// k => key
// p => promise
// r => response

const d = Object.getOwnPropertyDescriptors(Response.prototype);

const isFunction = value => typeof value === 'function';

const bypass = (p, k, { get, value }) => get || !isFunction(value) ?
p.then(r => r[k]) :
(...args) => p.then(r => r[k](...args));

const direct = (p, value) => isFunction(value) ? value.bind(p) : value;

const handler = {
get: (p, k) => d.hasOwnProperty(k) ? bypass(p, k, d[k]) : direct(p, p[k])
};

/**
* @param {RequestInfo | URL} input
* @param {...RequestInit} init
* @returns {Promise<Response> & Response}
*/
var fetch$1 = (input, ...init) => new Proxy(fetch(input, ...init), handler);

customElements.define(
'list-m', class extends HTMLElement {
Expand All @@ -7,30 +170,42 @@ customElements.define(
}
connectedCallback() {
this.style.display = 'block';
init(this);
}
set data(d) {
const tag = this.getAttribute('template');
this.createList(tag, d);
try {
defineCustomElement(tag);
} catch (e) {}
this.innerHTML = this.createList(tag, d);
}
createList(tag, d) {
let html = '';
const namesWithNoPrefix = getAttributes(tag).map (v=>v.substring(2));
for (const item of d) {
html += `
return d.map (
props => `
<${tag}
${allAttrs(namesWithNoPrefix, item).join('\n')}
${allAttrs(props).join('\n')}
></${tag}>
`;
}
this.innerHTML = html;
`
).join('\n')
}
}
);
function allAttrs(props, obj) {
return props.map (
prop => attr(prop, obj[prop])
function allAttrs(props) {
return Object.entries(props).map (
([name, value]) => `t-${name}="${value}"`
)
}
function attr (name, value) {
return `t-${name}="${value}"`
async function init(that) {
const source = that.getAttribute('source');
if (!source) return
const data = await getSourceContent(source);
if (!data) return
that.data = data;
}
async function getSourceContent(source) {
try {
return await fetch$1(source).json()
} catch (e) {
return false
}
}
1 change: 1 addition & 0 deletions esm.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "list-m",
"version": "0.0.1",
"version": "0.1.0",
"description": "",
"main": "src/index.js",
"type": "module",
Expand Down
6 changes: 5 additions & 1 deletion rollup/rollup.config_esm.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import resolve from '@rollup/plugin-node-resolve'
export default {
input: './src/index.js',
output: {
file: './esm.js',
format: 'esm'
}
},
plugins: [
resolve()
]
}
2 changes: 1 addition & 1 deletion rollup/rollup.config_esm_min.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import terser from '@rollup/plugin-terser';
export default {
input: './src/index.js',
output: {
file: './esm-min.js',
file: './esm.min.js',
format: 'esm'
},
plugins: [
Expand Down
39 changes: 9 additions & 30 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {defineCustomElement} from 'dry-html'
import fetch from 'fetch'
import fetch from '@webreflection/fetch'
customElements.define(
'list-m', class extends HTMLElement {
constructor() {
Expand Down Expand Up @@ -34,36 +34,15 @@ function allAttrs(props) {
}
async function init(that) {
const source = that.getAttribute('source')
const data = !source
? parseContent(that.textContent)
: await getSourceContent(source)
if (!source) return
const data = await getSourceContent(source)
if (!data) return
that.data = data
}
function parseContent(initialContent) {
let header, fieldNum, propCount = 0, dataCount = 0, data = [{}]
for (const line of initialContent.split('\n')) {
const trimmedLine = extendedTrim(line)
if (trimmedLine.replace(/\s/g, '') !=='') {
if (!header) {
header = line.split(',').map(v => extendedTrim(v))
fieldNum = header.length
} else {
if (propCount === fieldNum) {
propCount = 1
dataCount++
data.push({[header[0]]: trimmedLine})
} else {
propCount++
data[dataCount][header[propCount-1]]= trimmedLine
}
}
}
async function getSourceContent(source) {
try {
return await fetch(source).json()
} catch (e) {
return false
}
return data
}
function getSourceContent(source) {
return fetch(source).json()
}
function extendedTrim(s) {
return s.replace(/^\s+|\s+$/g, '')
}
1 change: 1 addition & 0 deletions test/data.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo
19 changes: 9 additions & 10 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
"imports": {
"dry-html": "./../node_modules/dry-html/esm-min.js",
"fetch": "./../node_modules/@webreflection/fetch/es.js"
"@webreflection/fetch": "./../node_modules/@webreflection/fetch/es.js"
}
}
</script>
Expand All @@ -23,18 +23,17 @@ <h1>list-m Tests</h1>
<div>{t-name}</div>
<div>{t-id}</div>
</template>
<list-m template="info-m">
name,id
name1
1
name2
2
name3
3
</list-m>
<list-m
template="info-m"
source="./data.json"
></list-m>
<list-m
template="info-m"
source="./foo.json"
></list-m>
<list-m
template="info-m"
source="./data.txt"
></list-m>
</body>
</html>
Loading

0 comments on commit 9352c3b

Please sign in to comment.