Skip to content

Commit

Permalink
Use fewer data attributes (#163)
Browse files Browse the repository at this point in the history
* Use fewer data attributes

* Add changeset
  • Loading branch information
matthewp committed Aug 28, 2022
1 parent c5dc16b commit c39d80e
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 84 deletions.
21 changes: 21 additions & 0 deletions .changeset/rotten-news-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
"corset": minor
---

Consolidate data properties

This consolidates data attributes, grouping them by type, so that you might have:

```html
<div data-corset-props="--one --two --three"></div>
```

Instead of:

```html
<div data-corset-prop-one data-corset-prop-two data-corset-prop-three></div>
```

This means less clutter in the DOM.

In addition to `data-corset-props` there is also `data-corset-stores` for stores and `data-corset-scope` for each scope values (item and index).
30 changes: 0 additions & 30 deletions src/custom-prop.js

This file was deleted.

27 changes: 16 additions & 11 deletions src/each.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// @ts-check
import { datasetKey } from './custom-prop.js';
import { addItemToScope } from './scope.js';
const eachSymbol = Symbol.for('corset.each');
const itemSymbol = Symbol.for('corset.item');
const indexSymbol = Symbol.for('corset.index');

/**
* @typedef {object} FragData
Expand Down Expand Up @@ -57,18 +60,20 @@ export class EachInstance {
* @param {*} index
*/
setData(frag, value, index) {
let itemProp = datasetKey('Item');
let indexProp = datasetKey('Index');
for(let element of frag.nodes) {
if('dataset' in element) {
/** @type {HTMLElement} */
(element).dataset[itemProp] = '';
/** @type {any} */
(element)[Symbol.for(itemProp)] = value;
/** @type {HTMLElement} */
(element).dataset[indexProp] = '';
/** @type {any} */
(element)[Symbol.for(indexProp)] = index;
let e = /** @type {HTMLElement} */(element);
addItemToScope(e, eachSymbol, 'item', itemSymbol, 'corsetScope', value);
addItemToScope(e, eachSymbol, 'index', indexSymbol, 'corsetScope', index);

// /** @type {HTMLElement} */
// (element).dataset[itemProp] = '';
// /** @type {any} */
// (element)[Symbol.for(itemProp)] = value;
// /** @type {HTMLElement} */
// (element).dataset[indexProp] = '';
// /** @type {any} */
// (element)[Symbol.for(indexProp)] = index;
}

}
Expand Down
18 changes: 7 additions & 11 deletions src/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createValueTemplate } from './template.js';
import { ComputedValue } from './compute.js';
import { registry as behaviorRegistry } from './mount.js';
import { lookup } from './scope.js';
import { storeDataName, storePropName, getKeySymbol } from './store.js';
import { storeDataSelector, storePropName, getKeySymbol } from './store.js';

/**
* @typedef {import('./binding').Binding} Binding
Expand Down Expand Up @@ -126,9 +126,7 @@ class ScopeLookupFunction {
*/
constructor(dataName, propName) {
/** @type {string} */
this.dataPropName = 'data-corset-' + dataName;
/** @type {string} */
this.dataSelector = '[' + this.dataPropName + ']';
this.dataSelector = `[data-corset-scope~=${dataName}]`;
/** @type {string} */
this.propName = propName;
/** @type {any} */
Expand All @@ -146,7 +144,7 @@ class ScopeLookupFunction {
let check = false;
if(changeset.selectors) check = true;
if(check) {
let value = lookup(element, this.dataPropName, this.dataSelector, this.propName);
let value = lookup(element, this.dataSelector, this.propName);
if(value !== this.value) {
this.value = value;
return true;
Expand All @@ -165,13 +163,13 @@ class ScopeLookupFunction {

registerFunction.call(registry, 'item', class extends ScopeLookupFunction {
constructor() {
super('item', 'corsetItem');
super('item', 'corset.item');
}
});

registerFunction.call(registry, 'index', class extends ScopeLookupFunction {
constructor() {
super('index', 'corsetIndex');
super('index', 'corset.index');
}
});

Expand All @@ -191,9 +189,8 @@ registerFunction.call(registry, 'store-get', class {
check([storeName, key], _props, { element }, changeset) {
let check = changeset.selectors;
if(check) {
let dataName = storeDataName(storeName);
/** @type {Map<string, any> | undefined} */
let map = lookup(element, dataName, `[${dataName}]`, storePropName(storeName));
let map = lookup(element, storeDataSelector(storeName), storePropName(storeName));
if(map?.get(key) !== this.value) {
this.value = map?.get(key);
return true;
Expand Down Expand Up @@ -222,9 +219,8 @@ registerFunction.call(registry, 'store', class {
check([storeName], _props, { element }, changeset) {
let check = changeset.selectors;
if(check) {
let dataName = storeDataName(storeName);
/** @type {Map<any, any> | undefined} */
let map = lookup(element, dataName, `[${dataName}]`, storePropName(storeName));
let map = lookup(element, storeDataSelector(storeName), storePropName(storeName));
if(map !== this.map) {
this.map = map;
return true;
Expand Down
35 changes: 19 additions & 16 deletions src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

import { flags } from './property.js';
import { EachInstance } from './each.js';
import { datasetPropKey } from './custom-prop.js';
import { Mountpoint } from './mount.js';
import { lookup } from './scope.js';
import { storePropName, storeDataName, storeDataPropName, Store } from './store.js';
import { lookup, addItemToScope, removeItemFromScope } from './scope.js';
import { storePropName, storeDataSelector, Store } from './store.js';

/**
* @typedef {import('./binding').Binding} Binding
Expand All @@ -25,6 +24,9 @@ const eachInstances = new WeakMap();
/** @type {WeakMap<HostElement, Map<MountedBehaviorType, Mountpoint>>} */
const mountPoints = new WeakMap();

const propsSymbol = Symbol.for('corset.props');
const storesSymbol = Symbol.for('corset.stores');

/**
*
* @param {HostElement} element
Expand All @@ -47,14 +49,14 @@ function render(element, bindings, root, changeset) {
let oldValue = binding.value;
let storeName = binding.update(changeset);
if(storeName) {
element.dataset[storeDataPropName(storeName)] = '';
let map = new Store(root);
/** @type {any} */
(element)[Symbol.for(storePropName(storeName))] = map;
addItemToScope(element, storesSymbol, storeName, Symbol.for(storePropName(storeName)),
'corsetStores', map)

root.mount?.context?.stores.set(storeName, map);
} else {
delete element.dataset[storeDataPropName(oldValue)];
delete /** @type {any} */(element)[Symbol.for(storePropName(oldValue))];
removeItemFromScope(element, storesSymbol, oldValue, Symbol.for(storePropName(oldValue)),
'corsetStores');
root.mount?.context?.stores.delete(oldValue);
}
invalid = true;
Expand All @@ -70,12 +72,14 @@ function render(element, bindings, root, changeset) {
if(binding.dirty(changeset)) {
binding.update(changeset);
let value = binding.getList();
element.dataset[datasetPropKey(propertyName)] = '';
/** @type {any} */
(element)[Symbol.for(propertyName)] = {
value,
compute: binding.compute
};
if(value.length) {
addItemToScope(element, propsSymbol, propertyName, Symbol.for(propertyName), 'corsetProps', {
value,
compute: binding.compute
});
} else {
removeItemFromScope(element, propsSymbol, propertyName, Symbol.for(propertyName), 'corsetProps');
}
}
}
}
Expand All @@ -86,8 +90,7 @@ function render(element, bindings, root, changeset) {
let args = binding.update(changeset);
if(args) {
let [storeName, key, value] = args;
let dataName = storeDataName(storeName);
let map = lookup(element, dataName, `[${dataName}]`, storePropName(storeName));
let map = lookup(element, storeDataSelector(storeName), storePropName(storeName));
map?.set(key, value);
}
invalid = true;
Expand Down
57 changes: 54 additions & 3 deletions src/scope.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,69 @@

/**
*
* @param {string} dataPropName
* @param {string | null} dataPropName
* @param {string} dataSelector
* @param {string} propName
* @returns
*/
export function lookup(element, dataPropName, dataSelector, propName) {
export function lookup(element, dataSelector, propName) {
/** @type {Element | null} */
let el = element;
do {
if(el.hasAttribute(dataPropName)) {
if(el.matches(dataSelector)) {
return /** @type {any} */(el)[Symbol.for(propName)];
}
el = element.closest(dataSelector);
} while(el);
}



/**
*
* @param {HTMLElement} element
* @param {symbol} listSym
* @param {string} key
* @param {symbol} keySym
* @param {string} dataProp
* @param {any} value
*/
export function addItemToScope(element, listSym, key, keySym, dataProp, value) {
let e = /** @type {any} */(element);
/** @type {Set<string>} */

let list = e[listSym];
if(!list) {
list = e[listSym] = new Set();
}
list.add(key);
let str = Array.from(list).join(' ');
element.dataset[dataProp] = str;

e[keySym] = value;
}

/**
*
* @param {HTMLElement} element
* @param {symbol} listSym
* @param {string} key
* @param {symbol} keySym
* @param {string} dataProp
*/
export function removeItemFromScope(element, listSym, key, keySym, dataProp) {
let e = /** @type {any} */(element);
/** @type {Set<string> | undefined} */
let list = e[listSym];
if(list) {
list.delete(key);
delete e[keySym];
if(list.size) {
let str = Array.from(list).join(' ');
element.dataset[dataProp] = str;
} else {
delete e[keySym];
delete element.dataset[dataProp];
}
}
}
10 changes: 1 addition & 9 deletions src/store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// @ts-check
import { pascalCase } from './custom-prop.js';

export const getKeySymbol = Symbol.for('corset.getKey');

/**
Expand Down Expand Up @@ -57,10 +55,4 @@ export const storePropName = storeName => `corset.store.${storeName}`;
* @param {string} storeName
* @returns {string}
*/
export const storeDataName = storeName => `data-corset-store-${storeName}`;
/**
*
* @param {string} storeName
* @returns {string}
*/
export const storeDataPropName = storeName => 'corsetStore' + pascalCase('-' + storeName);
export const storeDataSelector = storeName => `[data-corset-stores~=${storeName}]`;
6 changes: 2 additions & 4 deletions src/value.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,8 @@ export class PlaceholderValue {
*/
#get(args, { element }) {
let [propName] = args;
let dataName = 'prop-' + propName.slice(2);
let dataPropName = 'data-corset-' + dataName;
let dataSelector = '[' + dataPropName + ']';
return lookup(element, dataPropName, dataSelector, propName);
let dataSelector = `[data-corset-props~=${propName}]`;
return lookup(element, dataSelector, propName);
}
get() {
return this.value;
Expand Down
22 changes: 22 additions & 0 deletions test/test-custom-property.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,26 @@ QUnit.test('Can be used as a function', assert => {
`;
bindings.update(root);
assert.equal(root.firstElementChild.textContent, 'one two');
});

QUnit.test('Are removed when the selector no longer matches', assert => {
let root = document.createElement('main');
root.innerHTML = `<div id="app"></div>`;
function run(showOne) {
return sheet`
#app {
class-toggle: one ${showOne};
attr: data-one var(--v, none);
}
.one {
--v: two;
--another: three;
}
`;
}
let app = root.firstElementChild;
run(true).update(root);
assert.equal(app.getAttribute('data-one'), 'two');
run(false).update(root);
assert.equal(app.getAttribute('data-one'), 'none');
});

0 comments on commit c39d80e

Please sign in to comment.