This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-12-28
Channels
- # ai (1)
- # beginners (190)
- # boot (24)
- # cider (43)
- # cljsjs (3)
- # cljsrn (29)
- # clojars (6)
- # clojure (310)
- # clojure-dev (6)
- # clojure-nl (6)
- # clojure-russia (11)
- # clojure-spec (66)
- # clojure-uk (95)
- # clojurescript (103)
- # clojurewerkz (2)
- # core-async (9)
- # cursive (4)
- # datomic (5)
- # hoplon (163)
- # lein-figwheel (52)
- # off-topic (6)
- # om (6)
- # onyx (42)
- # perun (8)
- # re-frame (16)
- # reagent (10)
- # ring (7)
- # ring-swagger (1)
- # rum (1)
- # slack-help (2)
- # uncomplicate (1)
- # untangled (80)
I'm having an extremely difficult time figuring out when cells are automatically deref'ed, and when you need to explicitly deref them.
Another question: (cell= (reset! ~cell1 cell2))
doesn't work the way I would expect (I would expect this to put the value of cell2 whenever it changes into cell1, but the ~
in front of cell1 doesn't seem to have any effect and I get errors saying that reset! is operating on something invalid.
@jumblerg: is there some convention regarding the "selectable" things in Hoplon/UI? I noticed everything is non-selectable by default if I use elem
. However, there's a selectable
fn/trait in ui.cljs
which is used by the item*
element. It also has more knobs inside, like *selected*
and *state*
which I haven't yet figured out - is it something that will disappear further on or do you consider that stable-ish? 🙂
I guess it's not really related to the userSelect
property which I see can be influenced through the color
trait, but still I'm interested 🙂
About (cell= (reset! ~cell1 cell2))
you can do (cell= (reset! ~(cell cell1) cell2))
iirc. Or create a function like:
@mynomoto Formula cells are one of the more obvious situations. But what about: in the attribute position of HTML? in the body position of HTML? In an expression in an attribute position? In an expression in a body position? In the callback part of a lens? What happens when it is explicity deref'ed in any of those positions?
So you need to call cell
to stop the automatic deref'ing, and the automatic deref'ing still happens in the context of the "evaluate this right now" ~?
Your my-reset presumably works because it is hiding the cell from the macro, right? No way to include it as an anonymous function scoped inside the cell=
?
You can pass cells as attributes and they will cause automatic updates. You can also pass a single cell as only child of an element and it will autoupdate too.
~cell1
inside the cell=
macro will result in a Cell
object being embedded in the result code
Also you probably don't want to put cells inside cells like your snippet. No good things come out of that 😉
@mynmoto To make sure I understand, you're saying it has to be the only child of an element? So (text my-cell "some text" another-cell) won't work?
So text
is a special case, it does string concatenation. You can do (h/text "~{my-cell} some text ~{another-cell}")
In a html element like div there is the single child thing I mentioned. So you can do:
Here's why I have a cell inside a cell. I have a list of things, each of which has an "edit" button. The edit button pops open a modal dialog with fields for editing that particular item in the list. So a cell associated with the modal dialog needs to hold the current lens into the data where the editing will occur. At this point, I want to get this way to work because I'm learning a lot by doing this. But once I get this working, I'd be happy to re-engineer it with another approach. Is there a better way to model that?
No, they don't. But you will receive the new value of the lens as argument of the lens callback.
https://github.com/hoplon/javelin has better examples of how that works, not sure if you saw those.
Yeah, I read through those, and I tried to test some of this out manually with println statements, but inserting those statements in many cases changed the behavior, making it difficult to figure out what was going on.
@puzzler do you have some code somewhere public? It's easier to see what you are doing...
Also there is for-tpl
for dealing with lists on hoplon, you probably should be using that.
I don't, sorry. It's a private work project that I'm testing this out on. At some point, I may be able to make a smaller illustrative example.
dm3: this was part if my dabbling with pick and picks, as i recall - creating primitives for lists of items where one or many could be selected, and used to form controls like radio buttons, check boxes, dropdowns and other types of menus.
@puzzler the changing behavior seems odd, it shouldn't happen, if you have a way to reproduce I would like to take a look.
For the lens, I'm just using the path-cell shown here: https://github.com/hoplon/javelin#lenses
jumblerg: is there a way to force an attribute on all the sub-elems? Like if I want to make everything userSelect = true
I put one of these path-cells inside another cell which is used to govern which part of the data structure the modal dialog edits.
@dm3: user select is a different thing entirely, it is disabled by default since we're building webapps and not docs.
So there's a cell called something like modal-dialog-location-to-edit, and since it contains a path-cell and gets one level of deref-ing in many positions, I just use @modal-dialog-location-to-edit in those locations, which seems to work.
you don't want to click a button and have all the text around it selected and highlighted
The cell inside a cell is tricky because changes in the inside cell will not trigger updates.
The other tricky thing I'm trying to do is that I have two cells, let's call them cell1 and cell2. When cell1 is nil, I want it to get filled in with the value in cell2 (think of cell2 as holding the default value for cell1). Subsequent changes to cell1 should simultaneously update cell2. This is proving to be much harder to implement than I expected because the cells auto-deref in places where I don't want them to. Now that you've given me a workaround to deal with that, I can try again.
there's also this: https://github.com/hoplon/ui/blob/master/src/hoplon/ui.cljs#L186
(defn color [ctor]
"set the background color an the inner element."
(fn [{:keys [c o m v l] :as attrs} elems]
(with-let [e (ctor attrs elems)]
(let [l (cell= (if l :text :none))]
...
(bind-in! e [in .-style .-userSelect] l)))))
Using the workaround, it looks like it is possible to establish a bidirectional link between two cells:
@puzzler hey, i generally try to avoid making cells the children of html elements, although you can, it usually means a *-tpl
is preferable
although i think maybe someone said that?
well actually unless it is text
I had the impression that you could put an arbitrarily complex expression in the children of html elements provided you wrap the whole expression in a formula cell, and you only reach for a *-tpl macro if the presence/absence, or type, or number of html elements would be affected by the expression.
sure, so give me an example of something that isn’t inner text
that isn’t the presence/absence, type or number of html elements?
Another question: Can you put arbitrary key-value pairs in the attributes passed to something created with defelem, or do they need to be valid HTML attributes?
arbitrary
actually, the keys recognised by hoplon aren’t even all valid HTML attributes
e.g. :toggle
because deciding what to do with an attribute is based on on!
and do!
It seems to me that many Hoplon components (e.g. form components) need to be passed an "output" cell where changes are written to. Often, this is the same place you are getting data from, but sometimes the input source and output destination are different (e.g., an item in a checklist needs to get the item info from one place, and also needs to know the parent in order to remove the item from the checklist). Is there a standard convention for passing in input and output cells to a component? Do you just include extra key-value pairs in attributes, like :remove-target cell-for-list
.
I understand people usually either do the full frontend -> backend -> frontend cycle when changing data via forms, e.g. 1) create input element with an internal input cell where the intermediate value of the input is stored (e.g. text input) 2) on submit send the data to the backend, update state, receive updated result 3) update the state on the frontend
I'm thinking that a lens is the thing you pass to the component, so you can write the component as if it reads and writes from/to a standalone cell, but then you can pass in something that points to a position in a larger data structure if you want.
or 1) create input element which updates the global state thing (e.g. Datascript), 2) on submit update the database which might sync state to the backend automatically
(defc items [{:label "a" :value "b"}
{:label "c" :value "d"}])
(defc editing nil)
(div
(for-tpl [[idx {:keys [label value]}] (cell= (map-indexed (fn [idx item] [idx item]) items))]
(div
(text "~{label}: ~{value}")
(button
:click #(reset! editing idx)
"Edit")))
(when-tpl editing
(div
(let [edited-item (cell= (nth items editing))]
(input
:value (cell= (:label edited-item))
:change (fn [e]
(assoc-in items [@editing :label] <@U0YNY7ER5>))
:type "text"))
(button
:click #(reset! editing nil)
"Done"))))
One thing I was discussing with micha on the other day is that there is no facilities to create lens on the for-tpl
or loop-tpl
which would be nice to use in this situation.
That's actually really similar to what I have. Main difference is that I wasn't content for editing to store just a number, because that means the editing "component" needs to have hard-coded in it the info about the structure of items
. So instead of storing a number, I store a cursor(path-cell) into the data structure. That means the editing component becomes fully independent of the structure of the data. That still seems to me to be the best generalization of what you've done here.
So :value (cell= (:label edited-item))
becomes :value (cell= (:label @editing))
, for example.
Does that make sense, or should I edit your example to more closely reflect what I did?
@puzzler Well your solution is pretty elegant, if it works we should add to a demo. It would be best if we could avoid the cell in a cell because those things are hard to thing about.
Actually the derefing of the event e is new to me. Does that generally work? I've been using (-> e .-target .-value)
also editing
serves both as a signal that a modal should be open and a container for the values
I need to go for now, but I'll definitely check back later to see if anyone has further thoughts on this. I've learned a lot already from this discussion. Thanks.
@puzzler I don't think your way will work because the cell inside the cell is being replaced, not changed. So the watches won't trigger.
Maybe set-cell!=
could help. I think https://github.com/hoplon/javelin/blob/master/src/javelin/core.clj#L204 is missing a unquote on c
on the right side of the binding?
@mynomoto: I think you are correct
@flyboarder cool, I will open a pr.
(page "index.html")
(defc items [{:label "a" :value "b"}
{:label "c" :value "d"}])
(defn path-cell [c path]
(cell= (get-in c path) (partial swap! c assoc-in path)))
(defc editing nil)
(cell= (prn editing))
(html :lang "en"
(head
(html-meta :charset "utf-8")
(title "Hoplon • List Demo"))
(body
(div
(for-tpl [[idx {:keys [label value]}] (cell= (map-indexed (fn [idx item] [idx item]) items))]
(div
(text "~{label}: ~{value}")
(button
:click #(set-cell!= editing (get-in items [idx :label]) (partial swap! items assoc-in [@idx :label]))
"Edit")))
(when-tpl editing
(div
(input
:value editing
:change (fn [e]
(reset! editing <@U0YNY7ER5>))
:type "text")
(button
:click #(set-cell! editing nil)
"Done"))))))
(defc editing nil)
(defc items [{:label "a" :value "b"}
{:label "c" :value "d"}])
(div
(for-tpl [[idx {:keys [label value]}] (cell= (map-indexed (fn [idx item] [idx item]) items))]
(div
(text "~{label}: ~{value}")
(button
:click #(reset! editing (path-cell items [@idx :label]))
"Edit")))
(when-tpl editing
(div
(input
:value edit-path
:change (fn [e] (reset! @editing <@U0YNY7ER5>))
:type "text")
(button
:click #(reset! editing nil)
"Done"))))
Main difference from version before is changing idx to @idx in the path-cell (since the for-tpl has bound it to a cell).
I think the reason it works is that even though changes to the cell within cell might not be sensed directly, the when-tpl is detecting the change in editing
which causes its body to be reevaluated, thus causing the @editing
to be re-evaluated.
My main complaint about the set-cell! version is that you can't use it with any of the higher-order cell or lens constructors you've built. For example, your path-cell has gone unused, and you've duplicated the body of path-cell in your call to set-cell!. I don't like that aspect.
If there were a version of set-cell! that were a function rather than a macro, and could take a cell as the second input, that gets more interesting.
So of the two versions I posted, the first version seems fragile because it relies on the outer editing
cell being set to nil between values. The version below is more robust because the edit-path
does the intuitive thing and behaves as a reactive cell that updates when editing
is set to any new value.
Question: When you update a cell twice in a row within the body of one function (e.g., (do (reset! my-cell nil) (reset! my-cell val))) will reactive cells dependent on that value see both values (in this case, nil and val) percolating through, or will they only see that latter value?
@micha, no, I haven't seen it. It's not listed on the javelin github page as part of the API.
In this case, I want to set c
to a lens. Does set-formula! take two arities, or is there a set-lens!?
Yeah, I see how one could build a set-lens! function with a combo of set-formula! and set! .-update
I can see how that may be the most elegant solution, especially if set-lens! were already in the API. But I'm reasonably happy with my editing/edit-path solution shown above. It feels intuitive that editing is the cell you put a lens inside, and edit-path reactively gives you the current lens.
My main overall concern is I still don't feel like I have a thorough grasp of what things are captured by formula cells as sources to react to, what things are captured by the html elements in hoplon, when the -tpl macros are necessary, what things are automatically derefed, and the timing of how the propagations all percolate through the system. I feel like it's less "magic" than, say, reagent, and I feel closer to understanding it, but I'm not quite there yet. I haven't been able to get it from the examples alone. Maybe I simply need to sit down and study the javelin source code, although code walking macros can be hard to follow.
@puzzler glad you made that work, you are in the best hands now so I will just watch 😉 I use hoplon and javelin in a simpler way than you are trying to so I will learn new things 😃
@mynomoto Thanks for the tips. I'm very interested in lenses. I think they are a key part of building components that don't care about where they are getting and putting their data.