A reusable rater for Elm

I wanted to learn how to write reusable components/widgets/views in Elm so I decided to try my hand at building a rater. Here’s what I managed to build:

I’d love to hear your thoughts and get any suggestions on how the API might be improved.

16 Likes

One thing that bothers me about the API is that I have to configure the custom event handlers through the update function. See here for example. But, for HTML elements like a button you add the event handlers (like onClick) in the view code. Like so.

On the other hand, since that’s the way the abstraction naturally fell out I’m not too concerned about it.

One alternative you can have is to join the internal state with a custom type encoding the events you want the user of your component to receive.

rater :  Rater.State -> Html ( Rater.State, Rater.Event ) 

and you would use it like Html.map OnRater (rater model.fooRaterState) where you have

type Msg 
    = OnRater (Rater.State, Rater.Event)

You would then be able to pattern match it in your update like this:

    OnRater (newState, Rater.Changed value) -> 
        {model | fooRater = value, fooRaterState = newState} 

    OnRater (newState , _ ) -> 
        {model | fooRaterState = newState} 

You can of course, also have rater : ((Rater.State, Rater.Event) -> msg ) -> Rater.State -> Html msg
and use it in your views like rater OnRater model.fooRaterState

Thanks for the suggestion. How exactly would onChange, onHover and onLeave be configured in the view in this approach? I don’t see it.

You are not configuring them in the view, you are configuring them in the the rater and you are generating the events as Rater.Events. It would be the job of the client of the rater if they chose to handle the events or not.

I have altered the simple counter example to make it a little bit more complex to demonstrate this approach. Please pay attention to the simple API exposed in the first part of the example. That API can remain as simple as that no matter how complex the widget gets because, in essence it is about 2 things, updating its own state and informing the consumer that there is a widget event that they might want to react to.

https://ellie-app.com/6SN5w8LZ7Wga1

I’ve setup the counter to emit a Hover message with an encoded value (green for when the +1 button is hovered and red for -1)

This implementation duplicates the value (count) which is a very bad idea. You can imagine an implementation where the essential state of the widget is externalized.

I understand now. I’ll explore that approach further and see if I get an API I like. Thanks!

The limitation of this pattern is that you cannot have side-effects in the implemented widget (not ones that can be handled by the widget, of course you can delegate the generation of the side-effects based on some message you generate for the consumer of the widget).

If you used CSS for the hover styles, you could eliminate the state management and simplify the API:
https://ellie-app.com/6Tjs2sj3LGra1

…you could eliminate the state management and simplify the API

Not quite. Knowing the transient rating allows me to write custom onHover and onLeave handlers. Check out the examples " Customize onHover and onLeave", “Movie Rating” and “Reversed Rating” in the demo to see what I mean.

That’s fine. I wouldn’t want side effects in the implemented widget. Side effects should be pushed as far to the edge of the app as possible, so that’s good.

You could do onHover/onLeave statelessly too though :slight_smile:
https://ellie-app.com/6TpctCDcHzga1

4 Likes

Nice! This seems to be a much better approach.

If you do not need hover information outside the module, you can use CSS to emulate hover features using the CSS any sibling operator.

I used it to build a following line effect.
https://codepen.io/franzskuffka/pen/GDExL

Thanks! Yeah, that’s what @Herteby was showing me as well.