How to implement wrapping components in elm

643 views
Skip to first unread message

Daniel Kwiecinski

unread,
May 17, 2016, 8:13:56 AM5/17/16
to Elm Discuss
Hi Elmers,


Here is my scenario. Say I have Main.elm which defines main view form my application. I also have bunch of other components (with their corresponding model  and message types) say Counter and Gif. 
Now I'd like to create new generic component which as a parameter (initial value of its model?) takes list of any type of component (say two counters, then one gif and another three counters) and wraps them into some decorating html.
The scenario serves as a illustration of the question, how do I implement components which can wrap lists of arbitrary component types.

--
Regards,
Daniel

Wil C

unread,
May 17, 2016, 8:33:27 AM5/17/16
to Elm Discuss
Daniel,

I think normally, you don't. I think the constraint here is that you need to explicitly set the types of each of the sub-components for every component that you make for a page. In the example that you give, you'd actually need to create 4 types of components: TopLevel, Counter, CounterList, and Gif. 

TopLevel component would include CounterList and Gif. And then CounterList would contain Counters. It is CounterList's job to dynamically keep track of the number of Counters. That way, you don't need a generic component to contain an unknown number of things with unknown types. And then if those components need to talk to each other (Like once you add 5 or more counters, you see a funny cat gif), I believe you can send messages through Cmds (in 0.17) or Effects (in <0.17). 

With the hierarchical thinking of laying out components, I found that Thinking in React helps. 

If you find that you really need the flexibility of having different components in a container, it's doable. But it comes at a cost. Generally, if you're making a web app of some sort, it's not needed. I cover entity component systems recently in another thread, and it's for games.

Daniel Kwiecinski

unread,
May 17, 2016, 8:47:17 AM5/17/16
to Elm Discuss
So let me expand my scenario a little bit. Lets assume that the CounterList component is very feature heavy. It makes lots of work to layout its children, manages drag to sort or whatever fancy stuff you can imagine. Now in my app I have many instances of usage of CounterList and I want to apply the complex behaviour not only to counters but also to gif and to mixed counters with gifs and many many other possible configurations (think in hundreds). I don't really want to implement dedicated CounterList, GifList, 2GifsWith3CountersList and other few hundreds SomethingBlaBlaList.
Is it possible in elm at all? If yes how so?

P.S. It is not imaginary question. I try to port existing application implemented in Re-Frame (ClojureScript framework) in which this scenario is trivial.

Peter Damoc

unread,
May 17, 2016, 10:09:36 AM5/17/16
to Elm Discuss
Hi Daniel, 

If you have a limited number of components you can unify them into one kind of a component. 

Here is a self contained example that unifies Counter and RandomGif and then uses them in a single list. 
https://gist.github.com/pdamoc/aef6306a9001de109aeece37e5627d06




--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

Daniel Kwiecinski

unread,
May 17, 2016, 10:36:19 AM5/17/16
to Elm Discuss
The problem is that the generic container component (Let's call it C) do not know about it potential children (let's call them X, Y, Z) . There is top level component (Let's call it T) which has a knowledge about all of them (it is the app). The C is in self contained package, you can consider it to implement material design list view. How Can I implement C so T can use T with X, Y, Z ?

Daniel Kwiecinski

unread,
May 17, 2016, 11:40:30 AM5/17/16
to Elm Discuss
I got impression that https://github.com/debois/elm-mdl/blob/3.1.0/src/Material/Component.elm implements it in the pre 0.17 way. What would be the 0.17 equivalent if my assumption is correct?

Yosuke Torii

unread,
May 17, 2016, 12:00:58 PM5/17/16
to Elm Discuss
Hi Daniel, 

I don't know well about material design, but just tried to implement what you mean.


Does your app have any more requirements than this?


2016年5月17日火曜日 23時36分19秒 UTC+9 Daniel Kwiecinski:

Daniel Kwiecinski

unread,
May 17, 2016, 1:01:18 PM5/17/16
to Elm Discuss
Hi Yosuke,

  Thank you so much for your example. Ideally If we only could have the child components their models / init / updates  and could somehow define interest of some messages in parent, that would be awesome.

Peter Damoc

unread,
May 18, 2016, 3:42:45 AM5/18/16
to Elm Discuss
Can you mock some code that would show how would you like to use this? 
Imagine that it is already implemented in some library and write against that imaginary library. 



Daniel Kwiecinski

unread,
May 18, 2016, 6:15:24 AM5/18/16
to Elm Discuss
Here is a sketch of how it would look like in reagent (ClojureScript)


; -- SOME CONCRETE COMPONENTS

; a component taking a String as a model
(defn hello-component [name]
[
:p "Hello, " name "!"])

; a stateless component using another component
(defn say-hello []
[hello-component
"world"])

; a component taking a ratom (it's a signal in elm speak) as a model
(defn reactive-hello-component [name]
[
:p "Hello, " @name "!"])

; a component taking list of Strings as a model
(defn list-hellos [names]
(
for [n names]
[hello-component (
str "hello " n)]))

; -- GENERIC WRAPPING COMPONENT


; a wrapping components. take list of components as a parameter and wraps them in pages
(defn wrap-components [components]
(
fn []
[
:div {:class "components-wrapped-in-pages-so-we-can-swipe-them"}
(
for [c components]
[
:div {:class "page"} c])]))


; -- MAIN VIEW GLUING ALL TOGETHER


(defn main-view []
(
let [reactive-name (ratom "initial-name")
input-state (
ratom "")]
[
:div {:class "some-boilerplate"}

; the two lines below are not following re-frame pattern. There are there just to express I have the state which changes.
[:input {:onchange (fn [value] (!reset input-state value))}] ; react to inout changes and pass the value to model (in re-frame instead of directly updating the model we would send a signal (as in elm) and have subscription react to the signal but for simplicity I ommit the patern)
[:button {:onclick #(!reset reactive-name @input-state)}] ; copy the states on button click

[:span {:class "here-come-generic-swipe-able-pages-wrapping-any-components"}

; here is the usage of the wrapping container
(wrap-components [
say-hello
; stateless component
#(hello-component "some-fancy-name") ; #(...) is lambda in clojure, here we close over some static state
#(reactive-hello-component reactive-name) ; #(...) here we close over some reactive state, so the component re-renders when the model (state) changes
#(list-hellos ["a" "b" "c"]) ; component taking list as a state (model)
])]]))

; -- MOUNT VIEW TO DOM

; bind the main-view to DOM and start observing deltas to render if needed
(defn ^:export run []
(
r/render [main-view] (js/document.body)))

Yosuke Torii

unread,
May 18, 2016, 8:05:20 AM5/18/16
to elm-d...@googlegroups.com
Oh, it looks just nesting views (I'm not familiar with ClojureScript though). If so, the solution is much simpler. Like this:

```
container : List (Html msg) -> Html msg
container children =
  div
    [  style [ ("padding", "20px") ] ]
    children
```


Isn't it working for you? I often use this pattern for popup.


You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/sJl8iB_3EXQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Peter Damoc

unread,
May 18, 2016, 8:08:21 AM5/18/16
to Elm Discuss
Oh, that's much easier:

import Html exposing (..) 
import Html.Attributes exposing (class) 

helloComponent name = 
  p [] [text ("Hello, " ++ name ++ "!")]
  
sayHello = 
  helloComponent "world" 


listHello names = 
  div [] (List.map helloComponent names) 


-- GENERIC WRAPPING COMPONENT

wrapComponents components = 
  div [class "components-wrapped-in-pages-so-we-can-swipe-them"]
  (List.map (\c -> div [class "page"] [c]) components)


names = ["Jim", "Bill", "Joe"]


main = 
  wrapComponents 
    [ sayHello
    , helloComponent "Sandra"
    , listHello names
    ]

There is no Signal anymore in Elm and if you use The Elm Architecture, all you get is regular values. 



Daniel Kwiecinski

unread,
May 18, 2016, 8:17:00 AM5/18/16
to Elm Discuss
This is fine. Big thanks for your effort.
But, how about instead of components being simple functions we have components as {init, update, view, subscription} so they encapsulate their logic. 
Think in having a component similar to google places autocomplete. From it's parent we still want to pass a configuration to it and react to the commands coming from the autocomplete (such as place changed) but we do not want or need to interfere with the autocomplete component internal state, rest calls etc?

Peter Damoc

unread,
May 18, 2016, 8:47:16 AM5/18/16
to Elm Discuss
You just use regular Elm Architecture and compose the model of the autocomplete into the proper place, same with update and view. 

To speak in React terms, what you had above are components that have only props. These can be implemented with simple functions in Elm. 

If a component needs state and rest calls, it needs to follow the Elm Architecture. Please note that the component can be fully encapsulated in a module. The kind of boilerplate needed for state management is very small and very predictable. You can even extract it into some kind of Widget abstraction and have all the Widgets be updated by a single line of code. :) 





Daniel Kwiecinski

unread,
May 18, 2016, 9:59:54 AM5/18/16
to Elm Discuss
Sounds very promising. Could you please provide minimalist example?   

Peter Damoc

unread,
May 18, 2016, 10:20:38 AM5/18/16
to Elm Discuss
This gist (previously posted)
https://gist.github.com/pdamoc/aef6306a9001de109aeece37e5627d06
is a kind of minimalist example. It shows how to join together 2 different widgets (RandomGif and Counter).
The process for extending the list with new widgets is mechanical, just add options to all the relevant types. 




Daniel Kwiecinski

unread,
May 18, 2016, 12:38:45 PM5/18/16
to Elm Discuss
Hi Peter,

   Just skimmed on the gist, but can already tell this is great help. Many thanks for your work.

Cheers,
Dan

debois

unread,
May 19, 2016, 2:32:23 AM5/19/16
to Elm Discuss
As I understand it, you want to write a function which takes a list of TEA components, then wires up and renders those. As Peter pointed out, this can be done when your "components" are really just view functions. If they are actual TEA components, each with (view, update, Model, Message, Subscription), there's a problem in that you'll need, someplace, a type that can host "Model" for any such component. I don't think Elm has such a type. 

I wrote a library for treating TEA-components uniformly (https://github.com/debois/elm-parts); elm-mdl uses that. It gets most of the way, but in the end, to use it, you have to make a record that contains every type of Model you are treating. Elm-mdl does it like so: https://github.com/debois/elm-mdl/blob/master/src/Material.elm#L178-L190.

I wrote a blog post about how this is achieved: https://medium.com/@debois/elm-components-3d9c00c6c612#.x5mtskkmg. (It's phrased in 0.16 terms, though.)

Daniel Kwiecinski

unread,
May 19, 2016, 5:27:46 AM5/19/16
to Elm Discuss
Any plans to update elm-mdl and elm-parts to 0.17?

debois

unread,
May 19, 2016, 5:33:51 AM5/19/16
to Elm Discuss

Yosuke Torii

unread,
May 19, 2016, 5:41:40 AM5/19/16
to elm-d...@googlegroups.com
I just wrote a new version of example here. This container view can also handle the messages of its children that have different kind of messages defined outside. I think this is more practical than previous one. (I haven't tried it in my app yet, though)


You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/sJl8iB_3EXQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Søren Debois

unread,
May 19, 2016, 6:00:42 AM5/19/16
to elm-d...@googlegroups.com
Neat. We use the same idiom (view function takes a function mapping its message type, in your case, “container") in elm-mdl. 

David Legard

unread,
May 19, 2016, 10:39:07 PM5/19/16
to Elm Discuss
What would be the minimal list of Json dependencies required for a project to work?

Writing:
"dependencies": {
       
"elm-lang/core": "4.0.0 <= v < 5.0.0",
       
"elm-lang/html": "1.0.0 <= v < 2.0.0",
       
"elm-lang/window": "1.0.0 <= v < 2.0.0",
       
"debois/elm-dom": "1.2.0 <= v < 2.0.0",
       
"debois/elm-parts": "2.0.0 <= v < 3.0.0"
   
},


leads to a compiler error

I cannot find module 'Material'

Module 'Main' is trying to import it

Lambder

unread,
Jun 16, 2016, 12:01:44 PM6/16/16
to Elm Discuss
I try to get my head around elm-parts. 
My goal is to have 3 levels of nested components:   A -> B -> Counter

I have added the following function to Counter: 

render1 : Parts.Get Model c -> Parts.Set Model c -> (Parts.Msg c -> m) -> c -> Html m
render1 g s = 
  Parts.create1 view update g s 

and defined a B component to have :

type alias Model =
  { counterA : Counter.Model,
    counterB : Counter.Model 
  }

type Msg
  = Reset
  | CounterMsg (Parts.Msg Model)

view : Model -> Html Msg
view model =
  div
    []
    [ Counter.render1 .counterA (\m c -> {c | counterA = m}) CounterMsg model
    , Counter.render1 .counterB (\m c -> {c | counterB = m}) CounterMsg model
    --, Counter.render CounterMsg [1] model
    , button [ onClick Reset ] [ text "RESET" ]
    ]


Question: How do I define an A component to again have pair of B components and its model to be:

type alias Model =
  { bA : B.Model,
    bB : B.Model 
  }


Such example would actually be very useful to compose component based apps (and libs)

Many thanks,
Daniel

Lambder

unread,
Jun 16, 2016, 12:25:31 PM6/16/16
to Elm Discuss
I have created the example myself but not sure if it is idiomatic. Please comment.
Reply all
Reply to author
Forward
0 new messages