van
van copied to clipboard
Custom Elements support
The concise but powerful API of VanJS would make it very good alternative to template literals for Web Components.
I've been evaluating it for this use case but there are a couple of advanced use cases that aren't supported that would be great to see.
- Non primitive data types for properties (e.g. arrays, objects and functions)
- Custom event handling
I've attempted at implementing the Custom Elements Everywhere tests:
If you consider this useful I'd happily create a PR to add these into the existing test suite, as well as assisting with any API changes.
Look forward to seeing how this library progresses.
Thank you for your feedback!
I apologize for not being aware of the custom element feature in web standards when VanJS was developed. Extending VanJS to support custom property types and event handling seems reasonable to me, so patches are indeed welcome! 😀
VanJS was only published a week ago, thus the patching process hasn't been documented yet. However, I can provide the general steps involved in the development process for VanJS:
- Upgrade the version in the package.json file. Considering the significance of this feature, it might be suitable to increment the minor version.
- Modify source files in the src/ directory.
- Execute
bash publish.sh
within the src/ directory. (While it might seem unusual to run a command within a subdirectory, fixing that isn't currently a priority.) Make sure the bash script completes with no error messages. This action generates the published bundles corresponding to the new version. - Perform a browser-based test. Essentially, you need a static HTTP server to host the root directory. Then, attempt to load http://localhost:{port}/test/van.test.html in your browser to see if all tests pass.
- Develop new tests to encompass the new APIs.
- If applicable, update the corresponding
.d.ts
file as well. - Ideally, providing sample web pages to showcase the new feature would be beneficial.
Since this is an advanced feature I would encourage to implement it as an extension (in a separate file other than van.js) to keep the core library lean.
If you have any questions regarding the development process, feel free to let me know.
Thank you so much for the feedback!
Working through the instructions, running bash publish.sh
errors with node_modules
are needed so a npm/pnpm/yarn install
step is missing in the instructions.
publish.sh: line 11: ../node_modules/esbuild/bin/esbuild: No such file or directory
Not a problem, just a comment to improve the steps needed to run the test suite. :-)
Hmmm, I'm getting a sed
issue. I'll try to figure out what's going on.
bash publish.sh
../public/van-0.12.0.nomodule.js 3.3kb
⚡ Done in 9ms
../public/van-0.12.0.nomodule.debug.js 8.3kb
⚡ Done in 13ms
sed: -e expression #1, char 1: unknown command: `.'
Solved My version of sed
(4.7) works without a space between -i and .bak
After fixing sed
there was only 1 warning related to a direct eval
bash publish.sh
../public/van-0.12.0.nomodule.js 3.2kb
⚡ Done in 4ms
../public/van-0.12.0.nomodule.debug.js 8.2kb
⚡ Done in 9ms
▲ [WARNING] Using direct eval with a bundler is not recommended and may cause problems [direct-eval]
../test/van.test.js:789:119:
789 │ ...nst van = await (type === "es6" ? import(path).then(r => r.default) : fetch(path).then(r => r.text()).then(t => (eval(t), window.van)));
╵ ~~~~
You can read more about direct eval and bundling here: https://esbuild.github.io/link/direct-eval
1 warning
../test/van.test.nomodule.js 38.3kb
Sorry for spamming this issue, but this felt like the most appropriate place to leave feedback for the current build/test process.
I started a new thread : #37
Maybe we can discuss there.
VanJS integrates with WebComponents easily:
This is a counter component:
const { button, span } = van.tags
class MyCounter extends HTMLElement {
// Elements and functions
connectedCallback() {
this.value = van.state(0)
van.add(this,
button({
onclick: () => {
--this.value.val;
if (this.onchange)
this.onchange(this.value)
}
}, "-"),
span(this.value),
button({
onclick: () => {
++this.value.val
if (this.onchange)
this.onchange(this.value)
}
}, "+"))
}
}
customElements.define('my-counter', MyCounter);
And this is how you call it from within HTML:
<script>
function mychange(value){
out.textContent = `The counter is ${value.val}`
}
</script>
<my-counter onchange = "mychange(value)"/>
<h2 id="out">output</h2>
It is possible, but if you create your page with VanJS, you do not need WC. You can use JS-Classes instead.
After a little more digging, my previous comment on not being able to pass non primitive data types isn't entirely true. Arrays and functions do seem to work if the initial value isn't undefined
. If we take the example component that @efpage created and try to add it with the following code it does work:
const { h2, "my-counter": myCounter } = van.tags;
const textContent = van.state("The counter is 0");
van.add(
document.body,
myCounter({
onchange: (value) => textContent.val = `The counter is ${value.val}`
}),
h2(textContent)
)
If we modify MyCounter
so that that it also has the capability for fine grain controls such as onadd/onsubtract it doesn't pass down these new functions.
const { button, span } = van.tags
class MyCounter extends HTMLElement {
onadd; // Added
onsubtract; // Added
// Elements and functions
connectedCallback() {
this.value = van.state(0)
van.add(this,
button({
onclick: () => {
--this.value.val;
this.onsubtract?.(this.value) // Added
if (this.onchange)
this.onchange(this.value)
}
}, "-"),
span(this.value),
button({
onclick: () => {
++this.value.val
this.onadd?.(this.value) // Added
if (this.onchange)
this.onchange(this.value)
}
}, "+"))
}
}
customElements.define('my-counter', MyCounter);
Taking a look at the setter logic:
let setter = dom[k] !== _undefined ? v => dom[k] = v : v => dom.setAttribute(k, v)
The function onchange
exists as a property on HTMLElement
with a default value of null, but onadd
/onsubtract
have defaults of undefined
so therefore get passed down as string using setAttribute
. It's quite common to see this pattern in 3rd party components. One possible change could be to use the in
operator, e.g.
let setter = k in dom ? v => dom[k] = v : v => dom.setAttribute(k, v)
Objects seem to have a different issue, they are always treated as state derived properties. I think we could potentially do a similar thing here, by potentially changing this line:
else if (protoOf(v) === objProto) bind(...v["deps"], (...deps) => (setter(v["f"](...deps)), dom))
To something that checks the existence of one or both required properties for state derivation, e.g.
else if ("deps" in v) bind(...v["deps"], (...deps) => (setter(v["f"](...deps)), dom))
Hi @OFurnell,
In 0.12.0 we changed the way of when to set prop
value via property or via setAttribute
, see the current code. Not sure if it can solve the problem of custom event handler.
The issue with state-derived properties will likely be solved in 1.0 as part of the redesign of state-binding mechanism.
Any updates on this since v1? I'm interested in using VanJS to document some web components and wondering about setting non-primitive properties and binding to custom events too
Hi @sekoyo,
Though not officially tested, I believe it should be possible. You can try it out to see if it works.
Properties worked great, the custom event isn't caught however. Just to be sure I caught it successfully on the doc body:
document.body.addEventListener('myevent', () => {
console.log('caught my event on body')
})
Fired like this in web component:
connectedCallback() {
this.dispatchEvent(
new CustomEvent('myevent', {
composed: true,
bubbles: true,
detail: 'Hello from custom event',
})
)
}
set someProp(value: any) {
console.log('set someProp', value)
}
And attempted to catch like this:
const { 'lui-button': luiButton } = van.tags
luiButton(
{
someProp: { anObject: true },
onmyevent: e => console.log('myevent event fired', e),
},
'A button'
)
The support for custom event handers was added in 1.2.8: https://github.com/vanjs-org/van/discussions/246.
Very nice! 👏 Thanks
@all-contributors please add @sekoyo for bug
@all-contributors please add @OFurnell for bug