On Mon, Mar 28, 2011 at 6:28 PM, Michael Litchard <mich...@schmong.org>wrote:
> 1 > 2 > 3 > 4 > 5 > 6 > 7 > 8 > 9 > 10 > 11 > 12 > 13 > 14 > 15 > 16 > 17 > 18 > 19 > 20 > 21 > 22 > 23 > 24 > 25 > 26 > 27 > 28 > 29 > 30 > 31 > 32 > 33 > 34 > 35 > 36 > 37 > 38 > 39 > 40 > 41 > 42 > 43 > 44 > 45 > 46 > 47 > 48 > 49 > 50 > 51 > 52 > 53 > 54 > 55 > 56 > 57 > 58 > 59 > 60 > 61 > 62 > 63 > 64 > 65 > 66 > 67 > 68 > 69 > 70 > 71 > 72 > 73 > 74 > 75 > 76 > 77 > 78 > 79 > 80 > 81 > 82 > 83 > 84 > 85 > 86 > 87 > 88 > 89 > 90 > 91 > 92 > 93 > 94 > 95 > 96 > 97 > 98 > 99 > 100 > 101 > 102 > 103 > 104 > 105 > 106 > 107 > 108 > 109 > 110 > 111 > 112 > 113 > 114 > 115 > 116 > 117 > 118 > 119 > 120 > 121 > 122 > 123 > 124 > 125 > 126 > 127 > 128 > 129 > 130 > 131 > 132 > 133 > 134 > 135 > 136 > 137 > 138 > 139 > 140 > 141 > 142 > 143 > 144 > 145 > 146 > 147 > 148 > 149 > 150 > 151 > 152 > 153 > 154 > 155 > 156 > 157 > 158 > 159 > 160 > 161 > 162 > 163 > 164 > 165 > 166 > 167 > 168 > 169 > 170 > 171 > 172 > 173 > 174 > 175 > 176 > 177 > 178 > 179 > 180 > 181 > 182 > 183 > 184 > 185 > 186 > 187 > 188 > 189 > 190 > 191 > 192 > 193 > 194 > 195 > 196 > 197 > 198 > 199 > 200 > 201 > 202 > 203 > 204 > 205 > 206 > 207 > 208 > 209 > 210 > 211 > 212 > 213 > 214 > 215 > 216 > 217 > 218 > 219 > 220 > 221 > 222 > 223 > 224 > 225 > 226 > 227 > 228 > 229 > 230 > 231 > 232 > 233 > 234 > 235 > 236 > 237 > 238 > 239 > 240 > 241 > 242 > 243 > 244 > 245 > 246 > 247 > 248 > 249 > 250 > 251 > 252 > 253 > 254 > 255 > 256 > 257 > 258 > 259 > 260 > 261 > 262 > 263 > 264 > 265 > 266 > 267 > 268 > 269 > 270 > 271 > 272 > 273 > 274 > 275 > 276 > 277 > 278 > 279 > 280 > 281 > 282 > 283 > 284 > 285 > 286 > 287 > 288 > 289 > 290 > 291 > 292 > 293 > 294 > 295 > 296 > 297 > 298 > 299 > 300 > 301 > 302 > 303 > 304 > 305 > 306 > 307 > 308 > 309 > 310 > 311 > 312 > 313 > 314 > 315 > 316 > 317 > 318 > 319 > 320 > 321 > 322 > 323 > 324 > 325 > 326 > 327 > 328 > 329 > 330 > 331 > 332 > 333 > 334 > 335 > 336 > 337 > 338 > 339 > 340 > 341 > 342 > 343 > 344 > 345 > 346 > 347 > 348 > 349 > 350 > 351 > 352 > 353 > 354 > 355 > 356 > 357 > 358 > 359 > 360 > 361 > 362 > 363 > 364 > 365 > 366 > 367 > 368 > Ready or not, here I come. What is the purposes of these 368 numbers? Luke > > > > I'm working off of a example file from Yesod, ajax.lhs > I've made an important change in types, and this has resulted in > having to make the old code conform to the change. I will point out > the specifics, then present my question. In the event I failed to > include important information, I will paste in my code as well as the > prototype. > > [Original] > > > getHomeR :: Handler () > > getHomeR = do > > Ajax pages _ <- getYesod > > let first = head pages > > redirect RedirectTemporary $ PageR $ pageSlug first > > [Changed] > > > getHomeR :: Handler () > > getHomeR = do > > Tframe pages _ <- getYesod > > let first = head pages > > redirect RedirectTemporary $ PageR $ pageSlug first > > Error Message > > test.lhs:62:4: > Constructor `Tframe' should have 2 arguments, but has been given 1 > In the pattern: Tframe pages > In a stmt of a 'do' expression: Tframe pages <- getYesod **** > This is not what I wrote ***** > In the expression: > do { Tframe pages <- getYesod; > content <- widgetToPageContent widget; > hamletToRepHtml > (hamlet-0.7.1:Text.Hamlet.Quasi.toHamletValue > (do { (hamlet-0.7.1:Text.Hamlet.Quasi.htmlToHamletMonad > . preEscapedString) > "<!DOCTYPE html><html><head><title>"; > (hamlet-0.7.1:Text.Hamlet.Quasi.htmlToHamletMonad > . Text.Blaze.toHtml) > (Main.pageTitle content); > .... })) } > > As far as I can tell, I only made a cosmetic change. I don't know > what's going on here. > > > > [Original] > > > data Page = Page > > { pageName :: String > > , pageSlug :: String > > , pageContent :: String ******** I'm going to change this > ********** > > } > > [Changed] > > > data Page = Page > > { pageTitle :: String > > , pageSlug :: String -- ^ used in the URL > > , pageContent :: IO String ******** This is the change > ******* > > } > > > Here's where I run into trouble > > [Original] > > json page = jsonMap > > [ ("name", jsonScalar $ pageName page) > > , ("content", jsonScalar $ pageContent page) ******** I'm going > to change this ******** > > ] > > > > [My changes] > > > json page = jsonMap > > [ ("name", jsonScalar $ Main.pageTitle page) > > , ("content", jsonScalar $ liftIO $ pageContent page) ******* This > is the change *********** > > ] > > > Here's the compiler error > > test.lhs:107:35: > Couldn't match expected type `Char' against inferred type `[Char]' > Expected type: String > Inferred type: [String] > In the second argument of `($)', namely `liftIO $ pageContent page' > In the expression: jsonScalar $ liftIO $ pageContent page > Failed, modules loaded: none. > > > I'd appreciate a discussion about why this is wrong, and perhaps clues > as to what is right. > > > Last problem, stemming from the change in type to IO String. I don't > have a clue as to what change I should make. > > test.lhs:100:25: > No instance for (Text.Blaze.ToHtml (IO String)) > arising from a use of `Text.Blaze.toHtml' > at test.lhs:(100,25)-(103,3) > Possible fix: > add an instance declaration for (Text.Blaze.ToHtml (IO String)) > In the second argument of `(.)', namely `Text.Blaze.toHtml' > In a stmt of a 'do' expression: > (hamlet-0.7.1:Text.Hamlet.Quasi.htmlToHamletMonad > . Text.Blaze.toHtml) > (pageContent page) > In the first argument of > `hamlet-0.7.1:Text.Hamlet.Quasi.toHamletValue', nam > > ely > `do { (hamlet-0.7.1:Text.Hamlet.Quasi.htmlToHamletMonad > . preEscapedString) > "<h1>"; > (hamlet-0.7.1:Text.Hamlet.Quasi.htmlToHamletMonad > . Text.Blaze.toHtml) > (Main.pageTitle page); > (hamlet-0.7.1:Text.Hamlet.Quasi.htmlToHamletMonad > . preEscapedString) > "</h1><article>"; > (hamlet-0.7.1:Text.Hamlet.Quasi.htmlToHamletMonad > . Text.Blaze.toHtml) > (pageContent page); > .... }' > > > > And finally, both files can be found below, if it is necessary to look at > them. > > > [Original] > > <p>We're going to write a very simple AJAX application. It will be a > simple site with a few pages and a navbar; when you have Javascript, > clicking on the links will load the pages via AJAX. Otherwise, it will > use static HTML.</p> > > <p>We're going to use jQuery for the Javascript, though anything would > work just fine. Also, the AJAX responses will be served as JSON. Let's > get started.</p> > > > {-# LANGUAGE ScopedTypeVariables, TypeFamilies, QuasiQuotes, > TemplateHaskell, MultiParamTypeClasses #-} > > import Yesod > > import Yesod.Helpers.Static > > import Data.Monoid (mempty) > > Like the blog example, we'll define some data first. > > > data Page = Page > > { pageName :: String > > , pageSlug :: String > > , pageContent :: String > > } > > > loadPages :: IO [Page] > > loadPages = return > > [ Page "Page 1" "page-1" "My first page" > > , Page "Page 2" "page-2" "My second page" > > , Page "Page 3" "page-3" "My third page" > > ] > > loadPages :: IO [Page] > loadPages = do > > > > > data Ajax = Ajax > > { ajaxPages :: [Page] > > , ajaxStatic :: Static > > } > > type Handler = GHandler Ajax Ajax > > Next we'll generate a function for each file in our static folder. > This way, we get a compiler warning when trying to using a file which > does not exist. > > > staticFiles "static/yesod/ajax" > > Now the routes; we'll have a homepage, a pattern for the pages, and > use a static subsite for the Javascript and CSS files. > > > mkYesod "Ajax" [$parseRoutes| > > / HomeR GET > > /page/#String PageR GET > > /static StaticR Static ajaxStatic > > |] > > <p>That third line there is the syntax for a subsite: Static is the > datatype for the subsite argument; siteStatic returns the site itself > (parse, render and dispatch functions); and ajaxStatic gets the > subsite argument from the master argument.</p> > > <p>Now, we'll define the Yesod instance. We'll still use a dummy > approot value, but we're also going to define a default layout.</p> > > > instance Yesod Ajax where > > approot _ = "" > > defaultLayout widget = do > > Ajax pages _ <- getYesod > > content <- widgetToPageContent widget > > hamletToRepHtml [$hamlet| > > \<!DOCTYPE html> > > > > <html> > > <head> > > <title>#{pageTitle content} > > <link rel="stylesheet" href="@{StaticR style_css}"> > > <script src=" > http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"> > > <script src="@{StaticR script_js}"> > > \^{pageHead content} > > <body> > > <ul id="navbar"> > > $forall page <- pages > > <li> > > <a href="@{PageR (pageSlug page)}">#{pageName page} > > <div id="content"> > > \^{pageBody content} > > |] > > <p>The Hamlet template refers to style_css and style_js; these were > generated by the call to staticFiles above. There's nothing > Yesod-specific about the <a > href="/static/yesod/ajax/style.css">style.css</a> and <a > href="/static/yesod/ajax/script.js">script.js</a> files, so I won't > describe them here.</p> > > <p>Now we need our handler functions. We'll have the homepage simply > redirect to the first page, so:</p> > > > getHomeR :: Handler () > > getHomeR = do > > Ajax pages _ <- getYesod > > let first = head pages > > redirect RedirectTemporary $ PageR $ pageSlug first > > And now the cool part: a handler that returns either HTML or JSON > data, depending on the request headers. > > > getPageR :: String -> Handler RepHtmlJson > > getPageR slug = do > > Ajax pages _ <- getYesod > > case filter (\e -> pageSlug e == slug) pages of > > [] -> notFound > > page:_ -> defaultLayoutJson (do > > setTitle $ string $ pageName page > > addHamlet $ html page > > ) (json page) > > where > > html page = [$hamlet| > > <h1>#{pageName page} > > <article>#{pageContent page} > > |] > > json page = jsonMap > > [ ("name", jsonScalar $ pageName page) > > , ("content", jsonScalar $ pageContent page) > > ] > > <p>We first try and find the appropriate Page, returning a 404 if it's > not there. We then use the applyLayoutJson function, which is really > the heart of this example. It allows you an easy way to create > responses that will be either HTML or JSON, and which use the default > layout in the HTML responses. It takes four arguments: 1) the title of > the HTML page, 2) some value, 3) a function from that value to a > Hamlet value, and 4) a function from that value to a Json value.</p> > > <p>Under the scenes, the Json monad is really just using the Hamlet > monad, so it gets all of the benefits thereof, namely interleaved IO > and enumerator output. It is pretty straight-forward to generate JSON > output by using the three functions jsonMap, jsonList and jsonMap. One > thing to note: the input to jsonScalar must be HtmlContent; this helps > avoid cross-site scripting attacks, by ensuring that any HTML entities > will be escaped.</p> > > <p>And now our typical main function. We need two parameters to build > our Ajax value: the pages, and the static loader. We'll load up from a > local directory.</p> > > > main :: IO () > > main = do > > > > pages <- loadPages > > let s = static "static/yesod/ajax" > > warpDebug 3000 $ Ajax pages s > > > [My changes] > > {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, > MultiParamTypeClasses > > #-} > > import Yesod > > import Yesod.Helpers.Static > > import System.Environment > > import System.IO > > import System.Directory > > import System.FilePath.Posix > > import Control.Applicative > > import Data.List.Split > > > > > data Page = Page > > { pageTitle :: String > > , pageSlug :: String -- ^ used in the URL > > , pageContent :: IO String > > } > > > > > loadPage :: IO [Page] > > loadPage = do > > let directoryPath = "/home/mlitchard/playground/webTests/files" > > let processedPath = map (directoryPath </>) . filter (`notElem` > [".",".."]) > > pageFileNames <- processedPath <$> getDirectoryContents directoryPath > > let pageFiles = map readFile pageFileNames > > return $ zipWith popEntries pageFileNames pageFiles > > -- > return $ zipWith popEntries > > > popEntries :: FilePath -> IO String -> Page > > popEntries pageFileName pageFile = > > let pageT = last $ splitOn "/" pageFileName > > pageS = "Job" ++ pageT > > in Page { Main.pageTitle=pageT, > > pageSlug=pageS, > > pageContent=pageFile } > > > data Tframe = Tframe > > { tframePages :: [Page] > > , tframeStatic :: Static > > } > > > type Handler = GHandler Tframe Tframe > > > staticFiles "static/yesod/ajax" > > Routes > > > mkYesod "Tframe" [$parseRoutes| > > / HomeR GET > > /page/#String PageR GET > > /static StaticR Static tframeStatic > > |] > > defining the Yesod instance > > > instance Yesod Tframe where > > approot _ = "" > > defaultLayout widget = do > > Tframe pages <- getYesod > > content <- widgetToPageContent widget > > hamletToRepHtml [$hamlet| > > \<!DOCTYPE html> > > > > <html> > > <head> > > <title>#{Main.pageTitle content} > > <link rel="stylesheet" href="@{StaticR style_css}"> > > <script src=" > http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"> > > <script src="@{StaticR script_js}"> > > \^{pageHead content} > > <body> > > <ul id="navbar"> > > $forall page <- pages > > <li> > > <a href="@{PageR (pageSlug page)}">#{Main.pageTitle page} > > <div id="content"> > > \^{pageBody content} > > |] > > > > getHomeR :: Handler () > > getHomeR = do > > Tframe pages _ <- getYesod > > let first = head pages > > redirect RedirectTemporary $ PageR $ pageSlug first > > > getPageR :: String -> Handler RepHtmlJson > > getPageR slug = do > > Tframe pages _ <- getYesod > > case filter (\e -> pageSlug e == slug) pages of > > [] -> notFound > > page:_ -> defaultLayoutJson (do > > setTitle $ string $ Main.pageTitle page > > addHamlet $ html page > > ) (json page) > > where > > html page = [$hamlet| > > <h1>#{Main.pageTitle page} > > <article>#{pageContent page} > > |] > > > json page = jsonMap > > [ ("name", jsonScalar $ Main.pageTitle page) > > , ("content", jsonScalar $ liftIO $ pageContent page) > > ] > > > main :: IO () > > main = do > > > > pages <- loadPage > > let s = static "static/yesod/ajax" > > warpDebug 3000 $ Tframe pages s > > > If you've read to the bottom, thanks for your patience. I appreciate > any illumination you can send my way. > > > Michael > > _______________________________________________ > Haskell-Cafe mailing list > Haskell-Cafe@haskell.org > http://www.haskell.org/mailman/listinfo/haskell-cafe >
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe