@@ -6,12 +6,15 @@ import Reflex
6import Reflex.Dom.Class
6import Reflex.Dom.Class
7import Reflex.Dom.Widget.Basic
7import Reflex.Dom.Widget.Basic
9import Control.Monad.IO.Class
10import Control.Monad.IO.Class
10import Data.Fixed
11import Data.Fixed
11import Data.Monoid
12import Data.Monoid
12import qualified Data.Map as Map
13import qualified Data.Map as Map
13import Data.Map (Map)
14import Data.Map (Map)
14import GHCJS.DOM.Element
15import GHCJS.DOM.Element
16-- |A list view for long lists. Creates a scrollable element and only renders child row elements near the current scroll position.
19-- |A list view for long lists. Creates a scrollable element and only renders child row elements near the current scroll position.
17virtualListWithSelection :: forall t m k v. (MonadWidget t m, Ord k)
20virtualListWithSelection :: forall t m k v. (MonadWidget t m, Ord k)
@@ -67,4 +70,70 @@ virtualListWithSelection heightPx rowPx maxIndex i0 setI listTag listAttrs rowTa
67 preItems = min startingIndex numItems
70 preItems = min startingIndex numItems
68 in (topPx - preItems * sizeIncrement, (startingIndex - preItems, preItems + numItems * 2))
71 in (topPx - preItems * sizeIncrement, (startingIndex - preItems, preItems + numItems * 2))
73+virtualList :: forall t m k v a. (MonadWidget t m, Ord k)
74+ => Dynamic t Int -- ^ A 'Dynamic' of the visible region's height in pixels
75+ -> Int -- ^ The fixed height of each row in pixels
76+ -> Dynamic t Int -- ^ A 'Dynamic' of the total number of items
77+ -> Int -- ^ The index of the row to scroll to on initialization
78+ -> Event t Int -- ^ An 'Event' containing a row index. Used to scroll to the given index.
79+ -> (k -> Int) -- ^ Key to Index function, used to position items.
80+ -> Map k v -- ^ The initial 'Map' of items
81+ -> Event t (Map k (Maybe v)) -- ^ The update 'Event'. Nothing values are removed from the list and Just values are added or updated.
82+ -> (k -> v -> Event t v -> m a) -- ^ The row child element builder.
83+ -> m (Dynamic t (Int, Int), Dynamic t (Map k a)) -- ^ A tuple containing: a 'Dynamic' of the index (based on the current scroll position) and number of items currently being rendered, and the 'Dynamic' list result
84+virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder = do
85+ virtualH <- mapDyn (mkVirtualHeight . (*) rowPx) maxIndex
86+ containerStyle <- mapDyn mkContainer heightPx
87+ viewportStyle <- mapDyn mkViewport heightPx
89+ rec (viewport, result) <- elDynAttr "div" containerStyle $ elDynAttr' "div" viewportStyle $ elDynAttr "div" virtualH $
90+ listWithKeyShallowDiff items0 itemsUpdate $ \k v e -> elAttr "div" (mkRow k) $ itemBuilder k v e
91+ scrollPosition <- holdDyn 0 $ leftmost [ domEvent Scroll viewport
92+ , fmap (const (i0 * rowPx)) pb
94+ window <- combineDyn (\h -> findWindow h rowPx) heightPx scrollPosition
95+ performEvent_ $ fmap (\i -> liftIO $ elementSetScrollTop (_el_element viewport) (i * rowPx)) $ leftmost [setI, fmap (const i0) pb]
96+ return (nubDyn window, result)
98+ toStyleAttr m = "style" =: (Map.foldWithKey (\k v s -> k <> ":" <> v <> ";" <> s) "" m)
99+ mkViewport h = toStyleAttr $ "overflow" =: "auto" <> "position" =: "absolute" <>
100+ "left" =: "0" <> "right" =: "0" <> "height" =: (show h <> "px")
101+ mkContainer h = toStyleAttr $ "position" =: "relative" <> "height" =: (show h <> "px")
102+ mkVirtualHeight h = let h' = h * rowPx
103+ in toStyleAttr $ "height" =: (show h <> "px") <>
104+ "overflow" =: "hidden" <>
105+ "position" =: "relative"
106+ mkRow k = toStyleAttr $ "height" =: (show rowPx <> "px") <>
107+ "top" =: ((<>"px") $ show $ keyToIndex k * rowPx) <>
108+ "position" =: "absolute" <>
110+ findWindow windowSize sizeIncrement startingPosition =
111+ let (startingIndex, topOffsetPx) = startingPosition `divMod'` sizeIncrement
112+ numItems = (windowSize + sizeIncrement - 1) `div` sizeIncrement
113+ in (startingIndex, numItems)
116+ :: (Ord k, MonadWidget t m)
125+ -> Event t (Map k (Maybe v))
126+ -> (k -> v -> Event t v -> m a)
127+ -> m (Event t (Int, Int), Dynamic t (Map k a))
128+virtualListBuffered buffer heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder = do
129+ (win, m) <- virtualList heightPx rowPx maxIndex i0 setI keyToIndex items0 itemsUpdate itemBuilder
131+ let extendWin o l = (max 0 (o - l * (buffer-1) `div` 2), l * buffer)
132+ rec let winHitEdge = fmapMaybe id $ attachWith (\(oldOffset, oldLimit) (winOffset, winLimit) ->
133+ if winOffset > oldOffset && winOffset + winLimit < oldOffset + oldLimit
135+ else Just (extendWin winOffset winLimit)) (current winBuffered) (updated win)
136+ winBuffered <- holdDyn (0, 0) $ leftmost [ winHitEdge
137+ , fmap (uncurry extendWin) $ tagDyn win pb
139+ return (updated winBuffered, m)