Clojurians
#hoplon
<
2016-05-06
>

This page is not created by, affiliated with, or supported by Slack Technologies, Inc.

alandipert13:05:07

wrote a short thing here about how we're using hoplon to build new UI at work, http://hoplon.discoursehosting.net/t/hoplon-javelin-client-on-rails-or-other-backend/582

thedavidmeister13:05:12

i’m bumbling through sente to see if i can get it sending/receiving datoms

thedavidmeister13:05:24

hey @alandipert do you know if anyone is making any progress on https://github.com/hoplon/hoplon/pull/129

alandipert13:05:34

@thedavidmeister: just took a look - i'm not sure this is a bug

thedavidmeister13:05:10

why do you say that?

dm313:05:57

@thedavidmeister: I think you should remove that example minimal case from the comment as it doesn't update the cells when input changes

dm313:05:07

your other example in the test is more "correct"

dm313:05:23

and it is a bug which I spent an hour on and didn't get very far

thedavidmeister13:05:41

oh, i mean i can remove stuff if you think it would help

thedavidmeister13:05:49

i thought the minimal case could be helpful for debugging

dm313:05:03

the problem is that the case is incorrect

dm313:05:14

when you type input it doesn't change any state

dm313:05:21

except for the internal DOM state

alandipert13:05:34

ah, i was looking at the code in the comment

thedavidmeister13:05:42

this makes it pretty obvious

dm313:05:54

yes, these are good

dm313:05:01

but the code in the comment is not

thedavidmeister13:05:15

sure thing, i’ll remove the red herring tomorrow :simple_smile:

dm313:05:01

@alandipert: I debugged this and at some point Javelin doesn't propagate the update because the values are equal. The element gets removed, but its new :value doesn't get updated because it's equal to the old value. The actual value in the DOM, however, is empty.

thedavidmeister13:05:07

the value is equal, but it’s two different cells

dm313:05:06

not really

dm313:05:27

the input that gets removed from DOM is the last one

dm313:05:39

and the existing ones are repopulated from the value collection

dm313:05:04

(I'm pretty sure, but still might be wrong)

thedavidmeister13:05:28

i can reproduce this with a datascript db

thedavidmeister13:05:41

if i prn the db, there would be values for two as in the second gif

alandipert13:05:18

i don't understand the thing you're trying to do

alandipert13:05:38

have a list of things that one can edit and delete, is that the basic idea?

thedavidmeister13:05:01

yes, you can add new items by writing in the last input

thedavidmeister13:05:15

if you delete everything in an input, it is removed from the state

thedavidmeister13:05:21

so i want that input to be removed

thedavidmeister13:05:19

it works fine, provided i never have two sequential inputs with the same string value

alandipert13:05:49

i see. yes to do this, normally i manually maintain an index cell

alandipert13:05:23

i will try to make a working example that we can compare to the non-working one

thedavidmeister13:05:10

the datascript example i have with the same issue looks something like this:

thedavidmeister13:05:14

(defn user-input!
  [list-id id k v]
  (let [add     #(vector {:db/id list-id}
                         {:db/id id
                          :item/list-id list-id
                          k v})
        retract #(let [ entity (d/entity @state/conn id)
                        old-value (k entity)
                        ; :db/id is not returned by keys on a datascript entity.
                        k-is-only-key? (= #{:item/list-id k} (set (keys entity)))]
                  [ [:db/retract id k old-value]
                    (if k-is-only-key?
                        [:db/retract id :item/list-id list-id])])]
    (d/transact! state/conn (if (item-data/raw? v)
                                (add)
                                (retract)))))

thedavidmeister13:05:45

if the value is not an empty string, it adds, if it is an empty string it retracts

thedavidmeister14:05:41

maybe this helps if you can see the state as well?

alandipert14:05:44

that's an example of how i would go about it

alandipert14:05:55

the key thing is maintaining unique IDs for each item independently of its value

alandipert14:05:14

it definitely heads toward a database approach. i need to dig into your code more, but the analogue in datascript is probably eid

thedavidmeister14:05:49

@alandipert: that’s different behaviour though

thedavidmeister14:05:56

you’re using keyup and change

thedavidmeister14:05:27

can you make it work within a single event?

alandipert14:05:18

the keyup thing is nonessential... it just makes lis yellow that have "unsaved" changes

alandipert14:05:41

i need to study your examples more, but i think possibly you leave the realm of unidirectional flow

alandipert14:05:51

which would definitely make looptpl behave weird

thedavidmeister14:05:19

+      (let [values (cell {})
 +            values+final (cell= (into values {:new nil}))
 +            on-input! (fn [id v] (reset! values (->>
 +                                                  ; Update values.
 +                                                  (merge @values {id v})
 +                                                  ; Filter empty strings.
 +                                                  (remove #(let [[_ v] %] (= "" v)))
 +                                                  ; Ensure we have a map.
 +                                                  (into {}))))]
 +        (loop-tpl :bindings [[k v] values+final]
 +          (let [id (cell= (if (= :new k) (gensym) k))]
 +            (input
 +              :value v
 +              :id id
 +              :input #(on-input! @id @%))))))

thedavidmeister14:05:29

that’s the relevant bit

thedavidmeister15:05:01

i’m not really sure how that leaves unidirectional flow?

thedavidmeister15:05:57

also, does your example work on input?

thedavidmeister15:05:20

the issue with change is that i think other things might be going on in the browser, like focus, blur, etc.

alandipert15:05:06

it does work with input

thedavidmeister15:05:29

could you flick me a gist?

thedavidmeister15:05:47

i’m not sure why (merge @values {id v}) isn’t basically the same as what you did

alandipert15:05:12

i updated it

alandipert15:05:15

neither am i

thedavidmeister15:05:48

1 sec, i’ll fire it up on my comp

thedavidmeister15:05:56

@alandipert: oh, yes, you’re appending

thedavidmeister15:05:00

but, what about removing?

thedavidmeister15:05:56

it’s missing the bit where, when I delete the last character in an input, that input is removed

alandipert15:05:50

@thedavidmeister: i updated gist

alandipert15:05:12

i'm using :keyup again though

thedavidmeister15:05:26

@alandipert: yeah but that’s two different events again

thedavidmeister15:05:40

try making it happen in a single event

alandipert15:05:59

gotcha, updated gist again

thedavidmeister15:05:23

:input #(if (zero? (count @%)) (delete-item! @id) (edit-item! @id @%)))))))))

thedavidmeister15:05:01

click “append” until you see two consecutive numbers

thedavidmeister15:05:04

then delete the first number

alandipert15:05:35

i hardcoded in a constant for append

alandipert15:05:59

what's buggy about it?

thedavidmeister15:05:21

it deletes both values, and leaves an empty input behind

alandipert15:05:25

the 2nd gif is what i see currently, is that the buggy one?

thedavidmeister15:05:49

it’s important that the two sequential inputs have the same value

thedavidmeister15:05:56

“8” “8” in my example

thedavidmeister15:05:00

then delete the first one

thedavidmeister15:05:50

if i delete one 8

thedavidmeister15:05:56

after i have 2 8’s

thedavidmeister15:05:00

i should have one 8 left

thedavidmeister15:05:34

and i should never have an empty input immediately after processing the state in response to an event

alandipert15:05:16

hm, as far as i cann see my latest thing behaves the right way

alandipert15:05:27

i updated the gist so that every item is created with same text, can you play with it and let me know if it conforms?

thedavidmeister15:05:30

hmm, that does work

thedavidmeister15:05:33

the plot thickens

thedavidmeister15:05:36

what did you change?

thedavidmeister15:05:18

(button :click #(append-item! (str 1)) "Append”)

thedavidmeister15:05:42

or a single character

thedavidmeister15:05:50

(button :click #(append-item! "a") "Append”)

thedavidmeister15:05:07

because f is not foo

alandipert15:05:08

ah! that triggers it

thedavidmeister15:05:49

it’s the value when you hit delete for the last time that matters, not the initial values

thedavidmeister15:05:42

see, i’m not crazy :stuck_out_tongue:

alandipert15:05:30

never thought you were

alandipert15:05:35

it's hard to be crazier than javascript lol

thedavidmeister15:05:54

and actually, it is still broken if you change input to keyup

alandipert15:05:14

i wonder if this is a jquery thing

thedavidmeister15:05:22

so it’s not specific to the event

alandipert15:05:25

like i wonder if the value when the thing is empty is null and not an empty string or something?

thedavidmeister15:05:41

i really do not know ¯\(ツ)

alandipert15:05:11

the plot is so thick right now

thedavidmeister15:05:15

you can “kind of” work around it by doing the processing in two steps

thedavidmeister15:05:42

then on keyup, filter the db for any empty strings

thedavidmeister15:05:53

but that’s a total hack

thedavidmeister15:05:45

and doesn’t feel as slick to use if you have things responding to keydown and some to keyup, seemingly randomly

thedavidmeister15:05:33

what I guessed, based on not knowing the internals

thedavidmeister15:05:55

is that, after an input is deleted, something in the system checks to see if it needs to repopulate the values

thedavidmeister15:05:09

and for each value that is different to what it was before, it will put them back in

thedavidmeister15:05:24

but it will ignore the input if the :value dereferences to the same values, even if they were from two different cells

alandipert15:05:16

i'm making a little progress here

alandipert15:05:28

by the time delete is pressed, the value of the input is gone

alandipert15:05:01

then it seems like when that element is removed by hoplon, the browser shifts focus to the adjacent input and clears it

alandipert15:05:06

without firing an input event

alandipert15:05:34

what is still super weird is that this only manifests with single-char initial inputs

alandipert15:05:54

are you testing in chrome also btw?

thedavidmeister15:05:24

well actually, i can make it manifest for ”foo” if i do it in one go

alandipert15:05:43

oh if you delete all of foo without deleting from anything else you mean?

alandipert15:05:28

gotcha, yeah. seeing the same thing

thedavidmeister15:05:08

the important bit is just that the two inputs have the same value at the moment you hit delete

thedavidmeister15:05:19

which is a lot easier to trigger for single characters

thedavidmeister15:05:55

so f won’t match foo if you’re deleting one character at a time

thedavidmeister15:05:21

yeah, i’m testing in chrome

alandipert15:05:48

i don't think it's related to javelin directly, because i see the right thing happen - the empty element is removed

alandipert15:05:01

the problem seems to be jumping focus to the nearest sibling and clearing its value

alandipert15:05:24

in a way that doesn't trigger an input event for that sibling - so the input looks empty, but represents a nonempty value

thedavidmeister15:05:28

but i don’t think it is going to a sibling

thedavidmeister15:05:47

in the above gif, “input 3” is where the cursor stays the whole time

thedavidmeister15:05:21

the difference is just whether a new value from “index 3” is put into it, based on the state

alandipert15:05:00

ok i'm tracking now

thedavidmeister15:05:15

step 1: event for “input” fires

thedavidmeister15:05:41

step 2: the state is updated

thedavidmeister15:05:07

step 3: something checks if the new state = the new dom, if not, make a update-required? flag

thedavidmeister15:05:34

step 4: the browser deletes the value in the input element, in response to the delete key

thedavidmeister15:05:54

step 5: if update-required? was true, put the new state value back into the dom

thedavidmeister15:05:46

there’s no extra events, focus/blur/change, etc. because it’s actually the same input element the whole time

thedavidmeister15:05:46

that’s why I called the PR I put up “DOM churn formula cell bug"

thedavidmeister16:05:40

because micha told me a while back that the DOM is not being 100% rebuilt from scratch each time the state changes, for performance

thedavidmeister16:05:01

so hoplon/javelin intelligently update the DOM in situ, based on cells, do! and on!

thedavidmeister16:05:14

but in this case, it seems to be a bit inconsistent in exactly how that plays out

alandipert16:05:24

right. but in this case, the resource that looptpl is responsible for managing, is being modified underneath, and it doesn't know it needs to repaint

thedavidmeister16:05:34

except that sometimes it does

thedavidmeister16:05:10

which is why i am confused, and saying it looks like a bug

thedavidmeister16:05:25

if it just picked one way to behave and stuck with that, i’d be more comfortable

alandipert16:05:42

thanks for patiently explaining it

alandipert16:05:46

i'm a Bug Believer now also

thedavidmeister16:05:38

well, i’ve been looking at it for a while

thedavidmeister16:05:15

it’s not really obvious what is going on the first time you see it...

thedavidmeister16:05:55

well anyway, it’s 2:15am here

thedavidmeister16:05:01

so i’m going to leave you with that riddle :wink:

alandipert17:05:59

@thedavidmeister: thanks :simple_smile: see ya

micha18:05:38

i think javelin is doing the right thing

micha18:05:52

when you have 4 inputs all with the same text

micha18:05:17

populated from a data structure with 4 things in it, which each contain the same text

micha18:05:30

when you remove say the first thing

micha18:05:42

from the data structure, that is

micha18:05:01

(:txt (nth db 0))

micha18:05:04

that doesn't change

micha18:05:18

so it won't trigger the attribute to update

micha18:05:32

this is how it's supposed to work

micha18:05:21

ah actually here is a better fix

dm318:05:53

what did you change to make it work?

micha18:05:10

instead of just removing the item via dissoc in delete-item! i first set the text to nil, then dissoc

micha18:05:54

that ensures that the text will not be the same in that formula cell in the loop-tpl during the entire operation

micha18:05:48

because if the text in the next input (the text that will be replacing the text in the current input) doesn't change then the :value attribute won't update

micha19:05:14

so my change there just adds (swap! db assoc id nil) and then (swap! db dissoc id)

micha19:05:08

my only change was to add line 17

micha19:05:18

in the gist above

alandipert19:05:06

quite subtle

dm319:05:56

yeah, I think this looks like it works how it's supposed to work to exactly 1 user of Hoplon

dm319:05:03

namely Micha

micha19:05:13

i mean it's a corner case

micha19:05:19

the cells are doing the right thing

micha19:05:25

and the attributes are doing the right thing

dm319:05:25

yep, seems like it

micha19:05:36

so another solution is to make a special attribute that handles this

micha19:05:49

i doit one sec

alandipert19:05:05

it seems like less a javelin corner case and more a mental pothole in the relationship between cells and dom via loop-tpl

micha19:05:25

it has to do with bidirectional data binding

micha19:05:31

that's the problem really

micha19:05:49

bidirectional data binding is never going to be a cleanly solved thing

micha19:05:04

there is no general solution there i don't think

alandipert19:05:07

i guess another solution is to flow a distinct value through further

alandipert19:05:36

like the gensym doesn't cause repaint because it's been formula'd out by the time the cell val is mapped to the value attribute

alandipert19:05:00

hm - maybe some kind of wrapper object around the string that has an identity based on id + string

dm319:05:34

you'll probably run into some edge cases anyway

micha19:05:52

ok here we go:

micha19:05:11

a special bidirectional databinding :value2 attribute also solves the problem

alandipert19:05:48

(defn wrap [s]
  (js-obj "toString" (constantly s)))

alandipert19:05:09

another weird way to solve it

alandipert19:05:50

then you do:

(defn append-item!  [txt]     (swap! db assoc (gensym) (wrap txt)))
(defn edit-item!    [id txt]  (swap! db assoc id (wrap txt)))

micha19:05:55

i'm not sure the answer is to defeat equality tests

micha19:05:07

that would propagate to other formulas and stuff

micha19:05:21

the crux of the issue is the input element i think

micha19:05:45

and the fact that we want it to both have the value that's stored in a cell and also the value you're typing

micha19:05:04

and the cell is a formula

micha19:05:37

the :value2 attribute knows about how to correctly bind to an input cells 2-way style

micha19:05:02

like it knows that typing in the input gets its internal state out of sync with the formula

micha19:05:11

so it also fires when you do that

alandipert19:05:17

definitely seems right to solve bidi in specific cases

alandipert19:05:27

where all the conditions are known it can be solved precisely

alandipert19:05:15

does keyup catch things like pasting also?

micha19:05:24

probably not

micha19:05:49

it may be impossible to know that

micha19:05:53

without polling

alandipert19:05:27

aha dude i like the way you use gensym

micha19:05:27

i guess what we really want is to have attributes be more like methods on class instances

alandipert19:05:40

since it sorts the needed way it's both distinct and monotonic

micha19:05:02

so HTMLInput can have its own :value attribute with its own specific implementation

micha19:05:27

that could be aware of how to know when element internal state is out of sync with cell

alandipert19:05:28

you mean as opposed to that attribute being accessible to all types of dom node?

alandipert19:05:37

e.g. the dispatch is double

micha19:05:43

it could be a multimethod or protocol

micha19:05:49

the implementation

micha19:05:58

double dispatch

piotrek19:05:01

Hi, what’s your recommendation for communication with the REST web services from hoplon application?

piotrek19:05:59

I would like to make a “service” layer in my cljs app which would expose functions hiding the details of the actual communication implementation

piotrek19:05:24

but I was wondering what is the preferred way to define such functions which are async

piotrek19:05:38

future/promise? core.async? other solutions?

piotrek19:05:51

callbacks?

alandipert19:05:24

have you tried castra?

piotrek19:05:47

let’s say I need a function like (defn get-user-profile [username] …) - how would you handle the asynchronous aspect of it?

piotrek19:05:34

I saw it briefly

piotrek19:05:32

and I can see that it uses cells for communication with outside world

alandipert19:05:47

the idea with it is you define cells to represent the data you want to show

alandipert19:05:54

so (def user-profile (cell nil))

piotrek19:05:08

I could use cells as my abstraction

alandipert19:05:18

it's hard to go wrong if you do

alandipert19:05:33

basically, pretend like the data is always there, and then separately have machinery that updates the data from the server

piotrek19:05:47

in case I change my mind I could replace castra with another communication library but my service functions could still use cells as the api interface

alandipert19:05:03

you definitely want to use cells as the "read" part of your remote interface

alandipert19:05:17

since you can attach UI to them and the UI doesn't need to care about the processes responsible for updating their values

piotrek19:05:24

I guess with hoplon cells are the way to go

alandipert19:05:40

but like to implement the calls and the updating of the cells, other tech comes into play

alandipert19:05:45

in castra i think we use promises

piotrek19:05:50

I am just starting something small so I could even mock my services for now

alandipert19:05:54

that's for the "write" interface

piotrek19:05:56

and have them to fill the cells with dummy data

alandipert19:05:02

that's exactly how we prototype

piotrek19:05:04

and provide the real castra endpoints later

alandipert19:05:27

you can update cells from console etc. that way too, simulating server interactions and iterating on the UI without having a backend yet

piotrek19:05:54

I am wondering what’s the hoplon philosophy on maintaining the app state

piotrek19:05:13

is it more like om or re-frame approach to define some “fat” cells

piotrek19:05:21

or rather granular pieces of data?

alandipert19:05:53

cells make it not really matter how you divide your state data, as behind the scenes they form a consistent graph similar to a map in an atom

piotrek19:05:54

for example a page with list of users

alandipert19:05:12

except unlike a map in an atom not every code that knows about the big atom can see all the other data in it

piotrek19:05:15

I can define a cell with a list of users fetched from the server

piotrek19:05:29

I would also need to handle paging

piotrek19:05:38

if I have a lot of entities

piotrek19:05:00

so I could define a defelem like users-list

piotrek19:05:07

how should users-list element get its data? should it initiate the call to the server providing its local scoped cell?

piotrek19:05:37

or should I initiate such a call from outside of the element and pass it the cell?

alandipert19:05:18

i think it depends on you particular case

alandipert19:05:38

if there's one users-list data per-client, then it would maybe make sense for it to be globally managed

alandipert19:05:58

vs. interacting in the same app with multiple services that all have separate lists of users

piotrek19:05:18

well, let’s assume that I have a multitenant saas app

piotrek19:05:42

and I first choose the tenant for which I would like to display the users list

piotrek19:05:22

so I guess I could define defelem users-list which accepts tenant-id and the users-list will fetch the users for that specific tenant-id

alandipert19:05:14

could be, or maybe there's a global tenant-id cell and a users-list formula cell derived from tenant-id and data fetched from the server

piotrek19:05:18

it’s rather the question if the call to the server should be initiated inside of the users-list definition or rather it should just get the cell without knowing how the data will be put there

micha19:05:48

just getting the cell seems pretty good to me

alandipert20:05:21

i generally put "write" functions in their own separate namespace, maybe calling them from inside event handlers in defelems

piotrek20:05:37

those are the things that I am missing about how I should structure my hoplon app

alandipert20:05:44

so that defelems receive cells to display but defer to external functions to write/modify

piotrek20:05:01

I have a following use case:

piotrek20:05:09

I have a page where I can query for tenants

piotrek20:05:35

by different criteria (id, name, pricing plan or whatever) and display the list of matching tenants

piotrek20:05:21

when I choose one of the tenants I want to change the page to display a general info about the tenant with some subtabs with users list, invoicing etc.

piotrek20:05:47

should I use some client side routing to change the views (tenants list -> tenant page)?

piotrek20:05:57

or open a different hoplon page?

piotrek20:05:05

what’s your recommendation?

alandipert20:05:19

probably client side routing

piotrek20:05:24

with a separate page a global cell for tenant id makes more sense to me

piotrek20:05:45

otherwise why should I define a global tenant id cell when I am just displaying a list of tenants?

alandipert20:05:09

my idea with the current-tenant-id is that it would be nil if a particular tenant is not yet selected

alandipert20:05:38

and perhaps the tenant detail markup is hidden when it's nil

piotrek20:05:15

ok, when I think about it and based on your suggestions to use event handlers to initiate some state changes I could define a cell locally there and pass it to the tenant-view element

alandipert20:05:36

right - tenant-view could take a cell that contains the current tenant-id or nil

piotrek20:05:00

or even a cell with the whole tenant info data

piotrek20:05:17

so the event handler could do something like:

piotrek20:05:20

does it make sense?

piotrek20:05:46

btw, is there any example showing how to handle client side routing? extra points if it shows how to handle security :simple_smile:

alandipert20:05:20

there is a route-cell in javelin that is a cell of the current route, you can use it to know where you are

alandipert20:05:38

re: security, castra has csrf protection

micha20:05:07

there aren't any security considerations to worry about for client-side routing, because it's all in the client

piotrek20:05:10

well, it depends :simple_smile: - if there are multiple user roles with different access levels they might not be able to see some of the views

piotrek20:05:31

but I guess it is too specific in each application to have a general solution

piotrek20:05:51

@alandipert: where can I find route-cell? I cannot see it in javelin sources :confused:

piotrek20:05:52

hmm, I just googled that it was deprecated

alandipert20:05:36

the only thing deprecated about it was that it was in javelin, not hoplon. it just moved

piotrek20:05:00

@alandipert: thanks!

piotrek20:05:24

so I define a global cell like (def current-route (route-cell))

alandipert20:05:39

maybe another helpful thing to know in general about hoplon is that it's not as prescriptive as e.g. re-frame

alandipert20:05:46

you have way more options, but way less guidance

alandipert20:05:04

but you can choose to structure your app the re-frame way or any other way that feels natural

piotrek20:05:34

yes, I like it in hoplon

alandipert20:05:38

so there won't ever be one right way to organize, you'll just need to play around a little

piotrek20:05:39

it’s more like the whole clojure world

alandipert20:05:40

see what sticks

piotrek20:05:50

you can glue small parts together

piotrek20:05:56

it’s not a one big framework

alandipert20:05:16

re: route-cell yup, that's how

piotrek20:05:18

ok, so now I need to figure out how to glue my current-route cell with secretary routing :simple_smile:

alandipert20:05:52

not necessarily, your own routing code can be very simple

alandipert20:05:08

i definitely recommend starting that way withy our own simple things, vs getting secretary working

micha20:05:39

i like the bidi library for the route parsing i think

piotrek20:05:49

I can probably start with simple routes like /tenants-list or /reports

micha20:05:59

secretary is not that useful because it tries to be a whole mvc situation

micha20:05:08

bidi just does bidirectional route parsing

piotrek20:05:08

but then I would like to handle route params like tenant-id or something

micha20:05:17

perfect to have in a formula

piotrek20:05:22

ok, let me take a look at bidi

micha20:05:34

and the cool thing about it is that you can use it to generate links symmetrically

micha20:05:39

that's the real benefit

micha20:05:28

like bidi would parse #/foo/bar/123 -> {:main "foo" :sub "bar" :id 123} perhaps

micha20:05:04

but you could also then do the other way: {:main "omg" :sub "whoo" :id 42} -> #/omg/whoo/42

piotrek20:05:24

ok, so bidi returns some data when it matches a specific route (or nil if no match was found)

micha20:05:47

yeah the data would be in a formula cell that could could then dispatch on

piotrek20:05:53

and this data is shaped like {:handler something :route-params {map with params}}

micha20:05:07

what it parses to is up to you, i think

micha20:05:15

you can make it have whatever shape you like

piotrek20:05:24

the question is if I could store the element function as something

piotrek20:05:43

or just a function which I could call with the route params

piotrek20:05:53

and that function would return my view for the current-view cell

micha20:05:02

well the other way around

piotrek20:05:03

so I could define my current-view cell as:

micha20:05:06

like this:

micha20:05:23

(html
  (body
    (div :id "main" :toggle (cell= (= (:main route) "main"))
      (content for main screen))
    (div :id "otherpage" :toggle (cell= (= (:main route) "other"))
      (content for other screen))))

piotrek20:05:12

I was thinking about something like that

piotrek20:05:35

then my html could just define (div @current-view)

piotrek20:05:47

or something like that

piotrek20:05:07

so I could just define my routes with handlers as my functions returning a specific element

piotrek20:05:39

instead of defining the routes in one place and then have a list of divs with toggles based on the matching route

piotrek20:05:46

or maybe I am wrong?

alandipert20:05:04

if you deref current-view like that, it won't continue updating as the cell changes

alandipert20:05:14

since @ extracts the value from a cell at one point in time

piotrek20:05:28

yeah, I would need to enclose it in (cell= …)

alandipert20:05:52

there is actuall a dom leak with that particular approach

alandipert20:05:00

if-tpl and cond-tpl were added recntly to hoplon to mitigate

alandipert20:05:07

the safest thing is to use :toggle

alandipert20:05:11

per micha's example

micha20:05:27

piotrek: the (div (cell= ...)) thing is like the reactjs way

piotrek20:05:19

well, I thought it would be better to have one function that will produce an element for inclusion in my dom based on some data

alandipert20:05:19

(also @levitanong is awesome for writing that page)

piotrek20:05:33

then have a list of all the possible elements included with toggling logic

piotrek20:05:27

especially if I would have to handle route params there

piotrek20:05:48

it would be mixing toggling with extracting route params and passing them to the toggled element

piotrek20:05:57

I could actually define those functions so they use if-tpl internally

piotrek20:05:06

would that work?

micha20:05:11

yeah that's pretty much the same thing anyway

micha20:05:30

the key thing is that you're not destroying dom elements when the data changes

micha20:05:42

if-tpl will have more edge cases than :toggle

micha20:05:52

because it's removing things from the dom and reinserting them

micha20:05:08

so like for example if there is code that inspects the width of thelement

micha20:05:15

that will be 0 when the element isn't in the dom

micha20:05:19

things like that

micha20:05:51

usually you shouldn't have lots of :toggle things

micha20:05:56

even if your application is com[plex

micha20:05:15

usually you have some finite number of screens

micha20:05:18

and that's it

micha20:05:40

inside a screen you use loop-tpl and things for dynamically allocated elements

micha20:05:07

it's extremely rare to have a screen that needs to completely change its shape

micha20:05:16

i can't think of a single example

micha20:05:22

in a real application

piotrek20:05:30

@micha: you wrote "the (div (cell= ...)) thing is like the reactjs way"

piotrek20:05:38

yes, it’s like in react js

piotrek20:05:52

but I think it would be actually good to be able to code like that

piotrek20:05:20

so you don’t have to do manual book keeping of dom elements

piotrek20:05:53

is it a different philosophy in hoplon or some technical difficulties to allow such usage?

piotrek20:05:24

on the other hand I could define two separate global cells like current-view-id and current-view-params and have them updated dynamically by formula cell connected to route-cell and then enumerate all the possible views and have the toggle based on matching the specific current-view-id:

alandipert20:05:49

hoplon encourages one to work directly with the live/mutable dom nodes, whereas the idea wiht react is to synchronize immutable trees to mutable ones, efficiently

alandipert20:05:06

the browser already does what react does, of course

piotrek20:05:07

(def current-view-id (cell= view id (e.g. a keyword) based on route-cell and bidi matching))
(def current-view-params (cell= params map from bidi matching))

(html
  (body
    (div :id "main" :toggle (cell= (= current-view-id :main))
      (main-view :params current-view-params)
    (div :id "otherpage" :toggle (cell= (= current-view-id :other))
      (other-view :params current-view-params))))

piotrek20:05:30

ok, enough talking, time to practice :simple_smile:

piotrek20:05:46

thanks a lot for your help @alandipert and @micha

alandipert21:05:40

no problem, rock on

piotrek21:05:01

one qq about adzerk/cljs-console

piotrek21:05:27

I have a value like a map and I would like to log it using log/info

piotrek21:05:59

I tried:

(let [data {:a 1 :b 2}]
  (log/info “Data: ~data”))
but it doesn’t work

piotrek21:05:13

how can I interpolate a whole object?

alandipert21:05:48

you could do "Data: ~(pr-str data)"

micha21:05:12

@piotrek: or (log/info "Data: ~{data}")

micha21:05:38

if you have just a var you would use curly braces

micha21:05:48

if you have an expression you want to interpolate you can use parens