Fork me on GitHub
#clojurescript
<
2015-12-02
>
trancehime02:12:50

Oh... I just realize something.

trancehime02:12:48

How would I go about implementing a form that has one single submission button, but submits multiple items which have the same format. An example would be like shopping cart

trancehime03:12:59

Should I do deftype?

eyelidlessness03:12:06

it appears support for :notify-command was explicitly removed from figwheel some time back. is there a way to restore or replace it?

jaen07:12:54

@trancehime: what's wrong with a vector? As in {:items [{:name "top" :quantity 1} {:name "kek" :quantity 2}]}

trancehime07:12:18

I was asking about that over at #C0620C0C8

trancehime07:12:53

I had figured I would just use a vector and store that vector in an atom

trancehime07:12:09

but I do have some default values that are being rendered into the view already as a vector of items

trancehime07:12:32

but then my question became, would I just pass that entire atom containing the vector

trancehime07:12:38

into a function which processes the data

jaen07:12:15

Well, that depends. You're using plain reagent, yes?

trancehime07:12:41

Yeah, just plain reagent

trancehime07:12:45

I'm not using re-frame or anything

trancehime07:12:58

(I might consider into using re-frame in a different app or something)

jaen07:12:16

So yeah, I would keep the state in one or more atoms, and dereference them when you need the value inside.

jaen07:12:32

I'd probably wouldn't bother passing it as n argument in a simple application

jaen07:12:35

Just use it as a global

trancehime07:12:59

well thing is

trancehime07:12:09

the default values for a form come from a database

trancehime07:12:46

so i planned to write some call to server (AJAX probably since that's simple and easy for me to do), populate the atom, then render after the atom has been populated.

trancehime07:12:09

i guess I could just write the atom as global and continuously just reset! it when the form data has changed

trancehime07:12:34

and then I just dereference the entire atom (since I want to pass the entire vector) to my function?

jaen07:12:11

Yeah, that would be the simplest solution.

trancehime07:12:29

oh dangit that's what I already do for a different component

trancehime07:12:32

I am mega dumb

cjmurphy07:12:39

Why continuously reset! - isn't there a callback?

jaen07:12:25

I guess he means "continuosly" as "on change".

trancehime07:12:32

@cjmurphy: i do (fn [response] (reset! atom response)) as my handler

trancehime07:12:44

cause i am using cljs-ajax

cjmurphy07:12:53

So there is, whew!

trancehime07:12:56

not that one

trancehime07:12:12

cljs-ajax and of course I implement handler and error-handler

trancehime07:12:24

just meant that in the callback I would reset! that globally defined atom

trancehime07:12:32

with the data retrieved from the database,

trancehime07:12:08

so every time the database is queried, in the callback I would just change the value in the atom to the new one retrieved from the db.

jaen07:12:04

When do you query the database?

trancehime07:12:16

when the component is to be rendered/loaded

jaen07:12:30

Rendered or loaded?

jaen07:12:38

A query on render seems like a bad diea

jaen07:12:04

Generally needing any server side query to render sounds bad.

trancehime07:12:03

idk, enlighten me if there is a better way to do it

jaen07:12:06

Ok, so that's on mount, that's ok

jaen07:12:29

it would be bad if it were inside the inner fn

jaen07:12:34

As it would run on each single re-render

trancehime07:12:36

Oh no of course I would not do it in the inner fn

jaen07:12:51

Yeah, you calling this render confused me P ;

trancehime07:12:04

OK, well, TIL the correct term is on mount

trancehime07:12:12

we have cleared this up

jaen07:12:50

You can refer here for lifecycle events if you ever need to - https://facebook.github.io/react/docs/component-specs.html#lifecycle-methods

jaen07:12:24

So I guess it is okay, but why not just do it once on page load?

jaen07:12:42

Unless the a-thing is indeed the component for a whole page, not just the cart?

trancehime07:12:18

oh it's for a whole page, essentially.

trancehime07:12:42

I wasn't entirely being truthful with calling it a cart, it's not really a cart, but it was the closest example to the thing I have in mind

jaen07:12:06

It's usually just best to use the exact example, unless it is indeed top secret, sometimes people might understand things differently in analogy. But then again, they might as well in the same example, soo...

jaen07:12:23

So yeah, if it's a component for a whole big view of application

jaen07:12:29

Then it makes sense to call it there, yes.

jaen07:12:08

It kind of reminds me of a project I had to write for the university, I did it the dumb way as well - AJAX calls, atoms per view namespace, resetting them and so on.

jaen07:12:54

This component is a whole single view of application

jaen07:12:01

So it's analogous to what you are doing

jaen07:12:06

It more or less worked

jaen07:12:21

Then I just layered some caching on top of HTTP requests to hide the latency

jaen07:12:23

And it worked well

trancehime07:12:38

Well, maybe this would be a better explanation for what I am trying to accomplish

jaen07:12:43

For "enough for a project presentation" value of well

trancehime07:12:33

I have a page where the user would input some data for statistics of a competitive game. Let me use example of basketball, which generally has 13 players per team, total of 26 players. For simplicity's sake, let's say the user just has to input 3 things: POS, MIN and PTS. What I would get from the database would be the players who would be playing in the game, with POS, MIN and PTS set to default values.

trancehime07:12:50

Once this is all displayed on the page, user would fill in POS, MIN and PTS as needed, and then send the now changed values back to the server (basically, I do some server-side processing with this newly filled out data)

jaen07:12:23

How come server already knows the lineup for the match, isn't that something user decides as well?

trancehime07:12:52

what the server knows is based on game schedule, for games that havent actually been played yet. People know which teams will be in the game, but because the game hasn't actually played yet, we don't know how long each player was in the game (MIN), if they were on the field when the game was actually finished (POS) and how many points they scored for the team (PTS)

jaen07:12:15

Oh, I see

trancehime07:12:16

basically, a really dumbed down version of populating BOX SCORE

jaen07:12:26

I'm not all that good at that sports thing, sorry xD

trancehime07:12:49

That is alright, but is it clearer now what I'm trying to accomplish?

trancehime07:12:56

Would the approach we discussed prior be acceptable as well, or would there be a better approach to handle this?

trancehime07:12:13

(In my actual case, there would be less than 26 players, realsitically the most would be 10)

trancehime07:12:20

(sometimes there would only be 2)

jaen07:12:48

It seems like a sensible approach

jaen07:12:00

Here, it could look something like this

jaen07:12:11

This doesn't do syntax colouring

trancehime07:12:27

map-indexed takes each item in the coll and apply the (fn [...]) stuff until the coll is exhausted, right?

jaen07:12:38

Yeah, it's just like map

jaen07:12:45

But adds the index as first argument

jaen07:12:48

Which you need to know

jaen07:12:52

To update the element

jaen07:12:57

with update-in or something

jaen07:12:12

in the callback of get-game you reset the game-state atom to initial values, in the callback of update-game! you can display failure/success and/or close this form if it's no longer needed.

jaen07:12:20

Observe how I don't pass the atom out of the component

jaen07:12:31

I just deref when I want to pass it to the function that sends it

trancehime07:12:33

you just dereference it, right?

jaen07:12:42

It only needs to know what it has to send

jaen07:12:45

The current value of an atom

jaen07:12:49

Not that's it an atom

trancehime07:12:55

yeah, that's what I did with my other thing, I just dereferenced the current values in the atom

jaen07:12:06

You could call it SRP if you feel generous.

jaen07:12:23

Ah, I assumed you passed the atom from your phrasing, maybe I've just misunderstood you.

trancehime07:12:41

ah not the literal atom, but more like, passing in the dereferenced thing in its entirety.

trancehime07:12:59

which should be the collection.

trancehime07:12:34

anyway, I'm assuming swap! game-state update-in [idx] assoc :key new-value means we are updating the element in the index of game-state by assoc a particular key (in your gist it's :min) with the new-value right? where [idx] would be referring to {stuff}

trancehime07:12:00

or rather, the {stuff} at index idx

jaen07:12:13

Then it all makes sense; now that I scrolled up I'm not sure why I though you were passing that atom, my bad.

trancehime07:12:14

of the collection, which is game-state

jaen07:12:32

*which is in the atom game-state to be exact

trancehime07:12:40

yeah, whoops

jaen07:12:45

Generally functions like swap! or update-in are kinda funny

trancehime07:12:53

(i have learned a metric ton of stuff from you about this, so thanks so much)

jaen07:12:55

You can explode the function into the last argument

jaen07:12:02

(swap! game-state update-in [idx] assoc :min new-value)

jaen07:12:08

is basically equivalent to

jaen08:12:25

(swap! game-state (fn [old-value]
                    (update-in old-value [idx] assoc :min new-value)))
which in turn is equivalent to
(swap! game-state (fn [old-value]
                    (update-in old-value [idx] (fn [old-value]
                                                 (assoc old-value :min new-value)))))

jaen08:12:42

So it's basically a convenience to not have to nest like that

trancehime08:12:13

oh jesus that looks convoluted

jaen08:12:32

You could also have written it as (swap! game-state update-in [idx :min] (identity new-value)) if I'm not mistaken; do you see why?

jaen08:12:42

Ah sorry

jaen08:12:44

Not identity

jaen08:12:47

constantly

jaen08:12:54

(constantly new-value)

trancehime08:12:37

constantly always returns the same value... which is new-value, regardless of how many args you pass into it, if I'm not mistaken?

jaen08:12:09

So I can then delegate the assoc back to the key vector of update-in and just use that as my update function.

jaen08:12:22

As for nesting - well yeah functions like swap!, update`, update-in`, alter-var-root and so on accept a function that maps from the current state to the new state, so if you nest them, like in the case of using update-in to change something inside an atom with a swap! it can get unwieldy fast.

jaen08:12:59

So there's the sugar when you can explode the function into the body of them, as long as it's reasonably simple (which it is in this case)

jaen08:12:21

It takes a while to get used to that, at first it trips you up constantly how that works

trancehime08:12:50

so in this example, game-state is the atom, update-in [idx] assoc :min new-value could be exploded into the function portion.

jaen08:12:16

If you look at my three examples in reverse

trancehime08:12:21

update-in in of itself also needs a function as an argument.

jaen08:12:24

You can see how it folds up to the shortest version

trancehime08:12:29

Yeah, I actually see it now

jaen08:12:36

Yeah, this makes code more readable for simple update cases like that

jaen08:12:57

And usually there's little reason to put more complex logic inside the update rather than the outside.

jaen08:12:09

(not saying that won't ever happen, but for most cases it doesn't)

trancehime08:12:16

in the exploded version, old-value would be the old element at the index of the atom game-state and we are simply just assoc the new value of the key to that old element ;o

jaen08:12:57

Well, almost

jaen08:12:08

The inner old-value is different than outer old-value

jaen08:12:14

Maybe it was confusing I used the same name

jaen08:12:20

In the outer function

jaen08:12:26

old-value is the vector inside the atom

jaen08:12:37

In the inner it's the element at [idx]

trancehime08:12:40

and old-value in the inner function is the element at the index?

trancehime08:12:49

Yeah, they had the same name so I got tripped up lol

jaen08:12:01

#shadowing

trancehime08:12:18

Much easier to understand the logic, but of course not very pleasant to look at haha

jaen08:12:30

One thing you lose when using the sugar

trancehime08:12:39

Well that's why we call it syntactic sugar?

jaen08:12:32

Is the ability to close over the old value in the enclosing fn. If for some reason your value in update-in would depend on the old value in swap!, you couldn't do that with the sugar

jaen08:12:37

I don't imagine that's a common case

jaen08:12:42

But something to keep in mind

jaen08:12:02

Well yeah, sugar, spice and everything nice, or however it went

trancehime08:12:54

Well fortunately in this case I don't need to care exactly, since I'm just dealing with replacing values and not doing any computation or processing that would for some reason require the old value

trancehime08:12:12

I guess as you said, it could be a possibility in the future. I guess I can worry about that bridge when I have to cross it

jaen08:12:50

Well, since Clojurescript is single threaded (because JS is of course) you could cheat by just derefing the atom again, but that won't work in Clojure - in a multithreaded environment you're only guaranteed to get a consistent value if you use the one you get as the argument to update function.

jaen08:12:38

So it's better not to cheat I suppose

trancehime08:12:17

omg, just talked to my supervisor...

trancehime08:12:26

turns out i might not even need websockets after all

jaen08:12:19

Well, I remember you mentioning something about chat, so I don't imagine how to better do that than websockets.

jaen08:12:27

I mean you could do long polling I suppose

jaen08:12:36

If it's just a toy project

trancehime08:12:51

@jaen: Hmm, it's not exactly a toy project.

jaen08:12:51

Hmm, then why do you think websockets won't be needed?

jaen08:12:18

Just curious, maybe there's a legitimate reason, but wonder what would that be for an interactive chat

jaen08:12:27

(if I'm not misremembering the project)

trancehime08:12:49

You know the thing about the data input I just asked you about

trancehime08:12:17

It's supposed to be done in a way that n amount of users are allowed to input statistics for the same match

trancehime08:12:26

I thought it had to be done in a live format

trancehime08:12:56

Turns out I don't need to care about synchronicity, as users will choose to apply to a schedule they wish to input data on ahead of time

jaen08:12:02

And you will just lock them?

jaen08:12:08

That makes sense, I suppose.

jaen08:12:26

So it seems the chat back then was just another analogy? : V

trancehime09:12:21

I was only concerned about channels not the messages :V

jaen09:12:55

Well, I just implicitly assumed you're in fact writing a chat, where websockets are the best solution. Otherwise I might've suggested websockets are overkill P ;

jaen09:12:09

But well, at least you've learned something about sente and stuff.

trancehime09:12:36

@jaen yeah, I did. In case I do actually end up needing it

deas10:12:40

In a figwheel project, how do you you interactively (browser) test an :optimization :advanced build? "lein ring server"?

keeds10:12:37

I usually "python -m SimpleHTTPServer"

deas10:12:39

Feels wrong. And does not allow you to use ring handlers.

deas12:12:37

Guess the answer is "it depends". "lein run ..." is what I need.

thheller12:12:55

@deas do you have a server part?

thheller12:12:14

if not any http server or even the filesystem should work

deas12:12:26

Yes, and I have a websocket handler, so "lein ring server" is not enough.

thheller12:12:04

don't know enough about figwheel to give a proper answer sorry

thheller12:12:21

but usually you'd just need to configure the ring server to call the handler that figwheel uses

deas12:12:15

Figwheel takes care of everything. Except when it does not work. Using :optimization :advanced is my case.

thheller12:12:04

but you have ring handler?

deas12:12:23

"lein ring server" will probably do in 99% of the cases then. Except when it does not work. Such as when you have websocket handelers. 😉

thheller12:12:31

:advanced will never work with things figwheel tries to do (live reload, repl, etc)

thheller12:12:48

what kind of websocket handler?

deas12:12:50

Sure, but one might still want to try :advanced out.

joelkuiper12:12:11

Really silly question, but I’m trying to get Ladda (a button library) to work with Reagent, but it’s giving me headaches. The JavaScript equivalent is

var l = Ladda.create( document.querySelector( '.my-button' ) );
Which I translated to
el (fn [] (.getElementById js/document id))
        ladda-load! (fn [_] (.create js/Ladda (el)))

in the component-did-mount, but it seems not to work

deas12:12:15

@thheller: I am messing with gorilla-repl. It uses nrepl over websocket.

joelkuiper12:12:38

giving “a is not a constructor”

thheller12:12:22

any REPL will generally never work in :advanced

deas12:12:35

Oh, it does! But I guess not exactly the way you would expect. simple_smile You know gorilla-repl?

thheller12:12:49

@joelkuiper: one is using getElementById the other is querySelector?

joelkuiper12:12:11

nah it was a bug in the Ladda library, apparently the order of dependencies matters 😕

joelkuiper12:12:30

hard to track without source maps 😛

joelkuiper12:12:52

so I made a bit of progress, not a lot; but some 😛

thheller12:12:33

@deas never used it but technically a REPL really cannot work in :advanced

thheller12:12:50

it may launch but never be able to run anything

joelkuiper12:12:29

might be enough reason to generate a proper cljsjs from this

deas12:12:32

It does, believer me. But it is most likely not the use case you have in mind.

deas12:12:27

@thheller: It is not about a cljs repl.

dnolen12:12:25

@deas @thheller is trying to explain the limitation of :advanced

dnolen12:12:46

dev time stuff cannot in general work with :advanced

dnolen12:12:57

trying to make that stuff work is a waste of time

thheller13:12:24

@daes the problem is that :advanced will rename/remove a lot of code

thheller13:12:53

so if you attempt to call anything at all of your code, it will never have the name you expect it to

deas13:12:59

@thheller: I understand. simple_smile

thheller13:12:17

but it appears that gorilla itself is not part of your build?

thheller13:12:22

so that would still work

deas13:12:57

@thheller: Exactly. And the JS I was pointing to needs a ws-service. That's why "lein run ..." is my only option and "lein ring server" won't work.

thheller13:12:08

hmm not sure about the state of websockets in ring

thheller13:12:44

gorilla repl seems to require httpkit

thheller13:12:13

so you could probably roll your own main function that just starts a httpkit server with the gorilla handler

thheller13:12:23

still .. why go through the trouble of :advanced?

thheller13:12:37

it really is for production builds only

deas13:12:51

@thheller: with "lein figwheel" and :none, there is nothing to worry about. Works straight out of the box.

deas13:12:34

@thheller: Because I wan't to make sure :advanced code will be working in production.

thheller13:12:02

is gorilla REPL part of your production app?

deas13:12:01

@thheller: Sure! In fact I am working on a generic drop in jar.

dnolen13:12:33

@deas: you always should exclude any dev time stuff from your build that is not compatible with :advanced compilation

dnolen13:12:51

not doing so will lead to many cryptic problems and lost time

thheller13:12:39

wait ... gorilla is a Clojure REPL .. not a cljs repl

deas13:12:48

@dnolen: I understand.

thheller13:12:56

so it really doesn't interact with cljs at all

dnolen13:12:24

@deas ok then carry on simple_smile

jstew13:12:56

Speaking of excluding dev stuff, how does everyone here deal with doing that? specifically in html templates? I have a user.js file that I want to keep out of my production build, and I end up rendering the template server side to conditionally include it in dev mode. Is there a better way that I might be missing?

deas13:12:46

@thheller @dnolen : That's what I said. My story is NOT about a cljs repl. 😉

dnolen13:12:52

@jstew there’s many ways to do that

dnolen13:12:06

@deas I didn’t say it was, it was just that your comment above didn’t make any sense to me

dnolen13:12:31

I read “I’m trying Figwheel with :advanced” which doesn’t make any sense

dnolen13:12:46

but I may have misinterpreted what you were actually trying to communicate

dnolen13:12:13

@jstew and there’s nothing particularly wrong with how you are doing it

thheller13:12:27

yeah me too, mistakenly assumed gorilla was a cljs repl

thheller13:12:52

so all you really want is a server than can work for gorilla repl that isn't figwheel

thheller13:12:12

2 parts to that, the http kit server only acts as a relay to nrepl

thheller13:12:29

so you need to run both (which figwheel does for you)

thheller13:12:19

gorilla-repl.core seems to have a main to do just that

deas13:12:31

@dnolen: Sorry about not being clear enough. In fact I was just wondering "How do I interactively try :advanced code" in the browser.

deas13:12:21

@thheller @dnolen :Got it working. Thanks for your time.

jstew13:12:39

@dnolen: What are a couple of other ideas? I don't mind my solution, but I'm always looking for simpler ones.

dnolen13:12:48

@jstew you can do this by using different builds. the builds can define different entry points.

dnolen13:12:15

so your prod build just loads the main namespace, but the dev build uses a dev namespace that loads your tooling + the main namespace

dnolen13:12:21

then you don’t need markup customization

dnolen13:12:23

see the compiler :main option for defining an entry point

jstew13:12:17

Oh nice. I'm going to give that a try.

jstew13:12:05

so I just configure figwheel to use the dev build, then lein cljsbuild the production build? Seems so easy that I'm kind of mad at myself for not thinking of it!

dnolen13:12:58

not necessarily obvious … looking forward to updated books simple_smile

deas13:12:32

@jstew: Unlearning complexity can be difficult. 😉

exupero14:12:29

Is it possible to inline Node dependencies into the compiled output file so you don’t have to install NPM dependencies to run it?

dnolen14:12:48

@exupero: only if you use a JS build tool to build your NPM deps first

pbaille15:12:09

Hi, i'm trying to get a file via http request and convert it to dataURI with this code but it doesn't work, any idea? (def data (atom "")) (go (let [res (a/<! (http/get "http://files.parsetfss.com/42a74b87-d8af-4d8b-9bec-be3039f672fe/tfss-247d838f-26c4-4a0e-89b1-793f5165799b-0.jpg" {:with-credentials? false}))] (reset! data (str "data:" (get-in res [:headers "content-type"]) ";base64," (js/btoa (:body res))))))

pbaille15:12:54

i’ve got this error : Uncaught InvalidCharacterError: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

pbaille15:12:51

http/get from cljs/http

joseph15:12:57

@pbaille: try this function :

joseph15:12:00

`function utf8_to_b64(str) { return window.btoa(unescape(encodeURIComponent(str))); } `

joseph15:12:49

function utf8_to_b64(str) {
    return window.btoa(unescape(encodeURIComponent(str)));
}

pbaille15:12:35

i’ve tried like this: (js/btoa (js/unescape (js/encodeURIComponent (:body res))))

pbaille15:12:57

but it doesn’t work, i no longer have the InvalidCharacterError but it doesn’t display in the browser

pbaille15:12:08

thank you by the way simple_smile

exupero15:12:48

@pbaille: I had to do the following in a JS lib to handle non-latin characters:

pbaille15:12:47

thank you I will try

joseph16:12:13

@pbaille: it works in my machine

joseph16:12:21

(go (let [res (async/<! (http/get "" {:with-credentials? false}))]
(prn (js/btoa (js/unescape (js/encodeURIComponent (:body res))))  (get-in res [:headers "content-type"]))
             (reset! data (str "data:" (get-in res [:headers "content-type"]) ";base64," (js/btoa (js/unescape (js/encodeURIComponent (:body res))))))))

joseph16:12:44

you can see the printed log in the browser's console

joseph16:12:06

cljs.user=> (count @data)
453143

pbaille16:12:32

yes but when I try to mount the image [:img {:src @data}] it doesn’t work

pbaille16:12:53

I should be able to do this no?

mahinshaw19:12:22

@dnolen in js bin running (range 0 10) throws and Invalid number format error, analogous to what was happening in https://github.com/mfikes/replete/issues/8

dnolen19:12:33

@mahinshaw: I don’t know or care about bugs in jsbin simple_smile

mahinshaw19:12:57

Sorry, it looked like an issue with the reader that was seen in the mentioned link

dnolen19:12:10

replete bugs are also not ClojureScript bugs

dnolen19:12:25

the only bugs I care about are those that in the ClojureScript compiler and demonstrably so

dnolen19:12:30

anything else is not my concern

mahinshaw19:12:52

np, sorry for the noise