Announcing elm-components

Some time ago I came up with an idea of how to implement components in Elm. The experiment was successful, and I’ve decided to build and publish a package that implements that idea.

I understand the official position about components and I’ve tried to make it clear in the README when, in my opinion, you should use this library, and that you shouldn’t use it at all if you’re a beginner.

I also don’t think that a solution like this should always be used instead of the default approach. It’s an abstraction that may or may not be better to use depending on the characteristics of your project.

Finally, as any software, it’s not ideal, but I’m sure there is still a lot of room for improvements.

So, here it is: elm-components!

11 Likes

Hi,

Thank you for this @edkv.

Link click counts don’t lie, people are interested in this subject, and for a reasonable reason: they still have issues to scale their app. (edit: don’t try to guess people motivation).

Your README is insightful. When I saw your post, I was examinating the http://elm-bootstrap.info code that it also a good source of inspiration (after https://github.com/rtfeldman/elm-spa-example and https://github.com/evancz/elm-sortable-table).

All in all, I’m pretty convinced that these experimentations are beneficial to the Elm ecosystem.

Hopefully future elm versions will have a standard documented way to compose complex UIs, as I love not having to choose between several solutions, which is almost what Elm fulfils.

2 Likes

I clicked the link to validate my skepticism. I mean no disrespect, but I think it should be clear that clicking a link doesn’t imply supporting an idea :slight_smile:

9 Likes

Wins the prize for the longest README! :grinning:

1 Like

Sure, I’m sorry if you thought I was implying this, it was not my intention. I meant that composing complex UIs is not a fully solved matter yet, at least at the documentation level, and that people are still trying to find new ways to do it.

But I should not have tried to guess other people motivation, and edited my post in consequence (also removing a lot of useless verbiage by the way).

…hard work in every UI system, and the search for a silver bullet will never end!

5 Likes

@rtfeldman, reading the very interesting http://rundis.github.io/blog/2017/elm_bootstrap_launch.html blog, it is said that the API went through several iterations after some discussion on slack with you and others.

I would be very interested to know the pros and cons in its current version from your point of view, because AFAICT it tries to standardize its module “triplets” approach without breaking the “never put functions in your Model or Msg types” rule, and adding a nice pipeline configuration API by the way.

I think it’d be better to start a new thread about this…and probably more useful to hear others’ perspective than my own, since I haven’t actually used the current release! I had some early input, but to be honest I haven’t kept up with it; I don’t have any projects that use Bootstrap.

1 Like

Ok. I thought bringing readers attention to alternative composition techniques that are less controversial could offer some insight in this thread, but I get your point. Thank you.

Sure, but we use tools to help us with hard work. For example, refactoring in JS is hard work and Elm’s type system helps us with it (but it’s not a silver bullet either).

Plain TEA is fine if you are able to rely on the DOM elements most of the time. The boilerplate you write is rewarded with explicitness and transparency in how your system works. But if each button and text field needs to be a component with a complex API, this is where it stops to be funny.

The problem exists, just not everybody faces with it.

1 Like

Absolutely! But in my experience, the more complex the UI, the more I find off-the-shelf solutions force me into a box rather than helping me implement a nice solution to my unusual use case.

1 Like

I agree with your point (that the more complex the UI, the harder it is to find helpful off-the-shelf solutions). However, I think developers have a tendency to think their application is technically unique/different and it is easy to fall into the trap of Not Invented Here syndrome with such a mindset. “Our application is unique and no existing solution could solve our problem(s) as well as we can. We must invent our own!”

My main point is that it’s probably worth at least looking at what other people are doing and imitating them before taking the jump to design your own solution from scratch. Your elm-spa-example being a great starting point for planning a complex app. This is especially true for people that are relatively new to Elm and may have only been writing in a functional language for a few months.

Definitely! But for learning purposes, packages are very different from examples. One of the main advantages of using a package is that it saves you the time and effort of learning what’s going on under the hood of that package.

When it comes to making complex UIs, I don’t think that time and effort cost is avoidable.

To me, this package has the same characteristics as an elm-immutable-data-structures package: its appeal decreases the better you get at working in the domain. I have to admit I worry that the big bold warning in the readme for beginners not to use it (which I agree with and appreciate seeing!) will be about as effective as Dan Abramov shouting himself blue in the face that Redux isn’t necessary for React beginners.

I suppose we’ll find out!

:smile:

The debates around the “triplet”-architecture has been somewhat confusing for me for some of the same reasons as Redux in the context of React.
I believe the mantra to use as little of the features available as possible, but no less? In the sense that redux starts making sense when you start to grow to a certain level; in the same way as any Elm-module might eventually grow to a full featured subprogram, or triplet, of it’s project?

Is that accurate?

I’d say it’s “use the simplest approach that gets the job done.” In practice, making another model/view/update is rarely the simplest approach that gets the job done. For example, in https://github.com/rtfeldman/elm-spa-example it seemed like the right tool for the job once out of all the different ways view logic gets reused across the app.

Unfortunately, model/view/update triplets feel similar enough to Objects that using them as a foundational building block feels like a familiar approach coming from OO (e.g. React components). We used to get a lot of folks on Elm Slack who had used that pattern wherever it seemed like it could fit, as opposed to trying simpler APIs first. When they used that pattern eagerly like this, they were unhappy with how the code turned out.

Simplifying their code bases after the fact took a lot of work; hence the push to steer people away from going down that path in the first place - such as the apt warnings in this package that it’s not for beginners!

4 Likes

This is my experience as well actually. I have been always avoiding things like Bootstrap or various “form builders” (both for client-side and server-side rendered apps) because I like to be able to precisely control how everything works and to fully understand how it works.

But sometimes you need “off-the-shelf solutions”. For example, you use elm-lang/virtual-dom despite of the fact that it limits you in certain ways (you shouldn’t modify the DOM directly if you use it). But those limitations are totally acceptable and you really need the level of abstraction that it gives you to write complex dynamic GUIs.

elm-components simply gives you another level of abstraction (if you need it of course). Also, I’ve tried to make it as much flexible and not limiting as I could, all techniques you know and use should be available to you with elm-components as well. You can use components when you need them and just plain old functions when they are enough, so you are able to scale your app using both strategies.

Not in my practice :slight_smile:

If you have time to post some code on another thread (or on Slack or something) I’d be curious to see it!

@rtfeldman consider a button that should automatically remove focus when you click on it:

module App.Components.Button exposing (Config, Container, button)

import Components exposing (Component, Signal, convertSignal, send)
import Components.Html as Html exposing (Html)
import Components.Html.Attributes as Attr
import Components.Html.Events as Events
import Css
import Dom
import Task


type alias Container =
    Components.Container State Msg Parts


type alias Config m p =
    { onClick : Signal m p
    , css : List Css.Style
    }


type alias State =
    ()


type Msg
    = Clicked
    | BlurAttempted (Result Dom.Error ())


type alias Parts =
    ()


type alias Self p =
    Components.Self State Msg Parts p


button : Config m p -> List (Html m p) -> Component Container m p
button config contents =
    Components.mixed
        { init = \_ -> ( (), Cmd.none, [] )
        , update = update config
        , subscriptions = \_ _ -> Sub.none
        , view = view config contents
        , parts = ()
        }


update :
    Config m p
    -> Self p
    -> Msg
    -> State
    -> ( State, Cmd Msg, List (Signal m p) )
update config self msg state =
    case msg of
        Clicked ->
            ( state
            , Task.attempt BlurAttempted (Dom.blur self.id)
            , [ config.onClick ]
            )

        BlurAttempted focusResult ->
            case focusResult of
                Ok () ->
                    ( state, Cmd.none, [] )

                Err (Dom.NotFound id) ->
                    Debug.crash "TODO: Handle error"


view : Config m p -> List (Html m p) -> Self p -> State -> Html m p
view config contents self () =
    Html.button
        [ Attr.id self.id
        , Attr.type_ "button"
        , Attr.css config.css
        , Events.onClick (send Clicked |> convertSignal self)
        ]
        contents

I want all buttons in my app to have this functionality. It doesn’t even have any state, but you’ll need to manually call its update and use an OutMsg each time you need a button. You may argue that I shouldn’t do things like this at all because browser vendors know better how a button should behave, but I don’t agree (I really want it). And it’s not fully completed actually, ideally I want to distinguish between keyboard and mouse “clicks” and leave the focus where it is if the “click” is made via keyboard (this seems to be not that easy and may introduce some state). Also imaging adding an animation which will also introduce state and subscriptions.

A couple more examples:

  • Each text field has quite complex HTML/CSS (looks a bit like material design text field boxes, but even more complex) and needs to maintain a focus state. Also it makes calls to a port to implement “reset” functionality (Html.Keyed doesn’t work for me because I have issues with Chrome’s autofill. But this won’t be needed after 0.19 of course).
  • Password field wraps the text field component and adds a “reveal” button. It needs some state to handle how it works (also that button is a Button component from the example above).
  • Dialog component with animated background. This one isn’t used that often but still I’m uncomfortable that nothing prevents me from forgetting to connect its subscriptions.

Indeed, some argue you should not do what you’re doing. However, since you disagree with browsers and want that behavior across the board anyway, why do it on a case-by-case basis instead of taking advantage of event bubbling?

You could add add a single event listener to the root element which detects if the target is a button and blurs if so. This would effectively replace the browser’s design with yours.

What changes about the text field when focus changes?

Yes, this should work. So thank you for the tip – it should be possible to eliminate the need to use a component in this case. But that will make the button less modular, and while it will be okay for me, what if you wanted to share it between multiple apps? Or maybe to build a package that provides various UI widgets, and it should use this functionality for its buttons?

Styles of the div inside of which the input element is located.