module Reflex.Dom.Widget.Lazy where
import Reflex
import Reflex.Dom.Class
import Reflex.Dom.Widget.Basic
import Control.Monad
import Control.Monad.IO.Class
import Data.Fixed
import Data.Monoid
import qualified Data.Map as Map
import Data.Map (Map)
import GHCJS.DOM.Element
import Data.Maybe
import Data.Int
virtualListWithSelection :: forall t m k v. (MonadWidget t m, Ord k)
=> Dynamic t Int
-> Int
-> Dynamic t Int
-> Int
-> Event t Int
-> String
-> Dynamic t (Map String String)
-> String
-> Dynamic t (Map String String)
-> (k -> Dynamic t (Maybe v) -> Dynamic t Bool -> m ())
-> Dynamic t (Map k v)
-> (Int -> k)
-> m (Dynamic t (Int, Int), Event t k)
virtualListWithSelection heightPx rowPx maxIndex i0 setI listTag listAttrs rowTag rowAttrs itemBuilder items indexToKey = do
totalHeightStyle <- mapDyn (toHeightStyle . (*) rowPx) maxIndex
containerStyle <- mapDyn toContainer heightPx
viewportStyle <- mapDyn toViewport heightPx
rec (container, sel) <- elDynAttr "div" containerStyle $ elDynAttr' "div" viewportStyle $ do
currentTop <- mapDyn (listWrapperStyle . fst) window
(_, lis) <- elDynAttr "div" totalHeightStyle $ tagWrapper listTag listAttrs currentTop $ selectViewListWithKey_ selected itemsInWindow $ \k v s -> do
(li,_) <- tagWrapper rowTag rowAttrs (constDyn $ toHeightStyle rowPx) $ itemBuilder k v s
return $ fmap (const k) (domEvent Click li)
return lis
selected <- holdDyn (indexToKey i0) sel
pb <- getPostBuild
scrollPosition <- holdDyn 0 $ leftmost [ domEvent Scroll container
, fmap (const (i0 * rowPx)) pb
]
window <- combineDyn (\h -> findWindow h rowPx) heightPx scrollPosition
itemsInWindow <- combineDyn (\(_,(idx,num)) is -> Map.fromList $ map (\i -> let ix = indexToKey i in (ix, Map.lookup ix is)) [idx .. idx + num]) window items
postBuild <- getPostBuild
performEvent_ $ fmap (\i -> liftIO $ setScrollTop (_el_element container) (i * rowPx)) $ leftmost [setI, fmap (const i0) postBuild]
indexAndLength <- mapDyn snd window
return (indexAndLength, sel)
where
toStyleAttr m = "style" =: (Map.foldWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m)
toViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
"left" =: "0" <> "right" =: "0" <> "height" =: (show h <> "px")
toContainer h = toStyleAttr $ "position" =: "relative" <> "height" =: (show h <> "px")
listWrapperStyle t = toStyleAttr $ "position" =: "relative" <>
"top" =: (show t <> "px")
toHeightStyle h = toStyleAttr ("height" =: (show h <> "px") <> "overflow" =: "hidden")
tagWrapper elTag attrs attrsOverride c = do
attrs' <- combineDyn Map.union attrsOverride attrs
elDynAttr' elTag attrs' c
findWindow windowSize sizeIncrement startingPosition =
let (startingIndex, topOffsetPx) = startingPosition `divMod'` sizeIncrement
topPx = startingPosition topOffsetPx
numItems = windowSize `div` sizeIncrement + 1
preItems = min startingIndex numItems
in (topPx preItems * sizeIncrement, (startingIndex preItems, preItems + numItems * 2))
virtualList :: forall t m k v a. (MonadWidget t m, Ord k)
=> Dynamic t Int
-> Int
-> Dynamic t Int
-> Int
-> Event t Int
-> (k -> Int)
-> Map k v
-> Event t (Map k (Maybe v))
-> (k -> v -> Event t v -> m a)
-> m (Dynamic t (Int, Int), Dynamic t (Map k a))
virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder = do
virtualH <- mapDyn (mkVirtualHeight . (*) rowPx) maxIndex
containerStyle <- mapDyn mkContainer heightPx
viewportStyle <- mapDyn mkViewport heightPx
pb <- getPostBuild
rec (viewport, result) <- elDynAttr "div" containerStyle $ elDynAttr' "div" viewportStyle $ elDynAttr "div" virtualH $
listWithKeyShallowDiff items0 itemsUpdate $ \k v e -> elAttr "div" (mkRow k) $ itemBuilder k v e
scrollPosition <- holdDyn 0 $ leftmost [ domEvent Scroll viewport
, fmap (const (i0 * rowPx)) pb
]
window <- combineDyn (\h -> findWindow h rowPx) heightPx scrollPosition
performEvent_ $ fmap (\i -> liftIO $ setScrollTop (_el_element viewport) (i * rowPx)) $ leftmost [setI, fmap (const i0) pb]
return (nubDyn window, result)
where
toStyleAttr m = "style" =: (Map.foldWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m)
mkViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
"left" =: "0" <> "right" =: "0" <> "height" =: (show h <> "px")
mkContainer h = toStyleAttr $ "position" =: "relative" <> "height" =: (show h <> "px")
mkVirtualHeight h = let h' = h * rowPx
in toStyleAttr $ "height" =: (show h <> "px") <>
"overflow" =: "hidden" <>
"position" =: "relative"
mkRow k = toStyleAttr $ "height" =: (show rowPx <> "px") <>
"top" =: ((<>"px") $ show $ keyToIndex k * rowPx) <>
"position" =: "absolute" <>
"width" =: "100%"
findWindow windowSize sizeIncrement startingPosition =
let (startingIndex, topOffsetPx) = startingPosition `divMod'` sizeIncrement
numItems = (windowSize + sizeIncrement 1) `div` sizeIncrement
in (startingIndex, numItems)
virtualListBuffered
:: (Ord k, MonadWidget t m)
=> Int
-> Dynamic t Int
-> Int
-> Dynamic t Int
-> Int
-> Event t Int
-> (k -> Int)
-> Map k v
-> Event t (Map k (Maybe v))
-> (k -> v -> Event t v -> m a)
-> m (Event t (Int, Int), Dynamic t (Map k a))
virtualListBuffered buffer heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder = do
(win, m) <- virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder
pb <- getPostBuild
let extendWin o l = (max 0 (o l * (buffer1) `div` 2), l * buffer)
rec let winHitEdge = fmapMaybe id $ attachWith (\(oldOffset, oldLimit) (winOffset, winLimit) ->
if winOffset > oldOffset && winOffset + winLimit < oldOffset + oldLimit
then Nothing
else Just (extendWin winOffset winLimit)) (current winBuffered) (updated win)
winBuffered <- holdDyn (0, 0) $ leftmost [ winHitEdge
, fmap (uncurry extendWin) $ tagDyn win pb
]
return (updated winBuffered, m)