Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sending data between the client and the server, ie ajax #268

Closed
henrylaxen opened this issue Apr 8, 2013 · 5 comments
Closed

sending data between the client and the server, ie ajax #268

henrylaxen opened this issue Apr 8, 2013 · 5 comments

Comments

@henrylaxen
Copy link
Contributor

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

import Prelude
import Data.Data
import qualified Data.Aeson as A
import Data.String.Here.Uninterpolated
import Fay.Convert
import Data.Aeson.TH
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as B8

data E = E
  { int :: Int,
    string :: String }
  deriving (Typeable, Data, Read, Show)

e = E 1 "one"
$(deriveJSON id ''E)

{-

Dear Fay people,

Greetings fellow fayers.  I am trying to figure out how to move
data from the browser (running fay code) to the server (running
Snap code).  I've looked though quite a bit of code, but cannot
really seem to find the magic incantation.  In particular, I've
looked at the example in snaplet-fay, which seems to use some (to
me) magical javascript in index.js called formJson.

I've also looked at Client.hs and Server.hs in the fay-server
package, but don't quite get it.  I can see the stringify going
out in line 19 of Client.API and in line 33 of Server.API the
code: decode json >>= readFromFay statement, which looks like json
coming in and a Haskell data coming out, so I thought I would try
it out on a trivial example to see what is going on.

This leads me to the code below.  I wanted to stringify a simple data
type, E, with fay using JSON.stringify, which I did with the commented
out program below, but then the problem was how do I get my orginal
data E back?  Decoding the string produced by stringify with aeson
decode yields Nothing.  It looks like showToFay and readFromFay are
inverses, but I can't run showToFay in the browser, just stringify?  I
have to admit to being quite confused.  Can any of you shed any light?
Once I get this working, I'll have a very juicy piece of fay code
running that allows people to easily enter calcudoku puzzles
(www.calcudoku.org).  I will write it up with anansi so that others
can benefit from it.

While I'm at it, I have one more fay question.  In the code that
reads users events and produces the haskell representation of the
calcudoku puzzle, I am passing around a "global reference" to 90%
of the functions in the module, which essentially holds the
current state of the "world."  I would use the State monad if it
were available in fay, but it isn't.  Is there some prettier way
to do this?  Any pointers would be appreciated.

Best wishes,
Henry Laxen

-}


main = do
  let a1 = A.decode js :: Maybe E
  print a1  -- Nothing
  let a2 = A.encode e
  print a2  -- "{\"string\":\"one\",\"int\":1}"
  let a3 = A.decode a2 :: Maybe E
  print a3  -- Just (E {int = 1, string = "one"})
  let Just a4 = showToFay e
  print a4  -- Object fromList [("string",String "one"),("instance",String "E"),("int",Number 1)]
  let Just a5 = readFromFay a4 :: Maybe E
  print a5 -- E {int = 1, string = "one"}

-- js is what I get when I use Fay to stringify E
-- it was produced by running the fay compiled program below through nodejs
js :: B8.ByteString
js =  B8.pack [here|{"instance":"E","int":1,"string":"one"}|]

{-

import Prelude
import Data.Data
import JQuery
import FFI

data E = E
  { int :: Int,
    string :: String }
  deriving (Typeable, Data, Read, Show)

e = E 1 "one"

main :: Fay ()
main = do
  let a = showE e
  putStrLn a

showE :: E -> String
showE = ffi "JSON.stringify(%1)"

-- produces:
{"instance":"E","int":1,"string":"one"}

-}
@bergmark
Copy link
Member

bergmark commented Apr 8, 2013

Hi,

Fay uses syb and aeson internally to automatically encode and decode values, so you do not need to derive your own aeson instances.

Like you discovered, the representation used is pretty much the JSON object you'd expect, with the instance field added since Fay needs some way of knowing what type to instantiate when decoding values.

index.js's formJson simply converts the values in a form into this JSON format. These forms also need a <input type="hidden" name="instance"/>.

To produce a string like this on the server side:

encode :: Show a => a -> ByteString
encode = Aeson.encode . showToFay

And to read:

decode :: Data a => ByteString -> Maybe a
decode = A.decode >=> readFromFay

Perhaps we should expose these directly from Fay.convert.

In the produced js these aren't available directly but internally the same thing happens, which lets you do FFI calls like

getE :: E
getE = ffi "{'instance':'E','int':1,'string':'one'}"

which will instantiate the object through the E constructor.

In the snaplet-fay example I defined this ffi function for ajax calls:

jPost :: String -> Automatic f -> (Automatic g -> Fay ()) -> Fay ()
jPost = ffi "jQuery.ajax(%1, { data: JSON.stringify(%2), type: 'POST', processData: false, contentType: 'text/json', success: %3 })"

The data supplied is stringified and sent to the server in the request body. contentType: 'text/json' tells jQuery to call JSON.parse on the response it receives, and Fay then takes care of the decoding from JSON to the correct fay type when the success callback is called. So if you don't use jQuery then you need to call JSON.parse on the response manually.

As for State you have a few options:

  • Use the fay ref's provided in the runtime or the fay-ref package
  • Pass the state manually, like you are doing now
  • Make the state globally accessible
  • Look at examples/Cont.hs which fakes a Monad instance

Let me know if this cleared things up for you, otherwise I can try to elaborate.

Cheers!

@bergmark
Copy link
Member

I'll close this, please ask if you have any other questions

@henrylaxen
Copy link
Contributor Author

Dear Adam,

Thanks so much for your input. I should have thanked you earlier, but I got things working with your help. I should be ready to put my example online any day now.

Best wishes,
Henry Laxen

@bergmark
Copy link
Member

Good to hear! Looking forward to it.
-Adam

@ghost
Copy link

ghost commented Mar 6, 2014

Adam,

thank you for the clarification. Are you planning designing RPC syntax and protocol? I mean for example:

runRPC :: ServerSettings -> RPC a -> Fay a
runRPC serverSettings proc = .... -- construction which calls ajax

myService param :: String -> RPC String
myService = return -- procedure not included nor-compiled within js code

callMyService :: String -> Fay String
callMyService = runRPC serverSettings . myService -- call being issued in javascript

I know there's and issue with the dependency solver. Some packages would not be required directly in source and thus should be excluded. Maybe a GC like algorithm?

The only thing i find dirty about this implementation is the ajax settings (url). I think it could be embedded in a data type with the service itself?

I don't quite now haskell-src-exts, but is it possible for it to work only with signatures? of the server side implementations? This would make things easier. This idea comes from haskelldb which is how it enforces types.

Makes me curious if this makes sense or if it's an overkill.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants