This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-05-06
Channels
- # admin-announcements (6)
- # beginners (147)
- # boot (9)
- # braveandtrue (5)
- # cider (11)
- # cljsjs (1)
- # cljsrn (4)
- # clojure (82)
- # clojure-greece (9)
- # clojure-poland (9)
- # clojure-russia (288)
- # clojure-taiwan (2)
- # clojure-uk (73)
- # clojurescript (123)
- # consulting (3)
- # cursive (26)
- # datascript (4)
- # datomic (32)
- # dirac (56)
- # emacs (11)
- # flambo (2)
- # hoplon (425)
- # jobs-rus (1)
- # lein-figwheel (3)
- # leiningen (16)
- # luminus (42)
- # mount (7)
- # om (1)
- # om-next (2)
- # onyx (8)
- # other-languages (146)
- # quil (3)
- # re-frame (17)
- # reagent (6)
- # spacemacs (2)
- # uncomplicate (8)
- # untangled (71)
- # vim (2)
- # yada (49)
ahoy!
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
i’m bumbling through sente to see if i can get it sending/receiving datoms
hey @alandipert do you know if anyone is making any progress on https://github.com/hoplon/hoplon/pull/129
@thedavidmeister: just took a look - i'm not sure this is a bug
why do you say that?
@thedavidmeister: I think you should remove that example minimal case from the comment as it doesn't update the cells when input changes
oh, i mean i can remove stuff if you think it would help
i thought the minimal case could be helpful for debugging
ah, i was looking at the code in the comment
this makes it pretty obvious
sure thing, i’ll remove the red herring tomorrow
@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.
the value is equal, but it’s two different cells
i can reproduce this with a datascript db
if i prn the db, there would be values for two a
s in the second gif
i don't understand the thing you're trying to do
have a list of things that one can edit and delete, is that the basic idea?
yes, you can add new items by writing in the last input
if you delete everything in an input, it is removed from the state
so i want that input to be removed
it works fine, provided i never have two sequential inputs with the same string value
i see. yes to do this, normally i manually maintain an index cell
i will try to make a working example that we can compare to the non-working one
the datascript example i have with the same issue looks something like this:
(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)))))
if the value is not an empty string, it adds, if it is an empty string it retracts
maybe this helps if you can see the state as well?
¯\(ツ)/¯
@thedavidmeister: https://gist.github.com/alandipert/2dc07f8fb53bda7cabd4150e988d894f
that's an example of how i would go about it
the key thing is maintaining unique IDs for each item independently of its value
it definitely heads toward a database approach. i need to dig into your code more, but the analogue in datascript is probably eid
@alandipert: that’s different behaviour though
you’re using keyup and change
can you make it work within a single event?
the keyup thing is nonessential... it just makes lis yellow that have "unsaved" changes
i need to study your examples more, but i think possibly you leave the realm of unidirectional flow
which would definitely make looptpl behave weird
+ (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 @%))))))
that’s the relevant bit
i’m not really sure how that leaves unidirectional flow?
also, does your example work on input
?
the issue with change
is that i think other things might be going on in the browser, like focus, blur, etc.
it does work with input
could you flick me a gist?
i’m not sure why (merge @values {id v})
isn’t basically the same as what you did
i updated it
neither am i
1 sec, i’ll fire it up on my comp
@alandipert: oh, yes, you’re appending
but, what about removing?
it’s missing the bit where, when I delete the last character in an input, that input is removed
@thedavidmeister: i updated gist
i'm using :keyup again though
@alandipert: yeah but that’s two different events again
try making it happen in a single event
gotcha, updated gist again
:input #(if (zero? (count @%)) (delete-item! @id) (edit-item! @id @%)))))))))
that is buggy
click “append” until you see two consecutive numbers
then delete the first number
i hardcoded in a constant for append
what's buggy about it?
it deletes both values, and leaves an empty input behind
the 2nd gif is what i see currently, is that the buggy one?
the first gif
it’s important that the two sequential inputs have the same value
“8” “8” in my example
then delete the first one
if i delete one 8
after i have 2 8’s
i should have one 8 left
and i should never have an empty input immediately after processing the state in response to an event
hm, as far as i cann see my latest thing behaves the right way
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?
hmm, that does work
the plot thickens
what did you change?
try a number
try this
(button :click #(append-item! (str 1)) "Append”)
or a single character
(button :click #(append-item! "a") "Append”)
oh, i know why
because f
is not foo
ah! that triggers it
it’s the value when you hit delete for the last time that matters, not the initial values
see, i’m not crazy 😛
never thought you were
it's hard to be crazier than javascript lol
and actually, it is still broken if you change input
to keyup
i wonder if this is a jquery thing
so it’s not specific to the event
like i wonder if the value when the thing is empty is null and not an empty string or something?
i really do not know ¯\(ツ)/¯
the plot is so thick right now
you can “kind of” work around it by doing the processing in two steps
so like...
add on input
or update
then on keyup
, filter the db for any empty strings
but that’s a total hack
and doesn’t feel as slick to use if you have things responding to keydown and some to keyup, seemingly randomly
what I guessed, based on not knowing the internals
is that, after an input is deleted, something in the system checks to see if it needs to repopulate the values
and for each value that is different to what it was before, it will put them back in
but it will ignore the input if the :value
dereferences to the same values, even if they were from two different cells
i'm making a little progress here
by the time delete is pressed, the value of the input is gone
then it seems like when that element is removed by hoplon, the browser shifts focus to the adjacent input and clears it
without firing an input event
what is still super weird is that this only manifests with single-char initial inputs
are you testing in chrome also btw?
well actually, i can make it manifest for ”foo”
if i do it in one go
oh if you delete all of foo without deleting from anything else you mean?
gotcha, yeah. seeing the same thing
the important bit is just that the two inputs have the same value at the moment you hit delete
which is a lot easier to trigger for single characters
so f
won’t match foo
if you’re deleting one character at a time
yeah, i’m testing in chrome
i don't think it's related to javelin directly, because i see the right thing happen - the empty element is removed
the problem seems to be jumping focus to the nearest sibling and clearing its value
in a way that doesn't trigger an input event for that sibling - so the input looks empty, but represents a nonempty value
but i don’t think it is going to a sibling
in the above gif, “input 3” is where the cursor stays the whole time
the difference is just whether a new value from “index 3” is put into it, based on the state
ok i'm tracking now
it’s like...
step 1: event for “input” fires
step 2: the state is updated
step 3: something checks if the new state = the new dom, if not, make a update-required?
flag
step 4: the browser deletes the value in the input element, in response to the delete key
step 5: if update-required?
was true, put the new state value back into the dom
there’s no extra events, focus/blur/change, etc. because it’s actually the same input element the whole time
that’s why I called the PR I put up “DOM churn formula cell bug"
because micha told me a while back that the DOM is not being 100% rebuilt from scratch each time the state changes, for performance
so hoplon/javelin intelligently update the DOM in situ, based on cells, do! and on!
but in this case, it seems to be a bit inconsistent in exactly how that plays out
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
except that sometimes it does
heh, right
which is why i am confused, and saying it looks like a bug
if it just picked one way to behave and stuck with that, i’d be more comfortable
thanks for patiently explaining it
i'm a Bug Believer now also
well, i’ve been looking at it for a while
it’s not really obvious what is going on the first time you see it...
well anyway, it’s 2:15am here
so i’m going to leave you with that riddle 😉
@thedavidmeister: thanks see ya
@thedavidmeister: @alandipert https://gist.github.com/micha/09d8b95c10378a8cf3e4b90164f4c4a1
instead of just removing the item via dissoc
in delete-item!
i first set the text to nil
, then dissoc
that ensures that the text will not be the same in that formula cell in the loop-tpl
during the entire operation
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
quite subtle
yeah, I think this looks like it works how it's supposed to work to exactly 1 user of Hoplon
it seems like less a javelin corner case and more a mental pothole in the relationship between cells and dom via loop-tpl
i guess another solution is to flow a distinct value through further
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
hm - maybe some kind of wrapper object around the string that has an identity based on id + string
(defn wrap [s]
(js-obj "toString" (constantly s)))
another weird way to solve it
then you do:
(defn append-item! [txt] (swap! db assoc (gensym) (wrap txt)))
(defn edit-item! [id txt] (swap! db assoc id (wrap txt)))
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
i like it
like it knows that typing in the input gets its internal state out of sync with the formula
definitely seems right to solve bidi in specific cases
where all the conditions are known it can be solved precisely
does keyup catch things like pasting also?
aha dude i like the way you use gensym
i guess what we really want is to have attributes be more like methods on class instances
since it sorts the needed way it's both distinct and monotonic
that could be aware of how to know when element internal state is out of sync with cell
you mean as opposed to that attribute being accessible to all types of dom node?
e.g. the dispatch is double
Hi, what’s your recommendation for communication with the REST web services from hoplon application?
I would like to make a “service” layer in my cljs app which would expose functions hiding the details of the actual communication implementation
but I was wondering what is the preferred way to define such functions which are async
have you tried castra?
let’s say I need a function like (defn get-user-profile [username] …)
- how would you handle the asynchronous aspect of it?
the idea with it is you define cells to represent the data you want to show
so (def user-profile (cell nil))
it's hard to go wrong if you do
basically, pretend like the data is always there, and then separately have machinery that updates the data from the server
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
you definitely want to use cells as the "read" part of your remote interface
since you can attach UI to them and the UI doesn't need to care about the processes responsible for updating their values
but like to implement the calls and the updating of the cells, other tech comes into play
in castra i think we use promises
that's for the "write" interface
yup totally
that's exactly how we prototype
you can update cells from console etc. that way too, simulating server interactions and iterating on the UI without having a backend yet
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
except unlike a map in an atom not every code that knows about the big atom can see all the other data in it
how should users-list
element get its data? should it initiate the call to the server providing its local scoped cell?
i think it depends on you particular case
if there's one users-list data per-client, then it would maybe make sense for it to be globally managed
vs. interacting in the same app with multiple services that all have separate lists of users
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
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
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
i generally put "write" functions in their own separate namespace, maybe calling them from inside event handlers in defelems
so that defelems receive cells to display but defer to external functions to write/modify
by different criteria (id, name, pricing plan or whatever) and display the list of matching tenants
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.
should I use some client side routing to change the views (tenants list -> tenant page)?
probably client side routing
otherwise why should I define a global tenant id cell when I am just displaying a list of tenants?
my idea with the current-tenant-id is that it would be nil if a particular tenant is not yet selected
and perhaps the tenant detail markup is hidden when it's nil
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
right - tenant-view
could take a cell that contains the current tenant-id or nil
btw, is there any example showing how to handle client side routing? extra points if it shows how to handle security
there is a route-cell
in javelin that is a cell of the current route, you can use it to know where you are
re: security, castra has csrf protection
there aren't any security considerations to worry about for client-side routing, because it's all in the client
well, it depends - if there are multiple user roles with different access levels they might not be able to see some of the views
@alandipert: where can I find route-cell
? I cannot see it in javelin sources 😕
@piotrek: i was mistaken - it's https://github.com/hoplon/hoplon/blob/master/src/hoplon/core.cljs#L603-L609
the only thing deprecated about it was that it was in javelin, not hoplon. it just moved
@alandipert: thanks!
maybe another helpful thing to know in general about hoplon is that it's not as prescriptive as e.g. re-frame
you have way more options, but way less guidance
but you can choose to structure your app the re-frame way or any other way that feels natural
so there won't ever be one right way to organize, you'll just need to play around a little
see what sticks
re: route-cell yup, that's how
ok, so now I need to figure out how to glue my current-route
cell with secretary routing
not necessarily, your own routing code can be very simple
i definitely recommend starting that way withy our own simple things, vs getting secretary working
but you could also then do the other way: {:main "omg" :sub "whoo" :id 42}
-> #/omg/whoo/42
ok, so bidi returns some data when it matches a specific route (or nil if no match was found)
(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))))
so I could just define my routes with handlers as my functions returning a specific element
instead of defining the routes in one place and then have a list of divs with toggles based on the matching route
if you deref current-view
like that, it won't continue updating as the cell changes
since @
extracts the value from a cell at one point in time
there is actuall a dom leak with that particular approach
if-tpl
and cond-tpl
were added recntly to hoplon to mitigate
the safest thing is to use :toggle
per micha's example
https://github.com/hoplon/hoplon/wiki/Template-Macros has details about if-tpl et al
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
(also @levitanong is awesome for writing that page)
it would be mixing toggling with extracting route params and passing them to the toggled element
is it a different philosophy in hoplon or some technical difficulties to allow such usage?
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
:
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
the browser already does what react does, of course
(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))))
thanks a lot for your help @alandipert and @micha
no problem, rock on
you could do "Data: ~(pr-str data)"