4 ms·
Can you elaborate a bit more on the example in your footnote about propagating client-side state? Does that provide functionality similar to Alpine.js's data ob
by DylanSp 3y ago
Can you elaborate a bit more on the example in your footnote about propagating client-side state? Does that provide functionality similar to Alpine.js's data objects/stores?
- lelanthran 3y agoSure, but bear in mind it's only an experiment, so I do not expect this to be long-lived, or if it is, I do not expect it to look the same way as it does now, and even if either of the above remain true, I still think that alternatives such as htmx are preferably for multiple reasons, bar one (explained in the last paragraph of this post). IOW, I expect this to be an experiment that I eventually throw away after having gained some insight. The `ps` in `psjs` below means "publish/subscribe". The big draw for me about htmx is that common usage does not require knowledge of Javascript! This means that htmx (and my set of custom elements) does not require the creator of the front-end to know that npm even exists, much less how to use it. The same goes for Virtual DOMs, Hooks, tree-shaking/web-packing, promises and async, functions, and the whole container-ship full of arbitrary things that common front-end stacks need you to know. My custom elements require javascript (for now), and require that the user know what is meant by 'publish a message onto a queue' and 'subscribe to a queue for messages of a particular subject'. ------------------------------------- I have at least four custom elements: psjs-tree, psjs-bind, psjs-subscribe and psjs-publish. I also have two JS functions, `publish` and `subscribe`: `publish` publishes an arbitrary payload (a JS object) to a channel (string) with a subject (also of string type). `subscribe` registers a callback function for a channel (string) with a pattern for the subject. These are all global. A piece of code could do: subscribe("ERROR", "*", (sender, subject, payload) => { dialog.innerHTML = `${sender.id}: Error ${subject} ${payload.errMessage}`; dialog.showModal(); }); and then any code, anywhere else can do `publish(this, "ERROR", "Rpc Request", { errMsg: "Rxed 404"});` My web components psjs-publish and psjs-subscribe execute those two functions (for publish, there is an `onevent` attribute). The psjs-publish component uses the closest psjs-tree ancestor to determine which fields go into the payload. The fields are set by psjs-bind. The psjs-subscribe does the opposite of psjs-publish, in that it uses the closest psjs-tree ancestor to determine which fields of the payload should be used to update the contents or values of the psjs-tree descendents. For example, a form that can have the values reset by some other code (say, fetching them from a server) and can have the values submitted: <psjs-tree> <psjs-subscribe channel="MYFORM" subject="update-fields"> </psjs-subscribe> <psjs-bind for="team-name"> </psjs-bind> <psjs-bind for="notifications"> </psjs-bind> <psjs-bind for="user-name"> </psjs-bind> <psjs-bind for="user-email"> </psjs-bind> <form> <div id="title"> Example</div> <div id="team-name"> </div> <div id="notifications"> </div> <input id="user-name"> </input> <input id="user-email"> </input> <psjs-publish onevent="click" channel=MYFORM subject=submit-fields> <button>Submit</button> </psjs-publish> </form> </psjs-tree> Of course, this means that I have a line of js somewhere that looks like this: subscribe("MYFORM", "submit-*", (sender, subject, payload) => { /* send payload to server */ }); And another that looks like this: const rsp = successfulFetchFromServer(); publish(this, "MYFORM", "update-fields", { "team-name": rsp.teamName, ... }); This is still very much a WIP, and I'm playing with having custom elements for performing RPC without using the publish/subscribe functions; this is so that, during usage of these components, there will be zero JS to write. Right now there is still the publish/subscribe calls that intercept messages and tx/rx messages from the server to local components. I'd like it to look like this eventually: <rpc-tree> <rpc-rx subject="Some Subject"> <rpc-bind for="team-name"> </rpc-bind> <rpc-bind for="notifications"> </rpc-bind> <rpc-bind for="user-name"> </rpc-bind> <rpc-bind for="user-email"> </rpc-bind> <form> <div id="title"> Example</div> <div id="team-name"> </div> <div id="notifications"> </div> <input id="user-name"> </input> <input id="user-email"> </input> <rpc-tx onevent="click" href="/some/path/to/submit" subject="Some Subject"> <button>Submit</button> </rpc-tx> </form> </rpc-tree> The psjs-subscribe/publish elements are still useful to propagate value changes locally within the client, but for non-local state an RPC set of elements seems preferable. All in all (and I realise that I wrote an exceptionally long post, so sorry!), I feel that htmx is better right now, but ... it forces the creation of a backend-for-frontend layer, so you have the layers of front-end -> backend-for-front-end -> API. With RPC calls only, I don't need to write the middle layer and can simply use the API directly from the client application, especially when using the custom element for specifying fragments.
- DylanSp 3y agoNo need to apologize for the length of your post! It's interesting to read about your experiments. Looking at your example with the psjs elements, it looks like the psjs-bind elements define what fields get captured and used in the published payload, as well as setting the input elements' values from a subscription event; is that correct? That does seem useful for keeping state synchronized across multiple local components. It seems like you'd still need some JS if there's any client-side logic that needs to be run, though I'm not sure if there's a way to avoid that. I'm thinking of something like displaying different parts of the page based on multiple input fields. Maybe you could handle a lot of use cases like that with plain HTML and CSS, though.
- lelanthran 3y ago> No need to apologize for the length of your post! It's interesting to read about your experiments. Thanks. As a newcomer, I looked into what knowledge is required to produce a dynamic front-end, and completely 'noped' out of it after a few days[1]. The web component approach looks, to me, more future-proof as well as easier to use. > Looking at your example with the psjs elements, it looks like the psjs-bind elements define what fields get captured and used in the published payload, as well as setting the input elements' values from a subscription event; is that correct? Broadly correct, yes, but a little clarification - elements don't have to be input elements, then can be any elements. So, for example, if the psjs-bind 'for' target would is a span element, then the innerText content is updated. For input elements, the value is updated, etc. I'm still working out whether this is a good idea or not. > It seems like you'd still need some JS if there's any client-side logic that needs to be run, though I'm not sure if there's a way to avoid that. I'm thinking of something like displaying different parts of the page based on multiple input fields. You're correct; some JS is still needed! Of course I consider that a failure that needs to be rectified - for updating the page based on user actions (or input element changes) I'm considering a custom element that subscribes to a channel and listens for a subject pattern, and then changes its style fields (`display: none` to `display: block` (or vice versa)) based on the payload. Keep an eye out for when I finally make this available to the world (maybe a name like 'ZeroJS', for example :-)) [1] Programming web services is dominated by out-of-standards/never-standardised technology - npm, build-steps (for front-end), frameworks, tree-shaking, commonjs includes, webpacking, etc. All I want is a quick way to produce dynamic web pages. A full 6-months of learning all the existing techis exactly my idea of a non-fun time.