Halogen 5 pager library

Hey! I created a tiny library to help with paging in Halogen 5 (that is currently the master branch): https://github.com/MonoidMusician/purescript-halogen-pager

It basically lets you supply a record of components (and their output adapters) and a variant that contains the data for the particular page (the slot and input to the component) and creates the H.slot invocation for you.

If this is interesting to you, feel free to let me know, and let me know if you need more functionality :slight_smile:

Example usage to come ā€¦

3 Likes

This is interesting!

If Iā€™m understanding properly, you have a record of tuples, where each tuple holds a component + a function to convert that componentā€™s Message type into the relevant handler (parentā€™s Query) type. That makes it possible for the parent to mount the component in the slot and attach it to the right handler. Ultimately this produces a render function compatible with the parent component (the result of mounting the slot), so ultimately you could have the parent do something like:

render st = 
  HH.div_
  [ HH.text "Some content"
  , HH.text "some other content"
  , renderPage st.pager.components st.pager.variant
  , HH.text "maybe a footer?"
  ]

Iā€™m assuming youā€™d also have some HTML controlling say a list of buttons where each button will trigger a variant update to a given page (1, 2, 3 <- clicking ā€˜1ā€™ will change the pager variant to be the first page).


Some other thoughts: it appears that this record of components doesnā€™t care what those componentsā€™ query algebras are, just whether their output can be attached to a proper handler. So you could have a pager with wildly different components inside. Thatā€™s pretty powerful! Itā€™d be nice to see an example in action.

Might be interesting to implement a Formless add-on for making form wizards and the like with this class involved; the component could help you page through and unify information flowing through the various sub-forms on different pages.


We have a few places in our application where weā€™re handling the paging problem by defining a new data type for locations (data ThisLocation = Page 1 | Page 2) and then casing on it in our render code (case location of Page 1 -> HH.slot ...). It works fine for say 2 pages at a time, but I could see a larger number benefitting from your solution.

Do you have other use cases in mind?

Iā€™m starting work soon on some dynamic layouts in our application in which a user has designated areas of the UI they can customize to different types ā€“ for example, they might be able to display a dropdown, or a typeahead, or a date picker, or whatever. That region determines which component ought to display (if any), and is responsible for managing the outputs of any child components and handling them properly. This seems like an ideal use case for this component!

Take an analytics application that allows you to create different ā€œtilesā€ where each tile visualizes data in chart or table format and various chart types are available. Each one might be a different component, but the tile is responsible for managing which chart or table displays along with any user interactions (like clicking to change the chart type). In this case, youā€™d create a record of possible components and an associated variant; each tile would be a copy of your component given the same possible components + variant.

Hey Thomas, thanks for the feedback!

Yeah, thatā€™s right. The various row types have to line up together like this:

  • components: Tuple (H.Component HH.HTML f i o m) (o -> Maybe q)
  • slots: H.Slot f o p
  • page: Tuple p i

where

  • q is the parent query type (well, the output from the HTML) ā€“ must be consistent throughout the row
  • m is the parent monad ā€“ also consistent
  • f is this slotā€™s query functor
  • o is this slotā€™s output, which gets adapted into q
  • p is this slotā€™s slot/index type
  • i is the input to the component

Yeah, actually what I would do is partially apply the function for efficiency, but you have the idea:

pager :: Variant Pages -> H.ComponentHTML Query Slots Aff
pager = renderPage
  { intro: staticPage $
      pageLink functions
        [ HH.text "About functions." ]
  , functions: staticPage $
      pageLink intro
        [ HH.text "Back to beginning." ]
  }

Regarding your questions over PM, I would say that the key thing that instantiates and establishes identity for the components, is the p type, the slot index. So if you drag tiles around, just keep the p value the same, and the component should swap around just fine.


I wonder if I can make it handle (arbitrarily) nested pages ā€¦ :thinking: I think the only issue would be focusing on child slots, so that probably requires something like the child path machinery. I guess it requires a second class. </musing>