Experimental reuse of code in Elm, Part I — Page with list of products

This is the first port of a series:

Part II

Part III

I tried to reuse a small app and insert it in a larger app. The small app create a box with a name, a picture and a price that can be displayed or hidden clicking on the product box.

This is the small app that I used as starting point:

Before proceeding I added some style and some animation. This is the enhanced version:

Now I want to have the products to be in a list and I want to have the interface to display them all, each of them with its own independent interaction

So, first of all I rename the Model to Product

Before:

type alias Model =
{ name : String
, price : Float
, priceInView : Bool
, picture : String
}

After:

type alias Product =
{ name : String
, price : Float
, priceInView : Bool
, picture : String
}

Then I create the new Model that is a list of products:

type alias Model =
{ productList : List Product
}

And I initialise it with a list of products:

init : Model
init =
Model
[ Product “Photocamera” 24.31 False “https://...jpg"
, Product “TV” 24.22 False “https://...jpg"
...
]

I need to identify each singular product now. I will do it using the position inside the List. Similar to what I have done before for the model, I rename the old view of the product to productView and I add a position parameter that is the position inside the List:

viewProduct : Product -> Int -> Html Msg
viewProduct product position =
div
[ if product.priceInView then
onClick (HidePrice position)
else
onClick (ShowPrice position)
...

Now the two messages sent when clicking have the position as parameter.
I updated the Msg list as well with the Integer type:

type Msg
= ShowPrice Int
| HidePrice Int

So now the updated function will know which product has received the click.

I need now to create a view function that loop all the product and call the productView for each of them. To do so, I use List.indexedMap:

This is the command

(List.indexedMap (\position product -> viewProduct product position) model.productList)

For each product in model.productList, this is calling viewProduct:

viewProduct product0 0
viewProduct product1 1
viewProduct product2 2
viewProduct product3 3
.. etc.

Now we need to change the update function that is the most difficult part, probably.

First I need two helpers that extract and add back a product from the List based on its position.

The “getter” first convert the List into an Array with Array.fromList, then it gets the product with Array.get

This return a Maybe Product. I use a condition to convert a Maybe product into a Product

This is the getter:

getProduct productList position =
let
product =
Array.get position (Array.fromList productList)
in
case product of
Nothing ->
Product ("PRODUCT NOT FOUND Position " ++ toString position ++ " not found") 0 False ""
            Just val ->
val

The “setter” use Array.set:

setProduct productList position product =
let
productArray = Array.set position product (Array.fromList productList)
in
Array.toList productArray

We are almost there.

Now using the setter and the getter I create a function that update the field princeInView using the parameter “value”. This function accept the list of products and return an updated new list of products.

updateProduct productList position value =
let
oldProduct = getProduct productList position
newProduct = { oldProduct | priceInView = value }
in
setProduct productList position newProduct

This function is needed in the new “update” function to update the model:

update msg model =
case msg of
ShowPrice position ->
{ model | productList = updateProduct model.productList position True }
    HidePrice position ->
{ model | productList = updateProduct model.productList position False }

This is all. You can find the final script here:

Is there a simpler way to implement this? Let me know in the comments below

Edited on April 30th, 2017

I tried to decoupled the product from the main file and move it in a separate module but I am having difficulties in using the Product.view from the main module because it returns this error:

Function `beginnerProgram` is expecting the argument to be:
{ …, view : Model -> Html Msg }
But it is:
{ …, view : { productList : ProductList } -> Html Product.Msg }

The problem seems to be the the system is expecting a Msg but instead is getting a Product.Msg because the view is coming from the module product.

Maybe using Html.map could help to fix the problem, but I could not figure it out yet.

Edited 6 May 2017

See the Part II and III to see a solution to the problem above.

Also have a look at this code here, there is a different approach to display products on the page:

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.