Layout questions with Style Elements and Elm - elm

I am building my first app in Elm and decided to use Style Elements package instead of CSS.
This is the layout that I am attempting. Its a single page app that does not scroll.
here is some code
view : Model -> Html Msg
view model =
layout Styles.stylesheet <|
pageWrapper
pageWrapper : Element Styles variation Msg
column Page
[ width (percent 100), height (percent 100) ]
[ navMain
, contentArea
]
navMain : Element Styles variation Msg
navMain =
row Navigation
[ spread, padding 10 ]
[ el (Nav Logo) [ width (px 50)] (text "Logo")
, row (Nav Link)
[ padding 15, spacing 10]
[ el (Nav Link) [] (text "About")
, el (Nav Link) [] (text "Services")
, el (Nav Link) [] (text "Team")
, el (Nav Link) [] (text "Location")
, el (Nav Link) [] (text "Contact")
]
]
contentArea : Element Styles variation Msg
contentArea =
-- At this point I thought I would make a row with an el with the image
and a column containing the other two images. And other than creating heights with pixels I don't know how to extend the main content to the bottom of the page.
What are some good example apps I can look at to get a good idea of how to control the layout? I have looked through several, and I feel like I am just missing something very obvious because so far SE has been awesome to work with!

Here's a very basic example:
https://ellie-app.com/k25rby75Da1/1
I've left the heights of the elements undefined below but you should be able to figure that out to suit your needs. I updated the ellie link to a version that I think better approximates the height in your example.
module Main exposing (main)
import Html
import Html.Attributes
import Color
import Style
import Style.Font as Font
import Style.Color as Color
import Element exposing (..)
import Element.Attributes exposing (..)
main =
Html.beginnerProgram
{ model = model
, update = update
, view = view
}
type alias Model =
{ navbar : String
, leftcontent : String
, rightcontent : { top : String, bottom : String }
}
model : Model
model =
{ navbar = "This is the navbar"
, leftcontent = "This is the left column content"
, rightcontent =
{ top = "This is the right top content"
, bottom = "This is the right bottom content"
}
}
update model msg =
model
type MyStyles = Navbar | Left | RightTop | RightBottom | None
stylesheet =
Style.styleSheet
[ Style.style Navbar [ Color.background Color.red ]
, Style.style Left [ Color.background Color.blue ]
, Style.style RightTop [Color.background Color.purple ]
, Style.style RightBottom [Color.background Color.gray ]
]
view model =
Element.viewport stylesheet <|
(column None [] [
row Navbar [] [ text model.navbar ]
, wrappedRow None []
[ column Left [ width (percent 50)] [ text model.leftcontent ]
, wrappedColumn None [ width (percent 50)]
[ el RightTop [ width fill] (text model.rightcontent.top)
, el RightBottom [ width fill ] (text model.rightcontent.bottom)
]
]])

Related

Modal in Elm without framework

I am new to ELM and I want to create a modal without the use of any libraries such as Bootstrap or ELM-UI. I found this simple example online which is also using JSON Decode. Is there a possibility to have the modal work simply without any framework/library and JSON Decode? How can I modify the code to simply get a working modal?
module Main exposing (main)
import Browser
import Html exposing (Html, Attribute, button, div, span, text)
import Html.Events exposing (onClick, on)
import Html.Attributes exposing (class, style)
import Json.Decode as Decode
type alias Model =
{ isVisible : Bool, count : Int }
initialModel : Model
initialModel =
{ isVisible = False, count = 0 }
type Msg
= Show
| Hide
| Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Show ->
{ model | isVisible = True }
Hide ->
{ model | isVisible = False }
Increment ->
{ model | count = model.count + 1 }
Decrement ->
{ model | count = model.count - 1 }
view : Model -> Html Msg
view model =
div []
[ button [ onClick Show ] [ text "Show!" ]
, if model.isVisible then
div
([ class dialogContainerClass
, on "click" (containerClickDecoder Hide)
]
++ dialogContainerStyle
)
[ div dialogContentStyle
[ span [] [ text "Click anywhere outside this dialog to close it!" ]
, span [] [ text "Clicking on anything inside of this dialog works as normal." ]
, div []
[ button [ onClick Decrement ] [ text "-" ]
, text (String.fromInt model.count)
, button [ onClick Increment ] [ text "+" ]
]
]
]
else
div [] []
]
dialogContainerClass : String
dialogContainerClass = "dialog-container-class"
containerClickDecoder : msg -> Decode.Decoder msg
containerClickDecoder closeMsg =
Decode.at [ "target", "className" ] Decode.string
|> Decode.andThen
(\c ->
if String.contains dialogContainerClass c then
Decode.succeed closeMsg
else
Decode.fail "ignoring"
)
dialogContainerStyle : List (Attribute msg)
dialogContainerStyle =
[ style "position" "absolute"
, style "top" "0"
, style "bottom" "0"
, style "right" "0"
, style "left" "0"
, style "display" "flex"
, style "align-items" "center"
, style "justify-content" "center"
, style "background-color" "rgba(33, 43, 54, 0.4)"
]
dialogContentStyle : List (Attribute msg)
dialogContentStyle =
[ style "border-style" "solid"
, style "border-radius" "3px"
, style "border-color" "white"
, style "background-color" "white"
, style "height" "120px"
, style "width" "440px"
, style "display" "flex"
, style "flex-direction" "column"
, style "align-items" "center"
, style "justify-content" "center"
]
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
If I understand your question correctly, the problem you're trying to solve is clicking outside the modal to close it. Decoding the event object to get information about the DOM is a bit of a hack in Elm – I think you're right to try to avoid it, unless necessary. One way to achieve the same thing is to add a click event handler with stop propagation to your modal contents – this stops the click event from firing on the container when it originates from within the modal.
I've put your example code in an Ellie and made some small changes: https://ellie-app.com/b9gDPHgtz2ca1
This solution uses Html.Events.stopPropagationOn, which is like on but does a call to event.stopPropagation(). This function does require you to supply a decoder, so I'm afraid you can't get away from importing Json.Decode, but we are using the simplest possible decoder – Decode.succeed – and only to satisfy the parameters of the function.
I've added a NoOp variant to Msg, as there is nothing to do when the modal is clicked; simply attaching this event handler stops the Hide event from firing when we don't want it to.
Code
module Main exposing (main)
import Browser
import Html exposing (Attribute, Html, button, div, span, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (on, onClick)
import Json.Decode as Decode
type alias Model =
{ isVisible : Bool, count : Int }
initialModel : Model
initialModel =
{ isVisible = False, count = 0 }
type Msg
= Show
| Hide
| Increment
| Decrement
| NoOp
update : Msg -> Model -> Model
update msg model =
case msg of
Show ->
{ model | isVisible = True }
Hide ->
{ model | isVisible = False }
Increment ->
{ model | count = model.count + 1 }
Decrement ->
{ model | count = model.count - 1 }
NoOp ->
model
view : Model -> Html Msg
view model =
div []
[ button [ onClick Show ] [ text "Show!" ]
, if model.isVisible then
div
(onClick Hide
:: dialogContainerStyle
)
[ div
(onClickStopPropagation NoOp
:: dialogContentStyle
)
[ span [] [ text "Click anywhere outside this dialog to close it!" ]
, span [] [ text "Clicking on anything inside of this dialog works as normal." ]
, div []
[ button [ onClick Decrement ] [ text "-" ]
, text (String.fromInt model.count)
, button [ onClick Increment ] [ text "+" ]
]
]
]
else
div [] []
]
onClickStopPropagation : msg -> Html.Attribute msg
onClickStopPropagation msg =
Html.Events.stopPropagationOn "click" <| Decode.succeed ( msg, True )
dialogContainerStyle : List (Attribute msg)
dialogContainerStyle =
[ style "position" "absolute"
, style "top" "0"
, style "bottom" "0"
, style "right" "0"
, style "left" "0"
, style "display" "flex"
, style "align-items" "center"
, style "justify-content" "center"
, style "background-color" "rgba(33, 43, 54, 0.4)"
]
dialogContentStyle : List (Attribute msg)
dialogContentStyle =
[ style "border-style" "solid"
, style "border-radius" "3px"
, style "border-color" "white"
, style "background-color" "white"
, style "height" "120px"
, style "width" "440px"
, style "display" "flex"
, style "flex-direction" "column"
, style "align-items" "center"
, style "justify-content" "center"
]
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}

Elm img attribute too many arguments

I am extremely new to Elm, and I am encountering a problem which frustrates me to no end.
Here is my code:
view : Model -> Html Msg
view model =
img [src "Img1.png", width 300, height 300] []
div []
[ input [ onInput ChangeUserInput ] []
, button [ onClick SaveText ] [ text "Save" ]
, button [ onClick Clear ] [ text "Clear" ]
, h1 [] [text model.userInput]
]
And the error I receive is that
The `img` function expects 2 arguments, but it got 5 instead
I think its parsing the div tag arguments as img arguments, but I can't figure out how to fix it.
Your view function needs to return a single HTML element. At the moment it seems you are trying to return two elements: an img and a div. The div and its two arguments are being picked up as arguments to the img because there is nothing in your code that Elm can use to identify the end of the list of arguments to pass to the img function.
You will need to wrap them both in an element that contains them, for example, another div:
view : Model -> Html Msg
view model =
div []
[ img [ src "Img1.png", width 300, height 300 ] []
, div []
[ input [ onInput ChangeUserInput ] []
, button [ onClick SaveText ] [ text "Save" ]
, button [ onClick Clear ] [ text "Clear" ]
, h1 [] [ text model.userInput ]
]
]

elm-ui center elements in wrapped row

I'm using Elm with mdgriffiths/elm-ui, and I've really been enjoying it. Right now, I'm trying to create a centered, wrapped row of elements:
I can get it to this point:
using this code:
button : String -> String -> Element Msg
button label url =
link
[ height (px 150)
, width (px 150)
, Border.width 1
, Background.color (rgb255 255 255 255)
]
{ url = url
, label =
Element.paragraph
[ Font.center ]
[ textEl [] label ]
}
row : Element Msg
row =
Element.wrappedRow
[ Element.spacing 25
, Element.centerX
, Element.centerY
, width (fill |> Element.maximum 600)
, Font.center
]
[ button "A" "/a"
, button "B" "/b"
, button "C" "/c"
, button "D" "/d"
]
But when I try adding Element.centerX to my buttons like this
link
[ Element.centerX
, ...
]
I get this instead:
I've also tried Font.center without success, and I don't know what else I can try.
I'm not sure if I'm missing something I should be using, or if the whole thing needs re-arranging, or if I just need to use the built-in CSS stuff.
Update:
Link to an Ellie with the issues I'm seeing.
https://ellie-app.com/7NpM6SPfhLHa1
This Github issue is useful for this problem. I'm afraid you will have to use some CSS (unless I'm missing something). I've found this before with elm-ui; every now and then it can't quite do what you want and you need a bit of CSS.
This works for me (taken from the post by AlienKevin in the link above). You need to set "marginLeft" and "marginRight" to "auto".
module Main exposing (main)
import Element as E
import Element.Border
import Html.Attributes
box : String -> E.Element msg
box label =
E.paragraph
[ E.width <| E.px 200
, Element.Border.width 1
, E.htmlAttribute (Html.Attributes.style "marginLeft" "auto")
, E.htmlAttribute (Html.Attributes.style "marginRight" "auto")
]
[ E.text label ]
row : E.Element msg
row =
E.wrappedRow []
[ box "A"
, box "B"
, box "C"
]
main =
E.layout [] row
(See here for an Ellie.)
You can also do the following:
Define the following elm-ui classes. I usually setup a UI.elm module for this
centerWrap : Attribute msg
centerWrap =
Html.Attributes.class "centerWrap"
|> htmlAttribute
dontCenterWrap : Attribute msg
dontCenterWrap =
Html.Attributes.class "dontCenterWrap"
|> htmlAttribute
Add the following to your css. Basically says center elements if has centerWrap class but not dontCenterWrap class.
:not(.dontCenterWrap).centerWrap>div.wrp {
justify-content: center !important;
}
Apply the attribute
wrappedRow [ width fill, UI.centerWrap, spaceEvenly ] [...]
Assuming you created a custom element that centerWraps and wanted to disable that you could use UI.dontCenterWrap
centerWrappedRow attr children =
wrappedRow (UI.centerWrap :: attr) children
-- somewhere else
...
centerWrappedRow [UI.dontCenterWrap] [..]
...

How can I make a table striped using elm-bootstrap 4.1.0?

I have been trying several things and cannot work out how to style this Elm table with Bootstrap so it's striped. I am trying to use elm-bootstrap and have installed rundis/elm-bootstrap 4.1.0
Bootstrap.Table is currently unused:
module Players.List exposing (..)
import Html exposing (..)
import Html.Attributes exposing (class)
import Msgs exposing (Msg)
import Models exposing (Player)
import RemoteData exposing (WebData)
import Bootstrap.Table as Table
view : WebData (List Player) -> Html Msg
view response =
div []
[ nav
, maybeList response
]
nav : Html Msg
nav =
div [ class "clearfix mb2 white bg-black" ]
[ div [ class "left p2" ] [ text "Players" ] ]
maybeList : WebData (List Player) -> Html Msg
maybeList response =
case response of
RemoteData.NotAsked ->
text ""
RemoteData.Loading ->
text "Loading..."
RemoteData.Success players ->
list players
RemoteData.Failure error ->
text (toString error)
list : List Player -> Html Msg
list players =
div [ class "col-md-4" ]
[ table [ class "table table-striped" ]
[ thead []
[ tr []
[ th [] [ text "Id" ]
, th [] [ text "Initials" ]
, th [] [ text "Time" ]
, th [] [ text "Score" ]
]
]
, tbody [] (List.map playerRow players)
]
]
playerRow : Player -> Html Msg
playerRow player =
tr []
[ td [] [ text player.id ]
, td [] [ text player.initials ]
, td [] [ text (toString player.time) ]
, td [] [ text (toString player.score) ]
]
As this should be ultra simple I'm clearly missing something here. How can I make this table striped?
Your example uses HTML functions from Elm's HTML library but you're probably going to have a better time using the appropriate Bootstrap types and functions. For example, using the table options defined in the documentation, you could rewrite the view functions like this:
list : List Player -> Html msg
list players =
Table.table
{ options = [ Table.striped ]
, thead = Table.thead []
[ Table.tr []
[ Table.th [] [ text "Id" ]
, Table.th [] [ text "Initials" ]
, Table.th [] [ text "Time" ]
, Table.th [] [ text "Score" ]
]
]
, tbody = Table.tbody [] (List.map playerRow players)
}
playerRow : Player -> Table.Row msg
playerRow player =
Table.tr []
[ Table.td [] [ text player.id ]
, Table.td [] [ text player.initials ]
, Table.td [] [ text (Debug.toString player.time) ]
, Table.td [] [ text (Debug.toString player.score) ]
]
That will give you the right HTML but you may still need to import the Bootstrap styles. The documentation gives an example of including the stylesheet, which you could do in some wrapping function, for example:
import Bootstrap.Grid as Grid
import Bootstrap.Table as Table
import Bootstrap.CDN as CDN
view : Model -> Html Msg
view model =
Grid.container []
[ CDN.stylesheet -- creates an inline style node with the Bootstrap CSS
, Grid.row []
[ Grid.col []
[ list model.players ]
]
]
Here is a slimmed down Ellie example to play with.

elm-mdl: How to push a record from tab X into the model of tab Y and update tab Y view?

I'm using the demo codebase of elm-mdl as a starting point for my elm project. I have a situation where I need to click a button on one tab (e.g. "tab X") and mutate the state of a different tab (e.g. "tab Y").
Every way I've wired it up so far does not work. This seems like an odd case because the parent of all tabs (e.g. Layout) in the demo codebase is "Demo". It seems in my case that the dependency graph become convoluted because the effect would reach across "Demo" children.
How can this be done? I'm running 0.18.0.
https://github.com/debois/elm-mdl/tree/v8/demo
I've done an example playing around with what I've understand from your question.
I think your issue have to be that you are not passing the model through your tabs , so they are not rendering data related to the current state, instead you should be taking data from the initial model which always keeps inmutable.
Starting from the example code you should have this:
view : Model -> Html Msg
view model =
Tabs.render Mdl
[ 0 ]
model.mdl
[ Tabs.ripple
, Tabs.onSelectTab SelectTab
, Tabs.activeTab model.tab
]
[ Tabs.label
[ Options.center ]
[ Icon.i "info_outline"
, Options.span [ css "width" "4px" ] []
, text "About tabs"
]
, Tabs.label
[ Options.center ]
[ Icon.i "code"
, Options.span [ css "width" "4px" ] []
, text "Example"
]
]
[ case model.tab of
0 ->
tab0 model
_ ->
defaultTab model
]
My working example is here