Make complex UIs (a.k.a. views) from simpler ones.
Wraps the hypertext literal with an extra special case for [key, DOM]. This allows you to design a custom UI using HTML, and tell the custom template how to bind sub-views to properties of the parent value. Simple but powerful.
You can now use a spread key to create a simple wrapper of an existing view
viewof num = view<div><h1>My entitled control</h1>${['...', Inputs.range()]}
This spread also works for destructuring an object
viewof num = view${['...', { 'v1': Inputs.range(), 'v2': Inputs.text() }]}
The motivation for these semantics was creating tables where you need to wrap inner elements in <td>, and the row/column values is conveniently represented as a dictionaries.
to create a custom UI over a very minimalistic value holder. In out case we donât want the component to render so I also hide it in a comment, which is weird and I need a better way to do that but its ok for now.
We can then programatically assign the output in response to external events. In out case we listen to a HTML file input and wrap the file attachment with the the LocalFileAttachment stuff. Combined with the ability to add inline the whole thing can be expressed in a single cell.
To get that working array-of-views bindings for view literal is now mutable which is quite cool for expressivity.
The data editor offers a minimal splice API so adjacent cells can add/delete rows programmatically. This allows decoupling the âadd itemâ UI cell from the data editor list view cell. I think this is a good way to break features across cells using back-writability.
I need this component for a commercial project. I tried to make it general though but I donât have too much time for non-essential features, but I am keen to support it so Suggestions or forks are very welcome.
Some better documentation was requested⌠here it is!
it includes a substantial port of a famous React example by Micheal Jackson (UNPKG, Redux). I think the observablehq view way is better! Hopefully this gives people a few ideas on how they can reuse UI components better.
it will use that builder to create new UI elements if that value is assigned
demo.value = {a : "my text"}
And it will prune the UI if you assing {}. So you can do backwritable dynamic keyed collections now.
I have some regrets. It would be nice if it did it could use the builder with normal property assignment like demo.value.a = âmy textâ, similarly for dynamic lists it be nice if I could just use the array mutation operators like v.array.push() and it would build the UI. Maybe dynamic proxies? I think I need to split the everything-view into separate scalar, array and object variants. Now I see the big picture itâs really âjust an instrumented JSONâ.
Anyway, I write lots of tests, the code has becomes massive and convoluted as I have added features, but the tests are to help define what the refactored replacement should do. Its broadly feature complete in that there is a way of achieving all the obvious things you would want to do, but also its incomplete in the sense there are gaps in the API so itâs not quite as easy and natural as working with native arrays and objects.
Very inspiring. I reminded me that I actually wanted to write dynamic tax calculator with all the sliders and connections showing what depends from what. One day I may do this⌠if I ever understand their maths.
I have had a couple of requests along the lines of âhow to dynamically change the options in a selectâ for complex UIs.
This is surprisingly hard because the options in Inputs.select are not part of the value, so you canât back write them dynamically! Variations of this issue bite me constantly when building complex UIs. How I want to use a UI component, and the degrees of freedom offered in the signature are often not in alignment.
SO I FIXED IT
Hopefully this makes everybodies great UI libraries stretch a little furtherâŚ
BTW I have found juice very useful in lots of places. Itâs almost a cheat code for reactivity as you can just artificially create a builder and then juice it into a reactive component without doing any wiring.
juice((args) => view`...`, {width: "[0].width"})
Even with view I still find building complex UIs too much work. The problem is getting things to work on mobile and desktop is tricky with CSS, and view only exposes HTML with gets pretty deep with responsive layouts.
So I long for the days of Visual Basic Form Builder, I created something simple like that. A simple grid, on panels that rearrange. 3 columns for desktop, less on smaller screens. Now I can just lay out components in absolute coordinates, but the panels rearrange to make it work. So much simpler!
Itâs all reactive too. So it can all be tweaked in real time too via external logic.
As I try to create large UIs, I have found view hits a performance issue because it generally removes its DOM and replaces it even for minor operations like adding an element to an array. It seems arrays are super useful for HTML, naturally representing table rows or columns or list elements.
view has needed a refactor a while, so I have taken the opportunity to refactor out âarrayâ handling in view, and apply a performance optimization.
If you do an in-place mutation of an array, it will mirror that in-place mutation in the DOM. So
viewof myArrayView.value.push(4)
or
myArrayView.push(4)
will instantiate a DOM node (using the construction time builder arg) to hold the 4 and append it to the DOM elements. I have support for push,pop,shift,unshift,splice
After finishing I realised for symmetry you should also be able to
but I have not generalized that far. Also I need to do a similar thing for dynamic objects. This is all too much work right this second, BUT, at least I have finally managed to refactor view a bit which should make future refactors a little bit easier.
I have tried to preserve backwards compatibility, the unit test coverage is expanding, but let me know if something has broken.