haskellhaskell-snap-frameworkheist

Snap: compiled splices code example


I think I did asked a similar question some time ago but it was not answered due to unstable API. So I was waiting for the 0.13 to pass by. I am not sure if it is correct to bring up a similar question...?

What is the alternative to interpreted runChildrenWith(Text) and mapSplices in the compiled splices world? (this combination seems to be the most common) I would really appreciate some code examples if possible.

If I understand correctly, we get together all the application splices and then add them to the heistInit. Can anyone show how to do it please?

Does the splice binding tag has to be unique across the whole application?

Is there a completed snap project utilising new APIs and compiled splices so that I could read and see learn?

Thank you.

-- UPDATE --

Great answer below. But some parts (the ones with lenses) got me even more confused, unfortunately. If I understand correctly this is the simple way to splice a string:

mySplice = "testSplice" ## testSplice
  where testSplice = return $ C.yieldRuntimeText $ do
          return "text to be spliced"

If i need to run the spliced string several times, say in 5 table raws i would do it like this:

mySplices = C.manyWithSplices C.runChildren mySplice

Is this correct?

I get bunch of errors trying to add the splices in heist config.

addConfig h $ mempty
 {
   hcCompiledSplices = "mySplice" ## mySplice -- or mySplices
 }

Where am I going wrong? Sorry for being slow.

All I need really ( just for now so I can understand) is to splice and display a simple string that I receive from database.

-- UPDATE 2 --

Thanks to the extremle helpfull Daniel`s answer I can finally get something working.

So far I get both variants of code working.

The first one, thanks to Daniel

stringSplice :: Monad n => C.Splice n
stringSplice = C.manyWithSplices C.runChildren splicefuncs (return ["aa","bb","cc"])
  where
    splicefuncs = "string" ## (C.pureSplice . C.textSplice $ id)

And the secod

testSplice :: C.Splice (Handler App App)
testSplice = return $ C.yieldRuntimeText $ return "text to be spliced"

Where

(C.pureSplice . C.textSplice $ id)

produces similar results to

return $ C.yieldRuntimeText $ return "text to be spliced"

Is there difference between the above? Any cases that one would prefer one to another? They seem to produce the same results.

There is a "deferMany" function in the compiled splices lib that, according to the docs, produces similar results to the mapSplices in interpreted lib. Can we use it instead of "C.manyWithSplices C.runChildren" combination??


Solution

  • Let's say you want to display information about a list of persons using compiled splices (assume that we start from the scaffolding generated by snap init.)

    A very simple _persons.tpl template with dummy values would be something like

    <body>
        <person>
            <div>
                <h1><name>dummy name</name></h1>
                <p><age>77</age></p> 
                <p><location>jauja</location></p> 
            </div>
        </person>
    </body>
    

    Where person, name, age, and location are the tags to be spliced.

    We define a trivial Snaplet that holds the info

    data Foo = Foo
        {
            _persons :: [Person]
        }
    
    makeLenses ''Foo
    
    data Person = Person
        {
            _name :: Text
        ,   _age :: Int
        ,   _location :: Text
        }   
    
    makeLenses ''Person
    

    and we add it to the App record:

    data App = App
        { _heist :: Snaplet (Heist App)
        , _sess :: Snaplet SessionManager
        , _auth :: Snaplet (AuthManager App)
        , _foo :: Snaplet Foo
        }
    

    we add the following to the app initializer

    f <- nestSnaplet "foo" foo $ makeSnaplet "foo" "Foo Snaplet" Nothing $ return $ Foo $ 
            [ Person "Ricardo" 33 "Los Cantones" 
            , Person "Luis" 38 "Montealto" 
            ]
    
    ...
    
    return $ App h s a f
    

    This function constructs a Handler that returns the list of persons (using view from Control.Lens):

    personH :: SnapletLens b Foo -> Handler b b [Person] 
    personH l = withTop l $ view persons <$> get 
    

    This function constructs the appropiate compiled splice from a RuntimeSplice that produces a list of Persons. RuntimeSplices represent information that can only be known at run time, as opposed to load time:

    personSplice :: Monad n => RuntimeSplice n [Person] -> C.Splice n
    personSplice = C.manyWithSplices C.runChildren splicefuncs 
        where
        splicefuncs = mconcat  
            [ "name" ## (C.pureSplice . C.textSplice $ view name)
            , "age" ## (C.pureSplice . C.textSplice $ T.pack . show . view age)
            , "location" ## (C.pureSplice . C.textSplice $ view location)
            ]
    

    And this function can be used to register the splice in the global Heist configuration. Notice that we lift the Handler into a RuntimeSplice:

    addPersonSplices :: HasHeist b => Snaplet (Heist b) -> 
                                      SnapletLens b Foo -> 
                                      Initializer b v ()
    addPersonSplices h l = addConfig h $ mempty 
       {
          hcCompiledSplices = "person" ## (personSplice . lift $ personH l) 
       } 
    

    Be sure to add this line to the app initializer:

    addPersonSplices h foo
    

    And to add the following pair to the app's routes:

    ("/persons",  cRender "_persons")
    

    If you now run the server, navigating to http://127.0.0.1:8000/persons should show the list.

    UPDATE

    For the simpler case (no complex records, no lenses) in which you only want to show a list of strings.

    The template could be something like:

    <body>
        <strings>
            <p><string>dummy value</string></p>
        </strings>
    </body>
    

    The top-level splice would be:

    stringSplice :: Monad n => C.Splice n
    stringSplice = C.manyWithSplices C.runChildren splicefuncs (return ["aa","bb","cc"])
        where
        splicefuncs = "string" ## (C.pureSplice . C.textSplice $ id)
    

    This means "when we encounter the tag associated to this splice, perform an action that produces a list of strings, and for each string, render the contents of the tag, substituting the current string for the string tag".

    Notice that the signature of manyWithSplices forces the stuff to the right of the (##) to have type RuntimeSplice n Text -> Splice n. Here id has type Text -> Text. C.TextSplice transforms it into something of type Text -> Builder, and C.pureSplice performs the final transformation into a RuntimeSplice n Text -> Splice n.

    In place of (return ["aa","bb","cc"]) you could provide a more complex action that connected a database and extracted the strings form there.

    A function to register this splice would be:

    addStringSplices :: HasHeist b => Snaplet (Heist b) -> Initializer b v ()
    addStringSplices h = addConfig h $ mempty 
        {
              hcCompiledSplices = "strings" ## stringSplice
        }