yesod-paginator 0.2.1 → 0.2.2.1
raw patch · 3 files changed
+151/−128 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Yesod.Paginator.Widget: getCurrentPage :: GHandler s m Int
+ Yesod.Paginator.Widget: paginationWidget :: Int -> Int -> Int -> GWidget s m ()
Files
- Yesod/Paginator.hs +37/−127
- Yesod/Paginator/Widget.hs +112/−0
- yesod-paginator.cabal +2/−1
Yesod/Paginator.hs view
@@ -11,13 +11,41 @@ -- But uses an entirely different approach. -- -- There are two pagination functions. One for arbitrary items where you--- provide the list of things to be paginated, and one for paginating--- directly out of the database, you provide the same filters as you--- would to @selectList@.+-- provide the list of things to be paginated: --+-- > getSomeRoute = do+-- > things' <- getAllThings+-- >+-- > (things, widget) <- paginate 10 things'+-- >+-- > defaultLayout $ do+-- > [whamlet|+-- > $forall thing <- things+-- > ^{showThing thing}+-- >+-- > <div .pagination>+-- > ^{widget}+-- > |]+--+-- And another for paginating directly out of the database, you provide+-- the same filters as you would to @selectList@.+--+-- > getSomeRoute something = do+-- > -- note: things is [Entity val] just like selectList returns+-- > (things, widget) <- runDB $ selectPaginated 10 [SomeThing ==. something] []+-- >+-- > defaultLayout $ do+-- > [whamlet|+-- > $forall thing <- things+-- > ^{showThing $ entityVal thing}+-- >+-- > <div .pagination>+-- > ^{widget}+-- > |]+-- -- Both functions return a tuple: the first element being the list of--- items to display on this page and the second being a widget showing--- the pagination navagation links.+-- items (or Entities) to display on this page and the second being a+-- widget showing the pagination navagation links. -- ------------------------------------------------------------------------------- module Yesod.Paginator@@ -26,32 +54,11 @@ , paginationWidget ) where -import Yesod -- TODO: minimal deps-import Control.Monad (when)+import Yesod+import Yesod.Paginator.Widget import Control.Monad.Trans.Class (MonadTrans)-import Data.Text (Text)-import Data.Maybe (fromMaybe)-import qualified Data.Text as T --- | Paginate an existing list of items.------ > getSomeRoute = do--- > things' <- getAllThings--- >--- > (things, widget) <- paginate 10 things'--- >--- > defaultLayout $ do--- > [whamlet|--- > $forall thing <- things--- > ^{showThing thing}--- >--- > <div .pagination>--- > ^{widget}--- > |]----paginate :: Int -- ^ items per page- -> [a] -- ^ complete list of items- -> GHandler s m ([a], GWidget s m ())+paginate :: Int -> [a] -> GHandler s m ([a], GWidget s m ()) paginate per items = do p <- getCurrentPage @@ -60,25 +67,10 @@ return (xs, paginationWidget p per tot) --- | Paginate directly out of the database.------ > getSomeRoute something = do--- > -- note: things is [Entity val] just like selectList returns--- > (things, widget) <- runDB $ selectPaginated 10 [SomeThing ==. something] []--- >--- > defaultLayout $ do--- > [whamlet|--- > $forall thing <- things--- > ^{showThing $ entityVal thing}--- >--- > <div .pagination>--- > ^{widget}--- > |]--- selectPaginated :: ( MonadTrans (PersistEntityBackend v) , PersistEntity v , PersistQuery (PersistEntityBackend v) (GHandler s m))- => Int-> [Filter v] -> [SelectOpt v]+ => Int -> [Filter v] -> [SelectOpt v] -> PersistEntityBackend v (GHandler s m) ([Entity v], GWidget s1 m1 ()) selectPaginated per filters selectOpts = do p <- lift getCurrentPage@@ -86,85 +78,3 @@ xs <- selectList filters (selectOpts ++ [OffsetBy ((p-1)*per), LimitTo per]) return (xs, paginationWidget p per tot)---- | A widget showing pagination links. Follows bootstrap principles.--- Utilizes a \"p\" GET param but leaves all other GET params intact.-paginationWidget :: Int -- ^ current page- -> Int -- ^ items per page- -> Int -- ^ total number of items- -> GWidget s m ()-paginationWidget page per tot = do- -- total / per + 1 for any remainder- let pages = (\(n, r) -> n + (min r 1)) $ tot `divMod` per-- when (pages > 1) $ do- let prev = [1 ..(page-1)]- let next = [(page+1)..pages ]-- let lim = 9 -- don't show more than nine links on either side- let prev' = if length prev > lim then drop ((length prev) - lim) prev else prev- let next' = if length next > lim then take lim next else next-- curParams <- lift $ fmap reqGetParams getRequest-- [whamlet|- <ul>- ^{linkToDisabled (null prev) curParams (page - 1) "← Previous"}-- $if (/=) prev prev'- <li>^{linkTo curParams 1 "1"}- <li>- <a>...-- $forall p <- prev'- <li>^{linkTo curParams p (show p)}-- <li .active>- <a>#{show page}-- $forall n <- next'- <li>^{linkTo curParams n (show n)}-- $if (/=) next next'- <li>- <a>...- <li>^{linkTo curParams pages (show pages)}-- ^{linkToDisabled (null next) curParams (page + 1) "Next →"}- |]---- | looks up the \"p\" GET param and converts it to an Int. returns a--- default of 1 when conversion fails.-getCurrentPage :: GHandler s m Int-getCurrentPage = fmap (fromMaybe 1 . go) $ lookupGetParam "p"-- where- go :: Maybe Text -> Maybe Int- go mp = readIntegral . T.unpack =<< mp--updateGetParam :: [(Text,Text)] -> (Text,Text) -> Text-updateGetParam getParams (p, n) = (T.cons '?') . T.intercalate "&"- . map (\(k,v) -> k `T.append` "=" `T.append` v)- . (++ [(p, n)]) . filter ((/= p) . fst) $ getParams--linkTo :: [(Text,Text)] -> Int -> String -> GWidget s m ()-linkTo params pg txt = do- let param = ("p", T.pack $ show pg)-- [whamlet|- <a href="#{updateGetParam params param}">#{txt}- |]---- | Similiar, but used for Previous/Next so that there's no href when--- disabled-linkToDisabled :: Bool -- ^ disabled?- -> [(Text,Text)] -> Int -> String -> GWidget s m ()-linkToDisabled True _ _ txt = [whamlet|- <li .prev .disabled>- <a>#{txt}- |]--linkToDisabled _ params pg txt = [whamlet|- <li .prev>- ^{linkTo params pg txt}- |]
+ Yesod/Paginator/Widget.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+module Yesod.Paginator.Widget+ ( getCurrentPage + , paginationWidget+ ) where++import Yesod++import Control.Monad (when)+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import qualified Data.Text as T++-- | Individual links to pages need to follow strict (but sane) markup+-- to be styled correctly by bootstrap. This type allows construction+-- of such links in both enabled and disabled states.+data PageLink = Enabled Int String String -- ^ page, content, class+ | Disabled String String -- ^ content, class++-- | Correctly show one of the constructed links+showLink :: [(Text, Text)] -> PageLink -> GWidget s m ()+showLink params (Enabled pg cnt cls) = do+ let param = ("p", T.pack . show $ pg)++ [whamlet|+ <li .#{cls}>+ <a href="#{updateGetParam params param}">#{cnt}+ |]++ where+ updateGetParam :: [(Text,Text)] -> (Text,Text) -> Text+ updateGetParam getParams (p, n) = (T.cons '?') . T.intercalate "&"+ . map (\(k,v) -> k `T.append` "=" `T.append` v)+ . (++ [(p, n)]) . filter ((/= p) . fst) $ getParams++showLink _ (Disabled cnt cls) =+ [whamlet|+ <li .#{cls} .disabled>+ <a>#{cnt}+ |]++-- | A widget showing pagination links. Follows bootstrap principles.+-- Utilizes a \"p\" GET param but leaves all other GET params intact.+paginationWidget :: Int -- ^ current page+ -> Int -- ^ items per page+ -> Int -- ^ total number of items+ -> GWidget s m ()+paginationWidget page per tot = do+ -- total / per + 1 for any remainder+ let pages = (\(n, r) -> n + (min r 1)) $ tot `divMod` per++ when (pages > 1) $ do+ curParams <- lift $ fmap reqGetParams getRequest++ [whamlet|+ <ul>+ $forall link <- buildLinks page pages+ ^{showLink curParams link}+ |]++ where+ -- | Build up each component of the overall list of links. We'll+ -- use empty lists to denote ommissions along the way then+ -- concatenate.+ buildLinks :: Int -> Int -> [PageLink]+ buildLinks pg pgs =+ let prev = [1 .. pg - 1]+ next = [pg + 1 .. pgs ]++ -- these always appear+ prevLink = [(if null prev then Disabled else Enabled (pg - 1)) "«" "prev"]+ nextLink = [(if null next then Disabled else Enabled (pg + 1)) "»" "next"]++ -- show first/last unless we're on it+ firstLink = [ Enabled 1 "1" "prev" | pg > 1 ]+ lastLink = [ Enabled pgs (show pgs) "next" | pg < pgs ]++ lim = 9++ -- we'll show ellipsis if there are enough links that some will+ -- be ommitted from the list+ prevEllipsis = [ Disabled "..." "prev" | length prev > lim + 1 ]+ nextEllipsis = [ Disabled "..." "next" | length next > lim + 1 ]++ -- the middle lists, strip the first/last pages and+ -- correctly take up to limit away from current+ prevLinks = reverse . take lim . reverse . drop 1 $ map (\p -> Enabled p (show p) "prev") prev+ nextLinks = take lim . reverse . drop 1 . reverse $ map (\p -> Enabled p (show p) "next") next++ -- finally, this page itself+ curLink = [Disabled (show pg) "active"]++ in concat [ prevLink+ , firstLink+ , prevEllipsis+ , prevLinks+ , curLink+ , nextLinks+ , nextEllipsis+ , lastLink+ , nextLink+ ]++-- | looks up the \"p\" GET param and converts it to an Int. returns a+-- default of 1 when conversion fails.+getCurrentPage :: GHandler s m Int+getCurrentPage = fmap (fromMaybe 1 . go) $ lookupGetParam "p"++ where+ go :: Maybe Text -> Maybe Int+ go mp = readIntegral . T.unpack =<< mp
yesod-paginator.cabal view
@@ -1,5 +1,5 @@ name: yesod-paginator-version: 0.2.1+version: 0.2.2.1 synopsis: A pagination approach for yesod description: Paginate a list showing a per-item widget and links to other pages homepage: http://github.com/pbrisbin/yesod-paginator@@ -13,6 +13,7 @@ library exposed-modules: Yesod.Paginator+ Yesod.Paginator.Widget build-depends: base >= 4 && < 5 , text >= 0.11 && < 0.12