Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a generic update function for a nested record in Elm

Tags:

elm

In Elm I have a model with nested attributes like:

model =
  { name = ""
  , disruptedFields =
    { advertising =
      { name = "Advertising"
      , checked = False
      }
    , travel =
      { name = "Travel"
      , checked = False
      }
    , utilities =
      { name = "Utilities"
      , checked = False
      }
    }
  }

disruptedFields contains a list of values for checkboxes. When I click on the checkbox I send an update message to UpdateDisruptedField, which currently looks like:

UpdateDisruptedField value ->
  let
    fieldCollection = model.disruptedFields
    field = fieldCollection.advertising
  in
    { model | disruptedFields =
      { fieldCollection | advertising =
        { field | checked = (not value) }
      }
    }

My update function is hard-coded to model.disruptedField.advertising in the field and advertising variables. This is working for me, but I'm stuck making the function generic.

How do I pass the record into UpdateDisruptedField so I can make it generic?

like image 407
bholtbholt Avatar asked Jul 23 '16 18:07

bholtbholt


Video Answer


1 Answers

This is a common problem for Elm apps with many input fields. There are two approaches for creating a generic update function to reduce code repetition.

  1. Extend the message for update with identifier for an input and then add another level of switch with case checkboxType of and handle the update with all the nested Records. Every time you will add a new field in the model, you will have to extend the update with an extra branch for handling the update.

  2. Re-structure your model with Dictionaries and handle the update in a proper generic way.

I prefer the second option, because updating nested Records is a chore.

Please consider the following example:

module Main exposing (..)

import Html exposing (div, input, text, label)
import Html.App exposing (beginnerProgram)
import Html.Events exposing (onCheck)
import Html.Attributes exposing (type', checked)
import Dict


(=>) : a -> b -> ( a, b )
(=>) a b =
    ( a, b )


main =
    beginnerProgram { model = model, view = view, update = update }


model =
    { name = ""
    , disruptedFields =
        Dict.fromList
            [ "advertising"
                => { name = "Advertising"
                   , checked = False
                   }
            , "travel"
                => { name = "Travel"
                   , checked = False
                   }
            , "utilities"
                => { name = "Utilities"
                   , checked = False
                   }
            ]
    }


type Msg
    = Check String Bool


view model =
    let
        checkbox ( key, data ) =
            label []
                [ text data.name
                , input
                    [ type' "checkbox"
                    , checked data.checked
                    , onCheck (Check key)
                    ]
                    []
                ]
    in
        div []
            (model.disruptedFields
                |> Dict.toList
                |> List.map checkbox
            )


update msg model =
    case msg of
        Check checkboxId checked ->
            let
                updateRecord =
                    Maybe.map (\checkboxData -> { checkboxData | checked = checked })

                disruptedFieldsUpdated =
                    Dict.update checkboxId
                        updateRecord
                        model.disruptedFields
            in
                { model | disruptedFields = disruptedFieldsUpdated }

Please note, that I've been using => operator to make Tuples look nicer.

disruptedFields is now a Dictionary, that uses String keys to identify every checkbox.

like image 75
halfzebra Avatar answered Sep 18 '22 22:09

halfzebra