Search code examples
haskellhaskell-snap-frameworkheist

Snap: compiled splice dependent on runtime decision and URL variable


I have a situation where I have to construct compiled splices and feed data into them which depends on the URL variable. I struggle to solve the problem.

So there is simple file name list that needs to be rendered in a table. Simple. Files belong to a group or category so you can list all files or related to a particular category. I pull data using this function:

getFilesList :: Maybe ByteString -> AppHandler [Document]
getFilesList cat = do
  let selection = maybe [] (\c -> ["category" =: T.decodeUtf8 c]) cat
  r <- eitherWithDB $ rest =<< find (select selection "files") {project = ["blob" =: 0]}
  return $ either (const []) id r

If it gets Nothing it pulls the whole list if it gets Just category it pulls files that belongs to that category. Easy so far.

I call the above function from within a handler so that I can feed an argument into it.

listFiles :: AppHandler [Document]
listFiles = do
  cat <- getParam "cat"
  let r = maybe Nothing (\c -> if c == "all" then Nothing else Just c) cat
  render "files/list-files"
  getFilesList r

If I get "all" or Nothing on the URL - I get the full list. Anything other then that - I get a category filtered list.

The URL root looks like this

("/files/:cat",           method GET    listFiles)

But now I have a problem because the "method" function will only accept Handler App App () signature. My handler returns data to be fed into the splices.

I construct my splices like so:

listFilesS :: Splices (Splice (Handler App App))
listFilesS = "files" ## files
  where
    files = manyWithSplices runChildren file $ lift listFiles -- Feed data here
    file = do
      "file-name"     ## (pureSplice . textSplice $ at "name")
      "file-oid"      ## (pureSplice . textSplice $ id)
      "file-date"     ## (pureSplice . textSplice $ dateFromDoc)
      "file-size"     ## (pureSplice . textSplice $ fsize)
      "file-type"     ## (pureSplice . textSplice $ at "type")
      "file-auth"     ## (pureSplice . textSplice $ const "admin")
      "file-link"     ## (pureSplice . textSplice $ flink)
      "file-category" ## (pureSplice . textSplice $ at "category")
      where id = T.pack . show . valueAt "_id"
        fsize = T.pack . show . round . (flip (/) 1024) . (at "size")
        flink = T.append "/files/" . id

I cannot find a way around it. Probably just missing something stupid. Any ideas what I am doing wrong?

In any case, my handler function looks incorrect since I render the template first and then pull the data. If I fix the handler then I cant feed the data based on the URL parameter.

Confused.


Solution

  • First of all, if listFiles is just returning [Document], then you don't want to call render "files/list-files". So the first order of business is to eliminate that line entirely. You might wonder why. That brings us to the second point. Your route should look like this:

    ("/files/:cat", method GET $ render "files/list-files")
    

    Your route is the result of rendering a template. That's pretty much always the case with Heist routes. Sometimes you might want to explicitly call render. Other times you might just use the routes automatically given to you by heistServe.

    I can't really comment on listFilesS without seeing more of the code for the Document API, but it looks reasonable. Assuming it works properly, you just have to bind that splice for your application with something like this:

    addConfig heist $ mempty { hcCompiledSplices = listFilesS }
    

    Then just use the files tag in your "files/list-files" template.