packages feed

yesod-crud 0.1.1 → 0.1.2

raw patch · 5 files changed

+211/−16 lines, 5 filesdep +MissingHdep +safedep ~classy-preludePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: MissingH, safe

Dependency ranges changed: classy-prelude

API changes (from Hackage documentation)

+ Yesod.Contrib.League.Crud: CrudMsgLinkView :: CrudMessage
+ Yesod.Contrib.League.Crud: CrudMsgTitleView :: Text -> CrudMessage
+ Yesod.Contrib.League.Crud: CrudMsgViewLinkNext :: CrudMessage
+ Yesod.Contrib.League.Crud: crudViewWidget :: Crud sub => Ent sub -> CrudM sub (CrudWidget sub)
+ Yesod.Contrib.League.Crud: getCrudViewR :: Crud sub => ObjId sub -> CrudHandler sub TypedContent
+ Yesod.Contrib.League.Crud.Sort: Sort :: k -> Bool -> Sort k
+ Yesod.Contrib.League.Crud.Sort: Sorts :: [Sort k] -> Sorts k
+ Yesod.Contrib.League.Crud.Sort: cancelLink :: (SortC k, Crud sub) => k -> Sorts k -> (Route (CrudSubsite sub) -> Route (Site sub)) -> (Route (Site sub), [(Text, Text)])
+ Yesod.Contrib.League.Crud.Sort: cancelSort :: SortC k => k -> Sorts k -> Sorts k
+ Yesod.Contrib.League.Crud.Sort: data Sort k
+ Yesod.Contrib.League.Crud.Sort: getSorts :: (SortC k, Crud sub) => CrudM sub (Sorts k)
+ Yesod.Contrib.League.Crud.Sort: instance Bounded k => Bounded (Sort k)
+ Yesod.Contrib.League.Crud.Sort: instance Enum k => Enum (Sort k)
+ Yesod.Contrib.League.Crud.Sort: instance Eq k => Eq (Sort k)
+ Yesod.Contrib.League.Crud.Sort: instance Eq k => Eq (Sorts k)
+ Yesod.Contrib.League.Crud.Sort: instance Show k => Show (Sort k)
+ Yesod.Contrib.League.Crud.Sort: instance Show k => Show (Sorts k)
+ Yesod.Contrib.League.Crud.Sort: isSortedBy :: SortC k => k -> Sorts k -> Maybe (Int, Bool)
+ Yesod.Contrib.League.Crud.Sort: newtype Sorts k
+ Yesod.Contrib.League.Crud.Sort: sortAsc :: Sort k -> Bool
+ Yesod.Contrib.League.Crud.Sort: sortIndicator :: (SortC k, Crud sub) => k -> Sorts k -> (Route (CrudSubsite sub) -> Route (Site sub)) -> CrudWidget sub
+ Yesod.Contrib.League.Crud.Sort: sortKey :: Sort k -> k
+ Yesod.Contrib.League.Crud.Sort: sortToSelectOpt :: ToEntityField k f -> Sort k -> SelectOpt f
+ Yesod.Contrib.League.Crud.Sort: sortsList :: Sorts k -> [Sort k]
+ Yesod.Contrib.League.Crud.Sort: sortsQuery :: SortC k => Sorts k -> [(Text, Text)]
+ Yesod.Contrib.League.Crud.Sort: sortsToSelectOpts :: ToEntityField k f -> Sorts k -> [SelectOpt f]
+ Yesod.Contrib.League.Crud.Sort: toggleLink :: (SortC k, Crud sub) => k -> Bool -> Sorts k -> (Route (CrudSubsite sub) -> Route (Site sub)) -> (Route (Site sub), [(Text, Text)])
+ Yesod.Contrib.League.Crud.Sort: toggleSort :: SortC k => k -> Bool -> Sorts k -> Sorts k
+ Yesod.Contrib.League.Crud.Sort: type SortC k = (Eq k, Enum k, Bounded k)
+ Yesod.Contrib.League.Crud.Sort: type ToEntityField k f = forall a. k -> (forall t. EntityField f t -> a) -> a
- Yesod.Contrib.League.Crud: class (CrudTypes sub, Yesod (Site sub), RenderMessage (Site sub) FormMessage, RenderMessage (CrudSubsite sub) CrudMessage) => Crud sub where crudSelect = crudDB >>= crudSelect' crudInsert obj = crudDB >>= flip crudInsert' obj crudGet k = crudDB >>= flip crudGet' k crudReplace k obj = do { db <- crudDB; crudReplace' db k obj } crudDelete k = crudDB >>= flip crudDelete' k crudShowHtml = fmap toHtml . crudShow crudNextPage _ = getRouter <*> pure CrudListR crudAlert route response = setMessage =<< defaultCrudAlertMessage route response crudListWidget = do { mr <- getMessenger; r2p <- getRouter; objects <- crudSelect; let kv (objId, obj) = (objId,) <$> crudShowHtml obj; pairs <- mapM kv objects; return (do { condH [((==) (length pairs) 0, do { (asWidgetT . toWidget) ((preEscapedText . pack) "<p>"); (asWidgetT . toWidget) (toHtml (mr CrudMsgNoEntities)); (asWidgetT . toWidget) ((preEscapedText . pack) "</p>") })] (Just (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<ol>"); mapM_ (\ (k_aeVX, s_aeVY) -> do { (asWidgetT . toWidget) ((preEscapedText . pack) "<li>"); (asWidgetT . toWidget) (toHtml s_aeVY); (asWidgetT . toWidget) ((preEscapedText . pack) "\n\ \<a href=\""); (getUrlRenderParams >>= (\ urender_aeVZ -> (asWidgetT . toWidget) (toHtml (\ u_aeW0 -> urender_aeVZ u_aeW0 [] (r2p (CrudUpdateR k_aeVX)))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) (toHtml (mr CrudMsgLinkUpdate)); (asWidgetT . toWidget) ((preEscapedText . pack) "</a>\n\ \<a href=\""); (getUrlRenderParams >>= (\ urender_aeW1 -> (asWidgetT . toWidget) (toHtml (\ u_aeW2 -> urender_aeW1 u_aeW2 [] (r2p (CrudDeleteR k_aeVX)))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) (toHtml (mr CrudMsgLinkDelete)); (asWidgetT . toWidget) ((preEscapedText . pack) "</a>\n\ \</li>") }) pairs; (asWidgetT . toWidget) ((preEscapedText . pack) "</ol>") })); (asWidgetT . toWidget) ((preEscapedText . pack) "<p><a href=\""); (getUrlRenderParams >>= (\ urender_aeW3 -> (asWidgetT . toWidget) (toHtml (\ u_aeW4 -> urender_aeW3 u_aeW4 [] (r2p CrudCreateR))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) (toHtml (mr CrudMsgLinkCreate)); (asWidgetT . toWidget) ((preEscapedText . pack) "</a>\n\ \</p>") }) } crudFormWidget action (w, enc) = do { mr <- getMessenger; r2p <- getRouter; return (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<form method=\"post\" action=\""); (getUrlRenderParams >>= (\ urender_aeWd -> (asWidgetT . toWidget) (toHtml (\ u_aeWe -> urender_aeWd u_aeWe [] (r2p action))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\" enctype=\""); (asWidgetT . toWidget) (toHtml enc); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) w; (asWidgetT . toWidget) ((preEscapedText . pack) "\n\ \<input type=\"submit\" value=\""); (asWidgetT . toWidget) (toHtml (mr CrudMsgButtonSubmit)); (asWidgetT . toWidget) ((preEscapedText . pack) "\">\n\ \</form>") }) } crudCreateWidget = do { form <- crudMakeForm Nothing; widgetEnc <- liftHandlerT $ generateFormPost form; crudFormWidget CrudCreateR widgetEnc } crudDeleteWidget ent = do { txt <- crudShow $ snd ent; mr <- getMessenger; return (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<p>"); (asWidgetT . toWidget) (toHtml (mr (CrudMsgConfirmDelete txt))); (asWidgetT . toWidget) ((preEscapedText . pack) "</p>\n\ \<input type=\"submit\" value=\""); (asWidgetT . toWidget) (toHtml (mr CrudMsgButtonDelete)); (asWidgetT . toWidget) ((preEscapedText . pack) "\">") }) } crudFormLayout action widgetEnc = do { mr <- getMessenger; fw <- crudFormWidget action widgetEnc; crudLayout $ do { setTitle . toHtml . mr . routeToTitle action $ mr CrudMsgEntity; fw } } crudLayout = liftHandlerT . defaultLayout getCrudListR = runCrudSubsite $ do { mr <- getMessenger; lw <- crudListWidget; crudLayout $ do { setTitle . toHtml $ mr CrudMsgEntities; lw } } getCrudCreateR = runCrudSubsite $ do { form <- crudMakeForm Nothing; widgetEnc <- liftHandlerT $ generateFormPost form; crudFormLayout CrudCreateR widgetEnc } postCrudCreateR = runCrudSubsite $ do { form <- crudMakeForm Nothing; ((result, w), enc) <- liftHandlerT $ runFormPost form; case result of { FormSuccess obj -> do { kOpt <- try $ crudInsert obj; case kOpt of { Left e -> crudAlert CrudCreateR (Left e) Right objId -> do { crudAlert CrudCreateR (Right obj); crudNextPage (Just objId) >>= redirect } } } _ -> pure () }; crudFormLayout CrudCreateR (w, enc) } getCrudDeleteR objId = runCrudSubsite $ do { mr <- getMessenger; r2p <- getRouter; obj <- crudGet objId >>= maybe404; (tokenW, enc) <- generateFormPost . renderDivs $ pure (); confirmW <- crudDeleteWidget (objId, obj); crudLayout $ do { setTitle . toHtml . mr . CrudMsgTitleDelete $ mr CrudMsgEntity; (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<form method=\"post\" action=\""); (getUrlRenderParams >>= (\ urender_aeXL -> (asWidgetT . toWidget) (toHtml (\ u_aeXM -> urender_aeXL u_aeXM [] (r2p (CrudDeleteR objId)))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\" enctype=\""); (asWidgetT . toWidget) (toHtml enc); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) tokenW; (asWidgetT . toWidget) ((preEscapedText . pack) ""); (asWidgetT . toWidget) confirmW; (asWidgetT . toWidget) ((preEscapedText . pack) "</form>") }) } } postCrudDeleteR objId = runCrudSubsite $ do { obj <- crudGet objId >>= maybe404; crudDelete objId; crudAlert (CrudDeleteR objId) (Right obj); crudNextPage Nothing >>= redirect } getCrudUpdateR objId = runCrudSubsite $ do { obj <- crudGet objId >>= maybe404; form <- crudMakeForm (Just obj); widgetEnc <- liftHandlerT $ generateFormPost form; crudFormLayout (CrudUpdateR objId) widgetEnc } postCrudUpdateR objId = runCrudSubsite $ do { obj <- crudGet objId >>= maybe404; form <- crudMakeForm (Just obj); ((result, w), enc) <- liftHandlerT $ runFormPost form; case result of { FormSuccess newObj -> do { eq <- crudEq obj newObj; if eq then do { crudAlert CrudListR (Right obj); crudNextPage (Just objId) >>= redirect } else do { unitOrExn <- try $ crudReplace objId newObj; crudAlert (CrudUpdateR objId) (const newObj <$> unitOrExn); when (isRight unitOrExn) $ crudNextPage (Just objId) >>= redirect } } _ -> pure () }; crudFormLayout (CrudUpdateR objId) (w, enc) }
+ Yesod.Contrib.League.Crud: class (CrudTypes sub, Yesod (Site sub), RenderMessage (Site sub) FormMessage, RenderMessage (CrudSubsite sub) CrudMessage) => Crud sub where crudSelect = crudDB >>= crudSelect' crudInsert obj = crudDB >>= flip crudInsert' obj crudGet k = crudDB >>= flip crudGet' k crudReplace k obj = do { db <- crudDB; crudReplace' db k obj } crudDelete k = crudDB >>= flip crudDelete' k crudShowHtml = fmap toHtml . crudShow crudNextPage _ = getRouter <*> pure CrudListR crudAlert route response = setMessage =<< defaultCrudAlertMessage route response crudListWidget = do { mr <- getMessenger; r2p <- getRouter; objects <- crudSelect; let kv (objId, obj) = (objId,) <$> crudShowHtml obj; pairs <- mapM kv objects; return (do { condH [((==) (length pairs) 0, do { (asWidgetT . toWidget) ((preEscapedText . pack) "<p>"); (asWidgetT . toWidget) (toHtml (mr CrudMsgNoEntities)); (asWidgetT . toWidget) ((preEscapedText . pack) "</p>") })] (Just (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<ol>"); mapM_ (\ (k_afzh, s_afzi) -> do { (asWidgetT . toWidget) ((preEscapedText . pack) "<li>"); (asWidgetT . toWidget) (toHtml s_afzi); (asWidgetT . toWidget) ((preEscapedText . pack) "\n\ \<a href=\""); (getUrlRenderParams >>= (\ urender_afzj -> (asWidgetT . toWidget) (toHtml (\ u_afzk -> urender_afzj u_afzk [] (r2p (CrudUpdateR k_afzh)))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) (toHtml (mr CrudMsgLinkUpdate)); (asWidgetT . toWidget) ((preEscapedText . pack) "</a>\n\ \<a href=\""); (getUrlRenderParams >>= (\ urender_afzl -> (asWidgetT . toWidget) (toHtml (\ u_afzm -> urender_afzl u_afzm [] (r2p (CrudDeleteR k_afzh)))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) (toHtml (mr CrudMsgLinkDelete)); (asWidgetT . toWidget) ((preEscapedText . pack) "</a>\n\ \<a href=\""); (getUrlRenderParams >>= (\ urender_afzn -> (asWidgetT . toWidget) (toHtml (\ u_afzo -> urender_afzn u_afzo [] (r2p (CrudViewR k_afzh)))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) (toHtml (mr CrudMsgLinkView)); (asWidgetT . toWidget) ((preEscapedText . pack) "</a>\n\ \</li>") }) pairs; (asWidgetT . toWidget) ((preEscapedText . pack) "</ol>") })); (asWidgetT . toWidget) ((preEscapedText . pack) "<p><a href=\""); (getUrlRenderParams >>= (\ urender_afzp -> (asWidgetT . toWidget) (toHtml (\ u_afzq -> urender_afzp u_afzq [] (r2p CrudCreateR))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) (toHtml (mr CrudMsgLinkCreate)); (asWidgetT . toWidget) ((preEscapedText . pack) "</a>\n\ \</p>") }) } crudFormWidget action (w, enc) = do { mr <- getMessenger; r2p <- getRouter; return (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<form method=\"post\" action=\""); (getUrlRenderParams >>= (\ urender_afzz -> (asWidgetT . toWidget) (toHtml (\ u_afzA -> urender_afzz u_afzA [] (r2p action))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\" enctype=\""); (asWidgetT . toWidget) (toHtml enc); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) w; (asWidgetT . toWidget) ((preEscapedText . pack) "\n\ \<input type=\"submit\" value=\""); (asWidgetT . toWidget) (toHtml (mr CrudMsgButtonSubmit)); (asWidgetT . toWidget) ((preEscapedText . pack) "\">\n\ \</form>") }) } crudCreateWidget = do { form <- crudMakeForm Nothing; widgetEnc <- liftHandlerT $ generateFormPost form; crudFormWidget CrudCreateR widgetEnc } crudViewWidget ent = do { objW <- crudShowHtml $ snd ent; return (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<p>"); (asWidgetT . toWidget) objW; (asWidgetT . toWidget) ((preEscapedText . pack) "</p>") }) } crudDeleteWidget ent = do { txt <- crudShow $ snd ent; mr <- getMessenger; return (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<p>"); (asWidgetT . toWidget) (toHtml (mr (CrudMsgConfirmDelete txt))); (asWidgetT . toWidget) ((preEscapedText . pack) "</p>\n\ \<input type=\"submit\" value=\""); (asWidgetT . toWidget) (toHtml (mr CrudMsgButtonDelete)); (asWidgetT . toWidget) ((preEscapedText . pack) "\">") }) } crudFormLayout action widgetEnc = do { mr <- getMessenger; fw <- crudFormWidget action widgetEnc; crudLayout $ do { setTitle . toHtml . mr . routeToTitle action $ mr CrudMsgEntity; fw } } crudLayout = liftHandlerT . defaultLayout getCrudListR = runCrudSubsite $ do { mr <- getMessenger; lw <- crudListWidget; res <- crudLayout $ do { setTitle . toHtml $ mr CrudMsgEntities; lw }; return $ toTypedContent res } getCrudCreateR = runCrudSubsite $ do { form <- crudMakeForm Nothing; widgetEnc <- liftHandlerT $ generateFormPost form; res <- crudFormLayout CrudCreateR widgetEnc; return $ toTypedContent res } postCrudCreateR = runCrudSubsite $ do { form <- crudMakeForm Nothing; ((result, w), enc) <- liftHandlerT $ runFormPost form; case result of { FormSuccess obj -> do { kOpt <- try $ crudInsert obj; case kOpt of { Left e -> crudAlert CrudCreateR (Left e) Right objId -> do { crudAlert CrudCreateR (Right obj); crudNextPage (Just objId) >>= redirect } } } _ -> pure () }; res <- crudFormLayout CrudCreateR (w, enc); return $ toTypedContent res } getCrudViewR objId = runCrudSubsite $ do { mr <- getMessenger; obj <- crudGet objId >>= maybe404; objW <- crudViewWidget (objId, obj); r2p <- getRouter; res <- crudLayout $ do { setTitle . toHtml . mr . CrudMsgTitleView $ mr CrudMsgEntity; (do { (asWidgetT . toWidget) objW; (asWidgetT . toWidget) ((preEscapedText . pack) "\n\ \<p><a href="); (getUrlRenderParams >>= (\ urender_afDL -> (asWidgetT . toWidget) (toHtml (\ u_afDM -> urender_afDL u_afDM [] (r2p CrudListR))))); (asWidgetT . toWidget) ((preEscapedText . pack) ">"); (asWidgetT . toWidget) (toHtml (mr CrudMsgViewLinkNext)); (asWidgetT . toWidget) ((preEscapedText . pack) "</p>") }) }; return $ toTypedContent res } getCrudDeleteR objId = runCrudSubsite $ do { mr <- getMessenger; r2p <- getRouter; obj <- crudGet objId >>= maybe404; (tokenW, enc) <- generateFormPost . renderDivs $ pure (); confirmW <- crudDeleteWidget (objId, obj); res <- crudLayout $ do { setTitle . toHtml . mr . CrudMsgTitleDelete $ mr CrudMsgEntity; (do { (asWidgetT . toWidget) ((preEscapedText . pack) "<form method=\"post\" action=\""); (getUrlRenderParams >>= (\ urender_afDY -> (asWidgetT . toWidget) (toHtml (\ u_afDZ -> urender_afDY u_afDZ [] (r2p (CrudDeleteR objId)))))); (asWidgetT . toWidget) ((preEscapedText . pack) "\" enctype=\""); (asWidgetT . toWidget) (toHtml enc); (asWidgetT . toWidget) ((preEscapedText . pack) "\">"); (asWidgetT . toWidget) tokenW; (asWidgetT . toWidget) ((preEscapedText . pack) ""); (asWidgetT . toWidget) confirmW; (asWidgetT . toWidget) ((preEscapedText . pack) "</form>") }) }; return $ toTypedContent res } postCrudDeleteR objId = runCrudSubsite $ do { obj <- crudGet objId >>= maybe404; crudDelete objId; crudAlert (CrudDeleteR objId) (Right obj); crudNextPage Nothing >>= redirect } getCrudUpdateR objId = runCrudSubsite $ do { obj <- crudGet objId >>= maybe404; form <- crudMakeForm (Just obj); widgetEnc <- liftHandlerT $ generateFormPost form; res <- crudFormLayout (CrudUpdateR objId) widgetEnc; return $ toTypedContent res } postCrudUpdateR objId = runCrudSubsite $ do { obj <- crudGet objId >>= maybe404; form <- crudMakeForm (Just obj); ((result, w), enc) <- liftHandlerT $ runFormPost form; case result of { FormSuccess newObj -> do { eq <- crudEq obj newObj; if eq then do { crudAlert CrudListR (Right obj); crudNextPage (Just objId) >>= redirect } else do { unitOrExn <- try $ crudReplace objId newObj; crudAlert (CrudUpdateR objId) (const newObj <$> unitOrExn); when (isRight unitOrExn) $ crudNextPage (Just objId) >>= redirect } } _ -> pure () }; res <- crudFormLayout (CrudUpdateR objId) (w, enc); return $ toTypedContent res }
- Yesod.Contrib.League.Crud: getCrudCreateR :: Crud sub => CrudHandler sub Html
+ Yesod.Contrib.League.Crud: getCrudCreateR :: Crud sub => CrudHandler sub TypedContent
- Yesod.Contrib.League.Crud: getCrudDeleteR :: Crud sub => ObjId sub -> CrudHandler sub Html
+ Yesod.Contrib.League.Crud: getCrudDeleteR :: Crud sub => ObjId sub -> CrudHandler sub TypedContent
- Yesod.Contrib.League.Crud: getCrudListR :: Crud sub => CrudHandler sub Html
+ Yesod.Contrib.League.Crud: getCrudListR :: Crud sub => CrudHandler sub TypedContent
- Yesod.Contrib.League.Crud: getCrudUpdateR :: Crud sub => ObjId sub -> CrudHandler sub Html
+ Yesod.Contrib.League.Crud: getCrudUpdateR :: Crud sub => ObjId sub -> CrudHandler sub TypedContent
- Yesod.Contrib.League.Crud: postCrudCreateR :: Crud sub => CrudHandler sub Html
+ Yesod.Contrib.League.Crud: postCrudCreateR :: Crud sub => CrudHandler sub TypedContent
- Yesod.Contrib.League.Crud: postCrudDeleteR :: Crud sub => ObjId sub -> CrudHandler sub Html
+ Yesod.Contrib.League.Crud: postCrudDeleteR :: Crud sub => ObjId sub -> CrudHandler sub TypedContent
- Yesod.Contrib.League.Crud: postCrudUpdateR :: Crud sub => ObjId sub -> CrudHandler sub Html
+ Yesod.Contrib.League.Crud: postCrudUpdateR :: Crud sub => ObjId sub -> CrudHandler sub TypedContent

Files

Yesod/Contrib/League/Crud.hs view
@@ -280,6 +280,7 @@               #{s}               <a href=@{r2p $ CrudUpdateR k}>#{mr CrudMsgLinkUpdate}               <a href=@{r2p $ CrudDeleteR k}>#{mr CrudMsgLinkDelete}+              <a href=@{r2p $ CrudViewR k}>#{mr CrudMsgLinkView}       <p>         <a href=@{r2p CrudCreateR}>#{mr CrudMsgLinkCreate}       |]@@ -312,6 +313,16 @@     widgetEnc <- liftHandlerT $ generateFormPost form     crudFormWidget CrudCreateR widgetEnc +  -- | View entity details+  crudViewWidget+    :: Ent sub -> CrudM sub (CrudWidget sub)++  crudViewWidget ent = do+    objW <- crudShowHtml $ snd ent+    return+      [whamlet|<p>^{objW}+      |]+   -- |Creates a widget to confirm deletion of an object. Override this if you   -- want to have a more sophisticated confirmation warning (such as showing   -- what other entities would be affected) or to change the delete button.@@ -352,28 +363,30 @@   -- |Handler for @GET@ on the list route @CrudListR@, to display a list of all   -- objects. Primarily uses 'crudListWidget' and 'crudLayout', but also sets a   -- title based on 'CrudMsgEntities'.-  getCrudListR :: CrudHandler sub Html+  getCrudListR :: CrudHandler sub TypedContent   getCrudListR = runCrudSubsite $ do     mr <- getMessenger     lw <- crudListWidget-    crudLayout $ do+    res <- crudLayout $ do       setTitle . toHtml $ mr CrudMsgEntities       lw+    return $ toTypedContent res    -- |Handler for @GET@ on the creation form @CrudCreateR@, to display an empty   -- form. Primarily uses 'crudMakeForm' and 'crudFormLayout'.-  getCrudCreateR :: CrudHandler sub Html+  getCrudCreateR :: CrudHandler sub TypedContent   getCrudCreateR = runCrudSubsite $ do     form <- crudMakeForm Nothing     widgetEnc <- liftHandlerT $ generateFormPost form-    crudFormLayout CrudCreateR widgetEnc+    res <- crudFormLayout CrudCreateR widgetEnc+    return $ toTypedContent res    -- |Handler for @POST@ on @CrudCreateR@, to create a new object. On success,   -- it inserts the object and displays an alert on the next page. If the form   -- validation fails, it displays the form again with errors. (If the database   -- insertion itself fails, it still moves on to the next page and displays   -- the exception as an alert.)-  postCrudCreateR :: CrudHandler sub Html+  postCrudCreateR :: CrudHandler sub TypedContent   postCrudCreateR = runCrudSubsite $ do     form <- crudMakeForm Nothing     ((result, w), enc) <- liftHandlerT $ runFormPost form@@ -387,28 +400,46 @@            crudAlert CrudCreateR (Right obj)            crudNextPage (Just objId) >>= redirect       _ -> pure ()-    crudFormLayout CrudCreateR (w, enc)+    res <- crudFormLayout CrudCreateR (w, enc)+    return $ toTypedContent res +  -- |Handler for @GET@ on @CrudViewR@, an object's details  request. It displays the+  -- object and employs an empty Yesod form for return.+  getCrudViewR :: ObjId sub -> CrudHandler sub TypedContent+  getCrudViewR objId = runCrudSubsite $ do+    mr <- getMessenger+    obj <- crudGet objId >>= maybe404+    objW <- crudViewWidget (objId, obj)+    r2p <- getRouter+    res <- crudLayout $ do+      setTitle . toHtml . mr . CrudMsgTitleView $ mr CrudMsgEntity+      [whamlet|+         ^{objW}+         <p><a href=@{r2p CrudListR}>#{mr CrudMsgViewLinkNext}+       |]+    return $ toTypedContent res+   -- |Handler for @GET@ on @CrudDeleteR@, a deletion request. It displays the   -- object and employs an empty Yesod form for its CSRF token.-  getCrudDeleteR :: ObjId sub -> CrudHandler sub Html+  getCrudDeleteR :: ObjId sub -> CrudHandler sub TypedContent   getCrudDeleteR objId = runCrudSubsite $ do     mr <- getMessenger     r2p <- getRouter     obj <- crudGet objId >>= maybe404     (tokenW, enc) <- generateFormPost . renderDivs $ pure ()     confirmW <- crudDeleteWidget (objId, obj)-    crudLayout $ do+    res <- crudLayout $ do       setTitle . toHtml . mr . CrudMsgTitleDelete $ mr CrudMsgEntity       [whamlet|        <form method=post action=@{r2p $ CrudDeleteR objId} enctype=#{enc}>          ^{tokenW}          ^{confirmW}        |]+    return $ toTypedContent res    -- |Handler for @POST@ on @CrudDeleteR@, confirming a deletion request. It   -- displays the result as an alert on the next page.-  postCrudDeleteR :: ObjId sub -> CrudHandler sub Html+  postCrudDeleteR :: ObjId sub -> CrudHandler sub TypedContent   postCrudDeleteR objId = runCrudSubsite $ do     obj <- crudGet objId >>= maybe404     crudDelete objId@@ -418,19 +449,20 @@   -- |Handler for @GET@ on @CrudUpdateR@, to display a form filled out with an   -- existing object. Most of the work is done by 'crudGet', 'crudMakeForm',   -- and 'crudFormLayout'.-  getCrudUpdateR :: ObjId sub -> CrudHandler sub Html+  getCrudUpdateR :: ObjId sub -> CrudHandler sub TypedContent   getCrudUpdateR objId = runCrudSubsite $ do     obj <- crudGet objId >>= maybe404     form <- crudMakeForm (Just obj)     widgetEnc <- liftHandlerT $ generateFormPost form-    crudFormLayout (CrudUpdateR objId) widgetEnc+    res <- crudFormLayout (CrudUpdateR objId) widgetEnc+    return $ toTypedContent res    -- |Handler for @POST@ on @CrudUpdateR@, to update an object. Guards against   -- updating an object that has not been changed, as detected by 'crudEq'. If   -- the form validation fails, it displays the form again with errors. (If the   -- database update itself fails, it still moves on to the next page and   -- displays the exception as an alert.)-  postCrudUpdateR :: ObjId sub -> CrudHandler sub Html+  postCrudUpdateR :: ObjId sub -> CrudHandler sub TypedContent   postCrudUpdateR objId = runCrudSubsite $ do     obj <- crudGet objId >>= maybe404     form <- crudMakeForm (Just obj)@@ -448,7 +480,8 @@             when (isRight unitOrExn) $               crudNextPage (Just objId) >>= redirect       _ -> pure ()-    crudFormLayout (CrudUpdateR objId) (w, enc)+    res <- crudFormLayout (CrudUpdateR objId) (w, enc)+    return $ toTypedContent res  maybe404 :: MonadHandler m => Maybe a -> m a maybe404 = maybe notFound return@@ -544,9 +577,12 @@   | CrudMsgLinkCreate           -- ^The link text leading to the create page   | CrudMsgLinkDelete           -- ^The link text leading to the delete page   | CrudMsgLinkUpdate           -- ^The link text leading to the update form+  | CrudMsgLinkView             -- ^The link text leading to the details page   | CrudMsgTitleCreate Text     -- ^The title "Create [entity]"   | CrudMsgTitleDelete Text     -- ^The title "Delete [entity]"   | CrudMsgTitleUpdate Text     -- ^The title "Update [entity]"+  | CrudMsgTitleView Text       -- ^The title "Show [entity]"+  | CrudMsgViewLinkNext         -- ^The link text to return from object details page  -- |Basic conversion of CRUD messages to English text. defaultCrudMessage :: CrudMessage -> Text@@ -563,10 +599,13 @@   CrudMsgConfirmDelete  obj  -> "Really delete " <> obj <> "?"   CrudMsgLinkCreate          -> "create"   CrudMsgLinkDelete          -> "delete"+  CrudMsgLinkView            -> "view"   CrudMsgLinkUpdate          -> "edit"   CrudMsgTitleCreate    noun -> "Create " <> noun   CrudMsgTitleDelete    noun -> "Delete " <> noun   CrudMsgTitleUpdate    noun -> "Update " <> noun+  CrudMsgTitleView      noun -> "Show " <> noun+  CrudMsgViewLinkNext        -> "Back"  -- |Choose and render an appropriate alert message after a CRUD operation. defaultCrudAlertMessage@@ -588,6 +627,7 @@   CrudCreateR     -> CrudMsgAlertCreated obj   (CrudUpdateR _) -> CrudMsgAlertUpdated obj   (CrudDeleteR _) -> CrudMsgAlertDeleted obj+  (CrudViewR _)   -> CrudMsgAlertNoChanges obj   CrudListR       -> CrudMsgAlertNoChanges obj  routeToTitle :: Route (CrudSubsite sub) -> Text -> CrudMessage@@ -595,6 +635,7 @@   CrudCreateR     -> CrudMsgTitleCreate obj   (CrudUpdateR _) -> CrudMsgTitleUpdate obj   (CrudDeleteR _) -> CrudMsgTitleDelete obj+  (CrudViewR _) -> CrudMsgTitleView obj   CrudListR       -> CrudMsgEntities  -- |Retrieve the message translator and renderer in the CRUD monad. Uses@@ -614,11 +655,13 @@   data Route (CrudSubsite sub)     = CrudListR     | CrudCreateR+    | CrudViewR (ObjId sub)     | CrudUpdateR (ObjId sub)     | CrudDeleteR (ObjId sub)    renderRoute CrudListR = ([], [])   renderRoute CrudCreateR = (["create"], [])+  renderRoute (CrudViewR k) = (["view", toPathPiece k], [])   renderRoute (CrudUpdateR k) = (["update", toPathPiece k], [])   renderRoute (CrudDeleteR k) = (["delete", toPathPiece k], []) @@ -629,6 +672,7 @@ instance CrudTypes sub => ParseRoute (CrudSubsite sub) where   parseRoute ([           ], _) = Just CrudListR   parseRoute (["create"   ], _) = Just CrudCreateR+  parseRoute (["view", k], _) = CrudViewR <$> fromPathPiece k   parseRoute (["update", k], _) = CrudUpdateR <$> fromPathPiece k   parseRoute (["delete", k], _) = CrudDeleteR <$> fromPathPiece k   parseRoute _ = Nothing
Yesod/Contrib/League/Crud/Persist.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE ScopedTypeVariables, Rank2Types #-} {-| Module: Yesod.Contrib.League.Crud.Persist Description: Representing CRUD entities using Persistent@@ -27,6 +28,7 @@   , PersistEntityBackend (Obj sub) ~ YesodPersistBackend (Site sub)   , PersistQuery (YesodPersistBackend (Site sub))   , ObjId sub ~ Key (Obj sub)+  , Crud sub   )  -- |Run a database query within the 'CrudM' monad.@@ -54,9 +56,19 @@ crudPersistDefaults :: CrudPersist sub => CrudDB sub crudPersistDefaults =   CrudDB-  { crudSelect' = crudSelectList [] [LimitTo 1000]+  { crudSelect' = crudPersistSelect   , crudInsert' = crudRunDB . insert   , crudGet' = crudRunDB . get   , crudReplace' = \k -> crudRunDB . replace k   , crudDelete' = crudRunDB . delete   }++crudPersistSelect :: CrudPersist sub => CrudM sub [Ent sub]+crudPersistSelect = crudSelectList [] []++----applySorts :: CrudPersist sub => [Sort (SortC sub) -> SelectOpt (Obj sub)+--applySorts :: forall sub. CrudPersist sub => Sorts (SortC sub) -> CrudM sub [SelectOpt (Obj sub)]+--applySorts (Sorts s) = mapM g s+--  where g :: Sort (SortC sub) -> CrudM sub (SelectOpt (Obj sub))+--        g (Sort c True) = return $ crudSelectOpts (Asc :: EntityField (Obj sub) t -> SelectOpt (Obj sub)) c+--        g (Sort c False) = return $ crudSelectOpts Desc c
Yesod/Contrib/League/Crud/Resources.hs view
@@ -19,6 +19,7 @@   [parseRoutes|   /                  CrudListR     GET   /create            CrudCreateR   GET POST+  /view/#ObjId-sub   CrudViewR     GET   /update/#ObjId-sub CrudUpdateR   GET POST   /delete/#ObjId-sub CrudDeleteR   GET POST   |]
+ Yesod/Contrib/League/Crud/Sort.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE Rank2Types #-}+module Yesod.Contrib.League.Crud.Sort+       ( Sort(..)+       , Sorts(..)+       , SortC+       , ToEntityField+       , cancelLink+       , cancelSort+       , getSorts+       , isSortedBy+       , sortIndicator+       , sortToSelectOpt+       , sortsQuery+       , sortsToSelectOpts+       , toggleLink+       , toggleSort+       ) where++import ClassyPrelude+import Data.String.Utils (split)+import Database.Persist+import Yesod.Contrib.League.Crud+import Yesod.Core+import Safe (toEnumMay)++data Sort k =+  Sort { sortKey :: k+       , sortAsc :: Bool+       }+  deriving (Eq, Show)++-- Assumes that Enum k produces non-negative int, so we can encode the up/down+-- flag as the sign.+instance Enum k => Enum (Sort k) where+  fromEnum (Sort k True) = fromEnum k + 1+  fromEnum (Sort k False) = - (fromEnum k + 1)+  toEnum i = Sort (toEnum (abs i - 1)) (i > 0)++instance Bounded k => Bounded (Sort k) where+  minBound = Sort maxBound False+  maxBound = Sort maxBound True++newtype Sorts k =+  Sorts { sortsList :: [Sort k] }+  deriving (Eq, Show)++type SortC k = (Eq k, Enum k, Bounded k)++type ToEntityField k f = forall a. k -> (forall t. EntityField f t -> a) -> a++getSorts :: (SortC k, Crud sub) => CrudM sub (Sorts k)+            -- TODO: can I cache this result?+getSorts = decodeSorts . maybe "" unpack <$> lookupGetParam "s"++encodeSorts :: SortC k => Sorts k -> String+encodeSorts = intercalate "." . map (show . fromEnum) . sortsList++decodeSorts :: SortC k => String -> Sorts k+decodeSorts = Sorts . catMaybes . map readEnumMay . split "."++readEnumMay :: SortC k => String -> Maybe k+readEnumMay s = readMay s >>= toEnumMay++sortToSelectOpt :: ToEntityField k f -> Sort k -> SelectOpt f+sortToSelectOpt f (Sort c True) = f c Asc+sortToSelectOpt f (Sort c False) = f c Desc++sortsToSelectOpts :: ToEntityField k f -> Sorts k -> [SelectOpt f]+sortsToSelectOpts f = map (sortToSelectOpt f) . sortsList++sortsQuery :: SortC k => Sorts k -> [(Text, Text)]+sortsQuery s =+  if null e then [] else [("s", pack e)]+  where e = encodeSorts s++toggleSort :: SortC k => k -> Bool -> Sorts k -> Sorts k+toggleSort k f s =+  case isSortedBy k s of+   Nothing  -> liftSorts (Sort k f :) $ cancelSort k s+   Just (_, asc) -> liftSorts (Sort k (not asc) :) $ cancelSort k s++sortEq :: SortC k => k -> Sort k -> Bool+sortEq k (Sort j _) = j == k++cancelSort :: SortC k => k -> Sorts k -> Sorts k+cancelSort k = liftSorts $ filter (not . sortEq k)++liftSorts :: ([Sort j] -> [Sort k]) -> Sorts j -> Sorts k+liftSorts f = Sorts . f . sortsList++isSortedBy :: SortC k => k -> Sorts k -> Maybe (Int, Bool)+isSortedBy k = loop 1 . sortsList+  where loop _ [] = Nothing+        loop i (Sort j asc : _) | j == k = Just (i, asc)+        loop i (_: s) = loop (i+1) s++toggleLink+  :: (SortC k, Crud sub)+     => k+     -> Bool+     -> Sorts k+     -> (Route (CrudSubsite sub) -> Route (Site sub))+     -> (Route (Site sub), [(Text, Text)])++toggleLink criterion defaultAscending sorts r =+  (r CrudListR, sortsQuery (toggleSort criterion defaultAscending sorts))++cancelLink+  :: (SortC k, Crud sub)+     => k+     -> Sorts k+     -> (Route (CrudSubsite sub) -> Route (Site sub))+     -> (Route (Site sub), [(Text, Text)])++cancelLink criterion sorts r =+  (r CrudListR, sortsQuery (cancelSort criterion sorts))++sortIndicator+  :: (SortC k, Crud sub)+     => k+     -> Sorts k+     -> (Route (CrudSubsite sub) -> Route (Site sub))+     -> CrudWidget sub++sortIndicator criterion sorts r =+  [whamlet|+   $maybe (i,k) <- isSortedBy criterion sorts+     <span .crud-sorts>+       $if k+         ↑+       $else+         ↓+       #{i}+       <a href=@?{cancelLink criterion sorts r}>×+   |]
yesod-crud.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                yesod-crud-version:             0.1.1+version:             0.1.2 synopsis:            Generic administrative CRUD operations as a Yesod subsite description:         This package helps you build administrative CRUD                      operations (Create, Retrieve, Update, Delete) into@@ -22,6 +22,7 @@ library   exposed-modules:     Yesod.Contrib.League.Crud                        Yesod.Contrib.League.Crud.Persist+                       Yesod.Contrib.League.Crud.Sort                        Yesod.Contrib.League.Crud.TVarMap   other-modules:       Yesod.Contrib.League.Crud.Resources   hs-source-dirs:      .@@ -39,11 +40,13 @@                        TypeFamilies                        ViewPatterns   build-depends:       base                  >= 4    && < 5-                     , classy-prelude        >= 0.11 && < 0.12+                     , classy-prelude        >= 0.11 && < 0.13                      , containers            >= 0.5  && < 0.6+                     , MissingH              >= 1.3  && < 1.4                      , monad-control         >= 1.0  && < 1.1                      , persistent            >= 2.1  && < 2.2                      , random                >= 1.1  && < 1.2+                     , safe                  >= 0.3.9                      , stm                   >= 2.4  && < 2.5                      , uuid                  >= 1.3  && < 1.4                      , yesod-core            >= 1.4  && < 1.5