Fork me on GitHub
#reagent
<
2016-01-25
>
sooheon07:01:23

@gadfly361: When using semantic-ui’s widgets with javascript, if I don’t care about cljs purity, I can include the jquery and semantic-ui.js dependencies, then run i.e. $(‘#select’).dropdown() in the console to initialise it. How can I call this from within my cljs source files? Or how do you use the semantic ui widgets currently? Not sure of the right way to approach this.

sooheon07:01:26

Ah figured it out. For posterity’s sake,

$(‘#select’).dropdown()
is equivalent to
(. (js/jQuery “#select") (dropdown []))
. I don’t know if this is a sane way to do it or not, but I just called that in the componentDidMount function, and it works.

jaen08:01:14

@sooheon: Yeah, that's how you would do it, more or less. Though consider what if you have, say, two components like that, what then?

jaen08:01:25

Here's an example I posted before of using exactly the dropdown - https://clojurians.slack.com/files/jaen/F0JKFP7T6/-.clj

jaen08:01:58

Might be helpful, especially the part about reagent/dom-node instead of jQuery selectors.

sooheon08:01:47

ah so instead of selecting by class, its a more explicit way of saying this element?

jaen08:01:50

If you would need it during rendering there's also reagent/current-component, but this should be rather rare, I think.

sooheon08:01:30

Just calling .dropdown with no args works for me, could you explain the rationale behind #js {:match “text" :onChange (fn [new-value] (reset! value new-value))} please?

jaen08:01:43

match is an option on what the dropdown should filter when you enter values - it can be "both" (default), "value" or "text". I want to match on text only.

jaen08:01:44

onChange is a callback that's made when value changes. The fact, that the dropdown "appears" to work is not enough - you have to somehow have your Clojurescript state synced with the dropdown and vice-versa.

jaen08:01:16

(fn [new-value] (reset! value new-value)) synchronises the current state of the dropdown with an atom passed as an argument.

jaen08:01:43

The code in :component-did-update does the reverse - it updates the dropdown when the atom changes.

jaen08:01:33

That's all because it's a stateful JS component, so you need to push and prod it a bit to behave with reagent.

jaen08:01:56

What I've written above is based on that.

sooheon08:01:49

Thanks very much! All of this is really helpful

sooheon08:01:30

Does the #js turn the following map from a cljs map to a javascript object?

jaen08:01:30

Yeah, but that's for literals only. #js name won't work, you need something like (clj->js name), though I usually prefer to construct options as literals most of the time anyway.

sooheon08:01:11

By literals you means the actual values, rather than function calls?

gadfly36108:01:23

@sooheon 👍 to advice by @jaen

jaen08:01:50

@sooheon: yeah, actual textual {:a 1} or 2 or "string" or something, not a function returning those.

sooheon08:01:01

@jaen: Awesome :). I’m still not quite used to the arg destructuring—is the choices atom to be passed in supposed to hold a vector of maps with {:value foo :text bar}?

sooheon08:01:58

Also I see that you did :as options in the destructuring but never used it—what would you use it for?

jaen08:01:48

I just call it like [dropdown {:value value-atom} {"key" "value" "key2" "value2"}]

jaen08:01:58

So no, that's not an atom, just a value.

sooheon08:01:42

ah it gets passed two arguments

jaen08:01:58

The downside is you can't change the list of things once you instantiate the component, but I didn't need it so far.

jaen08:01:18

In that case you could probably change _ in the inner fn to choices as well.

jaen08:01:23

But no need to make it an atom, I think.

jaen08:01:55

As for :as options it's really just my preference. I feel it documents what I'm destructuring. No need to actually do that if you feel it's superfluous.

sooheon08:01:19

Ok :) just wondering what your thoughts behind it were

jaen08:01:33

Also, you could probably use hiccup vectors in the choices map - I didn't try it yet, but I see no reason for it not to work.

jaen08:01:07

Also another downside - string keys; it's a JS library, so any key will be stringified. I could probably add some code to map that both ways, but didn't have a need to yet.

jaen08:01:23

The big upside - a rich component you don't have to duplicate for now.

sooheon08:01:56

For sure that’s the main upside to using semantic :)

jaen08:01:41

I would probably still rewrite them into React at some point (a la what re-com did with chosen dropdown for example), but if you're prototyping then it's perfect.

sooheon08:01:59

@jaen: I still don’t think i’m understanding what shape to pass it args in properly, I’m getting the “children with the same key” error, so value isn’t getting bound correctly. Is it supposed to receive two maps, one: {:value (r/atom nil)} representing the value of the dropdown component, and then other other {“foo” “Foo” “bar” “Bar”} representing the choices?

jaen08:01:17

Nah, I'm dumb, sorry, given you wrong example '

jaen08:01:32

(let [users (sc/users-query)]
  [dropdown {:value     user}
    (for [user @users]
      {:value (:id user) :text (:username user)})])

jaen08:01:36

Straight from where I use it.

jaen08:01:44

So it should be a vector of maps.

sooheon08:01:49

Haha thought that might be weird, because dropdown only takes one arg

jaen08:01:03

With :value and :text keys.

jaen08:01:13

No, it takes two args, at least my component.

sooheon08:01:42

[{:keys [value] :as options} choices] doesnt this mean you take one thing called choices, then destructure it?

jaen08:01:08

Hah, no, you're maybe a bit confused by let.

jaen08:01:17

If that were a let clause - you would be right

jaen08:01:31

But you can imagine argument vector as something like

jaen08:01:43

(let [[{:keys [value] :as options} FIRST-ARG
      choices                      SECOND-ARG]]
  ...)

jaen08:01:33

Hope it makes more sense now?

sooheon08:01:51

Ahhhh I see. Yeah, in the argument vector each top level sexp should be its own argument, I was confused by let where I’m binding something to a destructured map :)

sooheon08:01:00

Makes much more sense thanks

jaen09:01:19

Yeah. Argument vector is an ordered vector of patterns you would bind your n-th positional argument to.

jaen09:01:37

(as in the pseudocode let example above)

jaen09:01:49

Hopefully it now makes sense how I use that component.

sooheon09:01:48

@jaen: one more thing! :) in your actual example, you pass {:value user} as first arg before you iterate over @users as user below. So the user below is a map that contains :id and :username keys, what is user above that is the val of :value?

jaen09:01:36

Ah, I kind of omitted that, let me get the full code.

jaen09:01:44

(defn user-choice-component []
  (let [user (reagent-utils/wrap
               #(:id @selected-user)
               #(reset! selected-user-id (js/parseInt %)))]
    (fn []
      (let [users (sc/users-query)]
        [dropdown {:value     user}
          (for [user @users]
            {:value (:id user) :text (:username user)})]))))

jaen09:01:12

Where reagent-utils/wrap is this thing I've done - https://gist.github.com/jaen/c59a3e0abcf0c6f6ade2

jaen09:01:17

Not sure how kosher is that

jaen09:01:37

But the stock reagent wrap has this problem it receives a value, so you can't for example construct it in the outer let of a Form2 component

jaen09:01:44

Because it won't update.

jaen09:01:30

Before writing that wrapper I actuall had a second parameter in options, the :on-change callback

jaen09:01:00

But I though writing something atom-like would be cleaner.

sooheon09:01:05

Hrm this has too many levels for me to really grok right now :p

sooheon09:01:22

I’ll try wrestling with it and come back with more informed questions

jaen09:01:34

Yeah, I can imagine it's a bit more complex

jaen09:01:46

But forstarters

jaen09:01:48

You could just do

sooheon09:01:57

i thought the :value val was just an atom that held something like component local state

jaen09:01:07

If you do it like that

jaen09:01:10

It will work

jaen09:01:27

(let [user (reagent/atom 12)] ...)

jaen09:01:32

For example should just work, try it.

jaen09:01:13

Why I'm doing it that way, is because I need a "view" of another reactive value that's updateable through that "view".

jaen09:01:15

Hence this.

jaen09:01:25

It's a bit like the wrap example here - http://reagent-project.github.io/news/news050.html - but like I said, you can't really use reagent's wrap in an outer let of Form 2.

jaen09:01:23

So, to sum up

(defn user-choice-component []
  (let [user-id (reagent/atom "first")]
    (fn []
      (let [users (sc/users-query)]
        [dropdown {:value     user-id}
          (for [user @users]
            {:value (:id user) :text (:username user)})]))))

jaen09:01:26

Will probably work

sooheon09:01:59

Yes, it does! :)

sooheon09:01:46

The whole wrap thing, I think I will have to come back to

sooheon09:01:21

Thanks for all the reading material though. I’ll try to start absorbing it through osmosis

jaen09:01:20

Sure, no problem; if anything's unclear then feel free to ask.

jaen09:01:08

Also, if anyone thinks I'm doing something dumb with my Wrapper - feel free to yell at me.

sooheon13:01:49

I’m curious if anyone has experience the following using bidi (or any other client side routing): navigating around with links in the app works fine, and keeps track of things like user log in state, but if I directly type in and navigate to an address, the entire app reloads, losing all state. Most likely I’m neglecting to do something very basic with session or something, anyone recognise a common newbie pitfall?

jaen13:01:29

I think that has nothing to do with bidi for that matter. You can listen to hash change event, but I'm not sure if there's something similar for URL change.

jaen13:01:13

If you change whole URL you'll just get your page reloaded.

jaen13:01:17

Fairly sure, though not 100%.

sooheon13:01:40

jaen: I think the problem is my app-db (using re-frame) doesn’t persist across reload. When i navigate routes with links, they’re nearly instantaneous, but when I reload or manually go to a page, it seems like the whole app is loading again. if my init fn looks like this:

(defn ^:export init []
  (routes/start!)
  (rf/dispatch-sync [:initialize-db])
  (reagent/render
   [views/app]
   (.getElementById js/document "app")))
what else could be wrong?

jaen13:01:05

Because well, as you say - you reload. Your page will get loaded again, can't do much about that.

jaen13:01:13

At least as far as I know.

jaen13:01:30

But you can do one thing - if your app db doesn't persist across reloads

jaen13:01:54

Then you could serialise it using, say, transit and store in local storage using, for example, hodgepodge or something.

sooheon13:01:20

Wow so by default all apps with this architecture won’t persist app-db? That makes sense, but it’s a little surprising.

sooheon13:01:06

Sorry to keep asking so many questions, but could you steer me towards how to do what you said with the local storage?

sooheon13:01:57

^^this would mean that any re-frame apps would log out their users when they reload page, right?

jaen13:01:58

It's really not all that complicated, hodgepodge exposes it as ITransientAssociative so you can just do (assoc! hodgepodge.core/local-storeage :your-key "your-value)

jaen13:01:04

You would just have to hook it up

jaen13:01:10

In an appropriate place for re-frame

sooheon13:01:13

ah you meant literally hodgepodge

sooheon13:01:20

Haha I’ll start by googling that

sooheon13:01:24

thought it was a figure of speech

jaen13:01:44

Hah no, the lib is named that; sorry for confusion ; d

jaen13:01:10

And yes, when you reload a page in the browser all in-memory state goes away, no matter what language you use.

sooheon13:01:25

no after staring at code all day my reading comprehension is a little off :) might need parens around grammatical clauses

jaen13:01:26

So you have to store what you don't want to loose somewhere, like local storage for example.

jaen13:01:42

(just FYI local storage has a hard 5MB limit)

martinklepsch14:01:25

so... Form1,2,3... I have a component-did-update hook that is rerun every time the component arguments (i.e. loc in [google-map loc]) change, however when using loc in the code it refers to the initial value — any pointers how to make that change as well?

martinklepsch14:01:04

I tried using :component-did-update (fn [this loc] ...) similar as it is recommended for the render fn but that didn't do the trick

jaen14:01:36

Hm, according to React's docs componentDidUpdate gets previous props and componentWillUpdate will get next props.

jaen14:01:50

Maybe that's reflected in reagent?

jaen14:01:59

Just a guess, didn't have to do such hooks so far.

martinklepsch14:01:50

huh, so apparently did-update gets [this [some-fn props]]

martinklepsch14:01:10

Where some-fn seems to be the result of the create-class call

martinklepsch14:01:10

:component-will-update
      ;; not sure if I've fully understood why the signature is this way.
      ;; the first item in the second arg seems to be the return value of
      ;; the create-class call (seems!)
      (fn [this [_ new-center]]
        (.panTo (:map <@U06QXASV8>) new-center))

mihaelkonjevic21:01:02

hello, I need someone adventurous. I need feedback on my (kinda) framework for reagent apps, I can offer an incomplete readme and an example app. I'm planning to release it soon (it works for me "tm") but I'd like some feedback before everyone calls me an idiot simple_smile

martinklepsch21:01:04

@mihaelkonjevic: just provide a link, I'm sure some people here will be happy to take a look simple_smile

mihaelkonjevic21:01:04

simple_smile well here it goes: - lib/framework: https://bitbucket.org/retro/keechma - example app: https://bitbucket.org/retro/place-my-order . Place my order app is ported/rewrittent from the https://github.com/donejs/place-my-order app which is used as an example for the http://donejs.com/ framework

mihaelkonjevic21:01:41

readme for the place-my-order-app is inside the client folder