package incr_dom_partial_render

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file partial_render_list_intf.ml

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
open! Core
open! Import
open Util

module Interval = struct
  type 'a t =
    | Empty
    | Range of 'a * 'a
  [@@deriving compare, sexp]
end

module Measurements = struct
  type t =
    { list_rect : float Js_misc.Rect.t
    ; view_rect : float Js_misc.Rect.t
    }
  [@@deriving compare, sexp]
end

module type Key = sig
  type t [@@deriving sexp, compare]

  include Comparable.S with type t := t
end

(** [Row_id.t] is used to uniquely identify rows, while [Sort_key.t] is used to sort them.

    If the order of your rows frequently changes, you can use [Row_id.t] to uniquely
    identify a row even as its [Sort_key.t] value changes.
    This is useful for preserving the measured heights in [Height_cache] as row values
    change, causing rows to get reordered.

    In simple cases, [Row_id] and [Sort_key] can be the same (use [Make_simple] for this).
*)

module type Row_id = Key

module type Sort_key = sig
  include Key

  type row_id

  val row_id : t -> row_id
end

(** [Partial_render_list] provides common functionality for partially rendering large (e.g
    10_000 rows) lists or tables. It allows apps to measure and cache the height of each
    row and incrementally compute which rows to show while scrolling. It puts spacers
    above and below the viewport so that the list element remains the same height with
    only as many rendered rows as necessary to fill the viewport. This approach allows
    high data change rates because it doesn't change the dom for rows that are not in
    view.

    Because of the height cache your rows don't all have to be the same height. It is fine
    if some rows have more data or the editing UI is taller than a display row.

    See lib/incr_dom/examples/ts_gui for a demonstration of how to use of this module.
*)
module type S = sig
  module Row_id : Key
  module Sort_key : Key

  (** Height_cache keeps track of the rendered height of items so that scrolling
      to a given position can decide which elements to render accurately. This allows
      rows to have different heights and change height at runtime.

      It only caches heights for rows that are currently in the list with a given key, so
      items will be dropped on changing a filter or sort, but this is only noticeable if
      the height guess is wrong and the user is paying very close attention to consistency
      of scroll positions.

      The [height_guess] parameter is the default height in pixels returned for any item
      that is not in the cache. Rows that are measured to be exactly [height_guess] tall
      will not even be added to the cache. If most of your rows are a certain size you
      should determine the exact height returned to [measure_heights] in the typical case
      and use that as your guess.
  *)
  module Height_cache : sig
    type t [@@deriving compare, sexp_of]

    val empty : height_guess:float -> t

    (** [height t key] will return the actual height of [key] if available, otherwise it
        returns [height_guess] *)
    val height : t -> Row_id.t -> float
  end

  (** Meant to be stored in the derived model *)
  type 'v t

  val create
    :  rows:'v Sort_key.Map.t Incr.t
    -> height_cache:Height_cache.t Incr.t
    -> measurements:Measurements.t option Incr.t
    -> 'v t Incr.t

  val measurements : _ t -> Measurements.t option
  val find_by_position : _ t -> position:float -> Sort_key.t option

  (** [find_by_relative_position t key ~offset] returns the key at a distance of
      approximately [offset] away from [key], preferring closer elements to farther ones.
      If the offset extends past the end of the list, the end key is returned instead. *)
  val find_by_relative_position : _ t -> Sort_key.t -> offset:float -> Sort_key.t option

  val focus_offset_to_position : _ t -> Sort_key.t -> offset:float -> float

  (** Meant for rendering, apps should normally use Incr.Map.mapi' on this *)
  val rows_to_render : 'v t -> 'v Sort_key.Map.t

  (** (top, bottom) spacer pixel heights to put the rendered rows in the right place *)
  val spacer_heights : _ t Incr.t -> (float * float) Incr.t

  (** Scroll the view to the row with the given key, scrolling the minimum amount possible
      while still being [(top|bottom)_margin] pixels away from the top and bottom of the
      screen. If this can't be satisfied, the margin will be reduced to the point where it
      can. If a margin of 0 can't show the whole element, it will prefer showing the top.

      [?in] determines the element which should be scrolled. Default is `Window *)
  val scroll_into_scroll_region
    :  ?in_:Scroll_region.t
    -> _ t
    -> top_margin:float
    -> bottom_margin:float
    -> key:Sort_key.t
    -> Scroll_result.t

  val scroll_to_position
    :  ?in_:Scroll_region.t
    -> _ t
    -> position:float
    -> key:Sort_key.t
    -> Scroll_result.t

  val scroll_to_position_and_into_region
    :  ?in_:Scroll_region.t
    -> _ t
    -> position:float
    -> top_margin:float
    -> bottom_margin:float
    -> key:Sort_key.t
    -> Scroll_result.t

  (** [is_in_region] and [get_position] return [None] if the given key does not exist or
      the view rect and list rect measurements are not yet available. *)

  val is_in_region
    :  _ t
    -> top_margin:float
    -> bottom_margin:float
    -> key:Sort_key.t
    -> bool option

  val get_position : _ t -> key:Sort_key.t -> float option
  val get_top_and_bottom : _ t -> key:Sort_key.t -> (float * float) option

  (** [measure_heights_simple] updates a height cache by measuring the rendered elements,
      relying on the app to provide a function for finding and measuring the element for a
      given key.
      This function can be used for table with collapsed borders and box sizing set to
      border-box.
  *)
  val measure_heights_simple
    :  _ t
    -> measure:(Sort_key.t -> float option)
    -> Height_cache.t

  (** [measure_heights] is like [measure_heights_simple], but allows the app to use the
      previous and next rows in addition to the current row to measure the current row's
      height (e.g. using the bottom position of the previous row and/or the top position
      of the next row).
      To avoid retaking the same measures three times for each row (once as the "previous"
      row, once as the "current" row, and once as the "next" row), [measure_row] allows
      the app to specify what measurements to take for a given row, and it is called
      exactly once per row.
      This function should be used for tables with non-collapsed borders.
  *)
  val measure_heights
    :  _ t
    -> measure_row:(Sort_key.t -> 'm option)
    -> get_row_height:(prev:'m option -> curr:'m option -> next:'m option -> float option)
    -> Height_cache.t
end

module type Partial_render_list = sig
  module type S = S
  module type Row_id = Row_id
  module type Sort_key = Sort_key

  module Interval = Interval
  module Measurements = Measurements

  module Make (Row_id : Row_id) (Sort_key : Sort_key with type row_id := Row_id.t) :
    S with module Row_id = Row_id and module Sort_key = Sort_key

  module Make_simple (Row_id : Row_id) :
    S with module Row_id = Row_id and module Sort_key = Row_id
end