fjvallarino/monomer

Proper Dynamic Widget Creation

Closed this issue · 6 comments

Hey again, I was looking over your example from Books as I have a similar issue to tackle (I'll describe in abstract)

A Function (createAlbum) loads a List of Image Paths [FilePath], it operates on the paths and returns a list of WidgetNodes
[WidgetNode AlbumModel AlbumEvt] which are given to a zstack in the buildUI function via an ALens' (named availableSelections)

The function is able to do everything successfully until the zstack is given the [WidgetNodes] in which monomer then hangs indefinetly. As I understand the Books example is making a web request rather than a filesystem request and maybe I have misunderstood some part of the process?

import Monomer
import Data.Text (Text, pack, unpack)
import System.Directory (getDirectoryContents)
import Control.Lens

data AlbumModel = AlbumModel {
  _activeImages        :: [Text],
  _availableSelections :: [WidgetNode AlbumModel AlbumEvt]
  } deriving (Show, Eq)
  
   
data AlbumEvt
    = CreateAlbum    :: [FilePath]
    |  SetAlbumPaths :: [WidgetNode AlbumModel AlbumEvt]
    deriving(Show, Eq)
    
MakeLenses'' AlbumModel
 
allImages :: FilePath -> IO [Text]
allImages imgPath = do
  all <- getDirectoryContents imgPath
  let filtered = Prelude.filter (isSuffixOf ".png") (Prelude.map pack all)
  return filtered

templateAlbum :: Text -> WidgetNode AlbumModel AlbumEvt
templateAlbum path = image path

createAlbum   :: IO [Text] -> [WidgetNode AlbumModel AlbumEvt]  
createAlbum = do
  imgs <- allImages "./assets/images/"
  let imr  = Prelude.foldr1 (++) (Prelude.map (templateAlbum . unpack) imgs)
  print  $ "Widgets " ++ show imr
  return $ (SetAlbumPaths $ imr) 
  
handleEvent wenv node model evt  = case evt of
  CreateAlbum    mdl        -> [ Task $ createAlbum mdl]
  SetAlbumPaths  nodes      -> [ Model $ model & availableSelections    .~ nodes]
  
buildUI wenv model evt = widgetTree where
  sectionBg = wenv ^. L.theme . L.sectionColor
  zstack (model ^. availableSelections),
  button "Load Images" (CreateAlbum model)

Any help is greatly appreciated, thanks for helping immensely in my Haskell Journey

Hi! Could you replace the single backticks ` with triple backticks? (```)? That will help see the code more easily. You can also indicate the language so it's formatted nicely (wrapping the complete source code you provided in a single pair of triple backticks is probably a good idea):

```haskell

your code here

```

You can also click on Preview before submitting it to make sure it is formatted correctly.

For sure! My bad I was writing this on my phone

EDIT: I cleaned up the code so it is more concise

Could you paste the complete source code? If you have a repository, pasting the link is also good. I think there are a few missing pieces, and I can't fully understand what could be causing your problem.

In general, I would avoid storing nodes in the model and only keep the information that represents them (album name, image path, etc.). Then, in buildUI, you can use the model to create the necessary widgets.

I see so instead of nodes being made and stored in model the FilePaths should be stored in the model.

From there I'll have to create some other mechanism (in buildUI like usint template there instead in conjunction with Prelude.map for example) to create image elements for the amount of FilePaths!

Thank you this provides me a lot of insight; I can fix it from here I believe 😁

I think what you want to do is similar to what the Todo example does. Not so much the editing part, but the list that is displayed. For example, you can:

  • Have an Album type and a list of albums in your AppModel
  • Load this list of albums from the filesystem during the Init event; I usually start with hardcoded data to test the UI looks how I expect)
  • Then, use that information to create your UI.
data Album = Album {
  _name :: Text,
  _year :: Int,
  _imgPath :: Text
} deriving (Eq, Show)

data AppModel = AppModel {
  _albums :: [Album],
 ...
}

buildUI wenv model = widgetTree where
  albumRow album = hstack [
    vstack [
      label (album ^. name),
      spacer,
      labelS (album ^. year)
    ],
    filler,
    image (album ^. imgPath)
      `styleBasic` [width 100, height 100]
  ]

  albumsRows = albumRow <$> (model ^. albums)
  widgetTree = vstack [
      label "Albums",
      spacer,
      vscroll albumsRows
  ]

Whenever you modify the model (for example, you add an album to albums after clicking on a button), buildUI will be called again and will include the new row.

handleEvent wenv node model evt = case evt of
  ButtonClicked -> [
    Model $ model
      & albums .~ (Album "New Album" 2015 "album-cover.jpg" : model ^. albums)
  ]

I think this issue can be closed. Please re-open or create a new one if needed. Thanks!