Fork me on GitHub
#hoplon
<
2016-07-27
>
adamw09:07:53

guys apologies I'm sure this is obvious, how can I maintain order with castra calls? Let's say I have a castra remote end-point to generate a unique token for a given file. If I call (mkremote repeatedly for each of n files, how can I associate the result of each remote call to a given file in the result cell?

adamw09:07:34

in other words, can I ensure the state cell is updated in the same order as the requests were sent, and if not, is it possible to make the state cell a vector, or map, similar to the loading cell?

adamw09:07:39

actually I guess I can fake it by storing a map and ensuring that my remote call passes an :identifier back and forth

dm309:07:12

I think that is the best solution

dm309:07:57

although you could probably make it work with a vector in a cell by specifying a lens

dm309:07:44

to the state cell argument for each mkremote call

jumblerg11:07:02

@adamw: when you call a castra function, it will immediately return a jquery promise which you can associate with each file; the promise, in turn, will invoke a success callback with the result of the remote function that evaluated on the server.

jumblerg11:07:43

the result of the function, returned through the callback, is not to be confused with the state, which just so happens to piggyback on the http response to the state cell. this is an implementation detail designed to “bind” the client state on the query side of the application to the state on the server.

jumblerg11:07:15

the state and the result can be the same, if you build your application that way, but they can also differ if you make use of the state-fn that can be passed into the castra middleware. if you supply a state-fn, the state resulting from that function will get synced to the state cell with each http request, while the result of each rpc call will be returned to the call site via the query promise.

micha14:07:50

my high-level picture of it is that there are two concerns:

micha14:07:45

1. the result of performing some action (command) on the server, which is a side effect you perform on the world outside the application. This results in possibly a change in the global state of the database. This global state is returned to the client and a javelin cell is updated (a "stem cell"). The client's UI reacts to this change globally. Usually the component that sent the commend has no need to know or care about how the database state will relate to the UI as a whole. This is why rpc fns don't just return a result (it's expected that the part of the program that reacts to changes to the database state will not be part of the piece of program that initiates the command.)

micha14:07:37

2. errors or exceptions raised by the command on the server. These do need to be handled locally in the component that sent the command, usually. Like if you have a form component that sends a command to the server, if validation fails only the form knows how to deal with that, for example. This is why the rpc fns return the promise. The promise can be used to receive errors and handle them locally in the form component, etc.

micha15:07:12

so the rpc fn returns a promise that is local to the component, but sets the state of a javelin cell that is part of a larger graph with the latest server state

micha15:07:58

it's hard to explain, but i can see it clearly in my head 🙂

micha15:07:34

i want to encapsulate the logic for reacting to changes of the backend server state in the components that convey that information to the user, the UI components

micha15:07:02

and those UI components are usually not the same ones that send the commands that result in those changes in the backend, like the forms and whatnot

dm315:07:19

makes total sense (having extensive exposures to CQRS architecture)

micha15:07:26

and secondly i want to encapsulate the error handling logic in the components that initiate the commands to the backend

micha15:07:15

if i can cleanly achieve both kinds of encapsulation then things go very smoothly and components are nicely composeable and docile

dm315:07:09

the "action" components send commands, which execute synchronously and produce errors synchronously. So a component knows when it did something bad.

dm315:07:42

the "view" components read their own "queries" (cells) that update asynchronously in response to the changes generated by executed commands

micha15:07:13

the rpc commands are async though

micha15:07:22

including errors etc.

dm315:07:06

yeah, but the logical separation should still be there

dm315:07:45

whoever acts on the error cell doesn't really matter to the "view" components. It might even be another "view" component that displays the errors (e.g. status bar)

micha15:07:02

i guess the fundamental strategy here is to have two separate channels for the RPC results, one local to the component and one provided from outside

micha15:07:39

like the mkremote thing takes the stem cell from outside in which it stores the backend state

micha15:07:01

but when you call the remote fn it returns the promise in lexical scope

micha15:07:22

that's just for the code that's encapsulated in the command component to use

micha15:07:56

yeah the error cell is for when you need to handle errors globally

dm315:07:55

so the promise is kind of like the sync command execution on the server side, as in it's local to the caller

micha15:07:17

i guess "globally" is the wrong word, maybe better is "the error cell is for handling errors via inversion of control"

micha15:07:36

and the promise is for handling them in the local scope

micha15:07:15

control is inverted in that the cells provided to mkremote could come from anywhere

micha15:07:52

but the promise is returned to the caller directly

micha15:07:40

it's surprisingly difficult to find good words to describe this

thedavidmeister15:07:08

hey, random question

thedavidmeister15:07:19

can i not run certain things on prerender?

thedavidmeister15:07:07

i got an ajax call erroring out and the error ending up in the rendered markup

micha15:07:36

could be an issue with phantomjs maybe?

micha15:07:48

anything that can run in phantomjs shoudl be fine

thedavidmeister15:07:03

oh it would be that i’m not running the server while i’m making the jar

micha15:07:17

ah that would do it 🙂

thedavidmeister15:07:00

yeah so it renders something that looks ok

thedavidmeister15:07:09

but anything that needs the backend won’t be there

thedavidmeister15:07:17

the issue is that it now actually puts an error in

thedavidmeister15:07:26

like, not a js error

thedavidmeister15:07:30

a system message i made

micha15:07:17

prerendering content from the server is problematic in my experience

micha15:07:32

because it looks legit enough to the user that it can be confusing

micha15:07:44

like for example if the user hits the refresh button

micha15:07:02

and they see a flash of some fully legit looking state that's prerendered

micha15:07:19

and then it flashes back to the actual correct state

thedavidmeister15:07:32

i don’t want it to show up

thedavidmeister15:07:36

i don’t want the error either

micha15:07:51

yeah i usually make static spinner type thing in prerendering

micha15:07:01

something that doesn't look like a valid application state

micha15:07:17

there is a thing in hoplon that you can use

micha15:07:47

hoplon.core/prerendering?

micha15:07:51

haev you seen that?

micha16:07:16

you can use it like (if prerendering? ...

micha16:07:59

so you can isolate some parts of the UI to just prerendering or not prerendering etc

micha16:07:34

(div
  (when-not prerendering?
    (some-component :with-server true)))

micha16:07:37

stuff like that

thedavidmeister16:07:20

(def env (j/cell nil))

(ajax.core/GET "/config" {:handler #(reset! env %)
                          :error-handler #(do (system-message.state/+! "Couldn't connect to the server. Refreshing the page might help.")
                                              (prn %))})

thedavidmeister16:07:34

i’ll just wrap that in the prerendering? thing

micha16:07:45

yeah that's an easy way to do it

micha16:07:01

(when-not prerendering?
  (ajax.core/GET ...

micha16:07:38

it will at least solve your immediate problem

micha16:07:33

if it's used in too many places it might get hairy and maybe need to make some abstraction on top of prerendering? in your app

thedavidmeister16:07:22

@micha probably fine for just that

thedavidmeister16:07:34

do i need to stick that in a cell?

thedavidmeister16:07:49

like (j/cell= (when-not h/prerendering? …))

thedavidmeister16:07:21

atm everything else is behind a login form

thedavidmeister16:07:52

ah, i see it’s just a value

thedavidmeister16:07:32

@micha thanks. Seems like another thing that should go in the wiki someday.

micha16:07:37

yeah it's just a value because it can't change during the lifetime of the application

micha16:07:04

like either it's running in the prerendering phantomjs browser or it's running in the user's browser