最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do you create stateful, modular, self-contained web components in Elm? - Stack Overflow

programmeradmin3浏览0评论

Suppose you want to create a UI which has 3 buttons. When you click in one of them, the others are released. In JavaScript, you could write:

var elements = ["Foo","Bar","Tot"].map(function(name){
  var element = document.getElementById(name);
  element.onclick = function(){
    elements.map(function(element){
      element.className = 'button';
    });
    element.className = 'button selected';
  };
  return element;
});
.button {
  border: 1px solid black;
  cursor: pointer;
  margin: 4px;
  padding: 4px;
}
.selected {
  background-color: #DDDDDD;
}
<div>
  <span id='Foo' class='button'>Foo</span>
  <span id='Bar' class='button'>Bar</span>
  <span id='Tot' class='button'>Tot</span>
</div>
  

Suppose you want to create a UI which has 3 buttons. When you click in one of them, the others are released. In JavaScript, you could write:

var elements = ["Foo","Bar","Tot"].map(function(name){
  var element = document.getElementById(name);
  element.onclick = function(){
    elements.map(function(element){
      element.className = 'button';
    });
    element.className = 'button selected';
  };
  return element;
});
.button {
  border: 1px solid black;
  cursor: pointer;
  margin: 4px;
  padding: 4px;
}
.selected {
  background-color: #DDDDDD;
}
<div>
  <span id='Foo' class='button'>Foo</span>
  <span id='Bar' class='button'>Bar</span>
  <span id='Tot' class='button'>Tot</span>
</div>
  

That is stateful, but not modular, self contained nor pure. In fact, it is so bad the state (a ternary bit) isn't even obvious. You can not inject it inside another model, how many times you want.

Most of the answers provided here so far are stateful, but not modular. The issue is that using that strategy, you can't drop a ponent inside another without the parent knowing about the children's model. Ideally, that would be abstracted away - the parent shouldn't need to mention the model of the child nodes on its own model, nor manually plumbing state from parent to node should be necessary. If I want to create a list of the app above, I don't want to store the state of each child node on the parent.

How do you create stateful, modular, self-contained web ponents in Elm?

Share Improve this question edited Dec 1, 2015 at 19:32 MaiaVictor asked Dec 1, 2015 at 10:39 MaiaVictorMaiaVictor 53.1k47 gold badges158 silver badges302 bronze badges 1
  • In the Elm Architecture, every ponent has a dependency to its direct children, but neither to its parents nor to its indirect children. That's reusability: you can use any ponent in any other ponent, and coupling will be restricted to only the parent municating with its children. To achieve the modularity you might mean, you have to extract the mon functionality in your ponents so that there are generic init-update-view functions that take a record representing the varying functionality of your ponents. – thSoft Commented Dec 2, 2015 at 10:39
Add a ment  | 

4 Answers 4

Reset to default 3

Elm can satisfy each of those requirements, making your ponent stateful, modular, self-contained, and pure. Here's an example in Elm using StartApp.Simple (pardon the inline styling):

import StartApp.Simple exposing (start)
import Html exposing (Html, div, span, text)
import Html.Attributes exposing (id, class, style)
import Html.Events exposing (onClick)

type alias Model =
  { elements : List String
  , selected : Maybe String
  }

init : Model
init =
  { elements = [ "Foo", "Bar", "Tot" ]
  , selected = Nothing
  }

type Action
  = Select String

update : Action -> Model -> Model
update action model =
  case action of
    Select s ->
      { model | selected = Just s }

view : Signal.Address Action -> Model -> Html
view address model =
  let 
    btn txt =
      span
        [ id txt
        , buttonStyle txt
        , onClick address <| Select txt
        ] [ text txt ]

    buttonStyle txt =
      style (
        [ ("border", "1px solid black")
        , ("cursor", "pointer")
        , ("margin", "4px")
        , ("solid", "4px")
        ] ++ (styleWhenSelected txt))

    styleWhenSelected txt =
      case model.selected of
        Nothing -> []
        Just s ->
          if s == txt then
            [ ("background-color", "#DDDDDD") ]
          else
            []
  in
    div [] <| List.map btn model.elements


main =
  start
    { model = init
    , update = update
    , view = view
    }

You have a clearly defined, statically typed model, an explicit and limited number of actions that can be performed against that model, and a type-safe html rendering engine.

Take a look at the Elm Architecture Tutorial for more information.

I just saw Chad's answer, while I was writing mine. This one also uses the Elm Architecture, but uses you original class names in the Html, and has a "stronger" model. The nice part about the stronger model is that you literally see the three bits like you mentioned in your question. There is also less implicit coupling between the name of the id and the actual button. But it leaves you with some duplicated names that you may or may not want. Depends on how much you want this coupling.

import StartApp.Simple as StartApp
import Html as H exposing (Html)
import Html.Attributes as HA
import Html.Events as HE

type alias Model =
  { foo : Bool
  , bar : Bool
  , tot : Bool
  }

type Action
  = Foo
  | Bar
  | Tot

model : Model
model =
  { foo = False
  , bar = False
  , tot = False
  }

update : Action -> Model -> Model
update clicked _ =
  case clicked of
    Foo -> { model | foo = True }
    Bar -> { model | bar = True }
    Tot -> { model | tot = True }

view : Signal.Address Action -> Model -> Html
view addr { foo, bar, tot } =
  [ foo, bar, tot ]
  |> List.map2 (viewButton addr) buttons
  |> H.div []

buttons : List (String, Action)
buttons =
  [ ("Foo", Foo)
  , ("Bar", Bar)
  , ("Tot", Tot)
  ]

viewButton : Signal.Address Action -> (String, Action) -> Bool -> Html
viewButton addr (id, action) selected =
  H.span
    [ HA.id id
    , HA.classList
      [ ("button", True)
      , ("selected", selected)
      ]
    , HE.onClick addr action
    ]
    [ H.text id
    ]

buttonStyle =

main =
  StartApp.start
    { model = model
    , view = view
    , update = update
    }

As devdave suggest, nesting is the only way that I have found to modularise ponents.

I have implemented a similar example which you can see live here: http://afcastano.github.io/elm-nested-ponent-munication/

The idea is that children expose functions to get the properties of their own model. This functions can in turn call even more nested functions for children ponents.

Check out the Readme.md of this repo for code examples: https://github./afcastano/elm-nested-ponent-munication

Here is another version of the same thing :)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import StartApp.Simple

type alias Model = Maybe String -- the id of the selected span

type Action = ButtonClick String

update : Action -> Model -> Model
update action model =
  case action of
    ButtonClick id ->
      Just id


view : Signal.Address Action -> Model -> Html
view address model =
  let
    renderButton id' label' =
      let
        selectedClass =
          case model of
            Just modelId -> if modelId == id' then " selected" else ""
            Nothing -> ""
      in
      span [ id id', class ("button" ++ selectedClass), onClick address (ButtonClick id') ] [ text label' ]
  in
  div []
    [ renderButton "foo" "Foo"
    , renderButton "bar" "Bar"
    , renderButton "tot" "Tot"
    ]


main =
  StartApp.Simple.start { model = Nothing, update = update, view = view }

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论