Fork me on GitHub
#hoplon
<
2016-12-28
>
micha00:12:29

@mynomoto very interesting!

mynomoto00:12:16

@micha your suggestion of nil punning everywhere worked like a charm. Thanks!

puzzler09:12:47

I'm having an extremely difficult time figuring out when cells are automatically deref'ed, and when you need to explicitly deref them.

puzzler09:12:00

What are the rules governing this behavior?

puzzler10:12:50

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.

dm312:12:43

@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? 🙂

dm312:12:54

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 🙂

mynomoto13:12:45

@puzzler As a general rule, cells are automatically derefed inside formula cells.

mynomoto13:12:32

On other situations like in function calls you need to deref then yourself.

mynomoto13:12:44

About (cell= (reset! ~cell1 cell2)) you can do (cell= (reset! ~(cell cell1) cell2)) iirc. Or create a function like:

mynomoto13:12:09

(defn my-reset [new-value]
  (reset! cell1 new-value))

(cell= (my-reset cell2))

dm313:12:34

~(partial reset! cell1)

puzzler13:12:49

@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?

puzzler13:12:46

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" ~?

puzzler13:12:24

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=?

mynomoto13:12:29

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.

dm313:12:03

~cell1 inside the cell= macro will result in a Cell object being embedded in the result code

dm313:12:20

instead of the symbol for cell1 hoisted out into the formula invocation

dm313:12:52

or cell1 symbol preserved for reset! to work properly

dm313:12:07

(haven't tested, so might be wrong :))

mynomoto13:12:43

Also you probably don't want to put cells inside cells like your snippet. No good things come out of that 😉

dm313:12:13

as far as I understand, Javelin wasn't designed with nested cells in mind

dm313:12:19

it's possible, but there's no automatic GC

puzzler13:12:25

@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?

mynomoto13:12:45

@puzzler also (cell= (reset! ~(cell cell1) cell2))

puzzler13:12:17

What happens in the callback of a lens?

mynomoto13:12:21

So text is a special case, it does string concatenation. You can do (h/text "~{my-cell} some text ~{another-cell}")

puzzler13:12:31

OK, but you're saying I shouldn't expect it to work, for example, in a general div?

mynomoto13:12:33

The callback of a lens will be called with the new value of the lens as argument.

mynomoto13:12:14

In a html element like div there is the single child thing I mentioned. So you can do:

mynomoto13:12:13

(defc my-span (span "aaa"))

(div my-span)

(reset! my-span (p "bbb"))

puzzler13:12:18

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?

mynomoto13:12:23

That should work.

puzzler13:12:51

I meant, do cells auto-deref in the callback of a lens?

mynomoto13:12:38

No, they don't. But you will receive the new value of the lens as argument of the lens callback.

mynomoto13:12:58

https://github.com/hoplon/javelin has better examples of how that works, not sure if you saw those.

puzzler13:12:40

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.

mynomoto13:12:18

@puzzler do you have some code somewhere public? It's easier to see what you are doing...

mynomoto13:12:13

Also there is for-tpl for dealing with lists on hoplon, you probably should be using that.

puzzler13:12:14

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.

mynomoto13:12:40

Can you paste the relevant lens section?

jumblerg13:12:54

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.

jumblerg13:12:46

i don't think i have that stuff right. same goes for comp.

mynomoto13:12:00

@puzzler the changing behavior seems odd, it shouldn't happen, if you have a way to reproduce I would like to take a look.

puzzler13:12:03

For the lens, I'm just using the path-cell shown here: https://github.com/hoplon/javelin#lenses

dm313:12:26

jumblerg: is there a way to force an attribute on all the sub-elems? Like if I want to make everything userSelect = true

puzzler13:12:49

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.

jumblerg13:12:37

@dm3: user select is a different thing entirely, it is disabled by default since we're building webapps and not docs.

dm313:12:51

yeah, my question got mixed up there, sorry 🙂

puzzler13:12:54

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.

dm313:12:56

was 2 questions

jumblerg13:12:18

you don't want to click a button and have all the text around it selected and highlighted

mynomoto13:12:20

The cell inside a cell is tricky because changes in the inside cell will not trigger updates.

dm313:12:22

so 1. Is there a way to "inherit" an attribute unconditionally?

jumblerg13:12:17

i think you get this feature for free via the browsers inheritance mechanism.

puzzler13:12:20

What would a better architecture for the modal dialog look like?

dm313:12:48

but nested elems override the property, right?

jumblerg13:12:59

i think it is a css inherited property.

jumblerg13:12:45

offhand, i don't think so, although could be wrong. otg atm.

dm313:12:13

it seems like they do from my tests

puzzler13:12:18

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.

jumblerg13:12:00

not css-inherited

jumblerg13:12:16

we could make it

dm313:12:14

(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)))))

dm313:12:46

doesn't this mean that each elem will set its' own userSelect value?

dm313:12:00

I think it'd be right to make it act as if it's inherited

dm313:12:05

given the default is inverted

puzzler13:12:32

Using the workaround, it looks like it is possible to establish a bidirectional link between two cells:

jumblerg13:12:43

try (cell= (or l "inherit")) for now

jumblerg13:12:05

i think so too, probably

thedavidmeister13:12:17

@puzzler hey, i generally try to avoid making cells the children of html elements, although you can, it usually means a *-tpl is preferable

thedavidmeister13:12:22

although i think maybe someone said that?

dm313:12:07

jumblerg: thanks for looking into it 🙂

thedavidmeister13:12:10

well actually unless it is text

jumblerg13:12:53

if it works well in practice, make the change and commit! :)

puzzler13:12:36

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.

thedavidmeister13:12:57

sure, so give me an example of something that isn’t inner text

thedavidmeister13:12:07

that isn’t the presence/absence, type or number of html elements?

puzzler13:12:54

Sure, I see your point.

puzzler13:12:24

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?

thedavidmeister13:12:16

actually, the keys recognised by hoplon aren’t even all valid HTML attributes

thedavidmeister13:12:41

because deciding what to do with an attribute is based on on! and do!

puzzler14:12:05

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.

dm314:12:10

puzzler: aren't you supposed to uses lenses for this reason?

dm314:12:25

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

puzzler14:12:58

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.

candera14:12:24

Or maybe a function?

dm314:12:50

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

mynomoto14:12:57

(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"))))

mynomoto14:12:36

@puzzler 🆙 is how I would do it.

mynomoto14:12:32

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.

puzzler14:12:38

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.

puzzler14:12:36

So :value (cell= (:label edited-item)) becomes :value (cell= (:label @editing)), for example.

puzzler14:12:43

Does that make sense, or should I edit your example to more closely reflect what I did?

mynomoto14:12:07

@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.

dm314:12:02

you can override the cell with something like javelin.core/set-cell!=

puzzler14:12:13

Actually the derefing of the event e is new to me. Does that generally work? I've been using (-> e .-target .-value)

dm314:12:30

also editing serves both as a signal that a modal should be open and a container for the values

puzzler14:12:07

Yes, I agree that editing is serving a dual purpose.

dm314:12:29

deref on js/Event is implemented to get the value IIRC

puzzler14:12:35

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.

mynomoto15:12:12

@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.

mynomoto15:12:13

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?

flyboarder16:12:58

@mynomoto: I think you are correct

mynomoto16:12:16

@flyboarder cool, I will open a pr.

mynomoto16:12:48

@puzzler set-cell!= and set-cell! make that work:

mynomoto16:12:56

(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"))))))

mynomoto16:12:18

@puzzler it only needs this pr above merged and a new release of javelin 😉

mynomoto16:12:10

Someone more responsible should say if doing that is a good idea but it works...

micha16:12:13

@mynomoto maybe we should merge the performance improvements

micha16:12:17

in javelin

micha16:12:26

plus this other one

mynomoto16:12:07

@micha that would be cool!

mynomoto18:12:19

That set-cell! is pretty rad! javelin

puzzler22:12:40

@mynomoto This is working for me:

puzzler22:12:44

(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"))))

puzzler22:12:32

Main difference from version before is changing idx to @idx in the path-cell (since the for-tpl has bound it to a cell).

puzzler22:12:08

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.

puzzler22:12:13

Does that sound correct?

puzzler22:12:00

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.

puzzler22:12:57

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.

puzzler22:12:10

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.

puzzler22:12:14

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?

micha22:12:48

@puzzler have you seen the javelin.core/set-formula! function?

micha22:12:05

no macros involved

micha22:12:42

(defc a 42)
(defc b 42)
(defc= c (+ a b))

micha22:12:45

you can then do

puzzler22:12:54

@micha, no, I haven't seen it. It's not listed on the javelin github page as part of the API.

micha22:12:18

(set-formula! c (fn [a b] (- a b)) [a b])

micha22:12:26

weird yeah it should be listed there

puzzler22:12:22

In this case, I want to set c to a lens. Does set-formula! take two arities, or is there a set-lens!?

puzzler22:12:32

I'll check the source.

micha22:12:13

ah that is missing form the API, but you can do a workaround

micha22:12:30

so i think you can do like

puzzler22:12:18

Yeah, I see how one could build a set-lens! function with a combo of set-formula! and set! .-update

micha22:12:58

(do (set-formula! c (fn ...) [a b]) (set! (.-update c) some-callback-fn) c)

puzzler22:12:52

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.

puzzler22:12:23

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.

micha22:12:13

the javelin cell= macro just identifies all symbols that could refer to cells

micha22:12:22

and considers them sources to react to

micha22:12:37

it does full macroexpansion before it walks

micha22:12:51

but it only sees what is there at compile time

micha22:12:01

that's why something like this works:

micha22:12:18

(defn update-foo [x]
  (reset! foo x))

(cell= (when bar (update-foo baz)))

micha22:12:51

oops i mean

micha22:12:38

(defn return-foo [] foo)

(cell= (when bar (reset! (return-foo) baz)))

micha22:12:56

foo is hidden from the macro there

mynomoto22:12:22

@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 😃

puzzler23:12:54

Presumably a cell= inside a cell- would work since all the macros are expanded.

puzzler23:12:09

I mean cell= in cell=

puzzler23:12:04

@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.

mynomoto23:12:13

@puzzler yeah they show promise, I think having a special for-tpl that creates lenses would be great for allowing new patterns.

mynomoto23:12:37

Navigating over the history is pretty cool. Advanced debug is on the way of github-client.