I want to put mathematical equations in a single page app written in elm.
I would like the equations to be rendered in the app and not being embedded as prerendered images.
I tried to realize this using Katex for elm (https://package.elm-lang.org/packages/yotamDvir/elm-katex/latest/) but my approach has 3 major problems:
when the page is loaded initially the equations are not rendered
the rendering does not always work when a link is clicked
rendered math elements are preserved in page changes when a link is clicked, thus destroying the content of the page.
This is hoow it looks:
Here is the code that I am using right now:
index.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>SPA with Math formulas</title>
<style>
body {
padding: 0;
margin: 0;
background-color: #000000;
color: #ffffff;
}
</style>
<script src="main.js"></script>
<style>
/* LaTeX display environment will effect the LaTeX characters but not the layout on the page */
span.katex-display {
display: inherit;
/* You may comment this out if you want the default behavior */
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex#0.12.0/dist/katex.min.css"
integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/katex#0.12.0/dist/katex.min.js"
integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4"
crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex#0.12.0/dist/contrib/auto-render.min.js"
integrity="sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa" crossorigin="anonymous"
onload="renderMathInElement(document.body);"></script>
</head>
<body>
<div id="elm-main"> </div>
<script>
// Initialize your Elm program
var app = Elm.Main.init({
flags: location.href,
node: document.getElementById('elm-main')
});
// Inform app of browser navigation (the BACK and FORWARD buttons)
window.addEventListener('popstate', function () {
app.ports.onUrlChange.send(location.href);
});
// Change the URL upon request, inform app of the change.
app.ports.pushUrl.subscribe(function (url) {
history.pushState({}, '', url);
app.ports.onUrlChange.send(location.href);
});
// Render math texts in app
app.ports.renderMath.subscribe(function () {
renderMathInElement(document.body, {
delimiters: [
{
left: "$begin-inline$",
right: "$end-inline$",
display: false
},
{
left: "$begin-display$",
right: "$end-display$",
display: true
}]
});
});
</script>
<noscript>
This site needs javascript enabled in order to work.
</noscript>
</body>
</html>
src/Main.elm
port module Main exposing (..)
import Browser exposing (Document, application)
import Element exposing (Attribute, Element)
import Element.Font
import Html
import Html.Attributes
import Html.Events
import Json.Decode as D
import Katex
import Url
import Url.Parser
main =
Browser.document
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
type Route
= Home
| Other
init : String -> ( Route, Cmd Msg )
init url =
( locationHrefToRoute url, Cmd.none )
type Msg
= PushUrl Route
| UrlChanged String
update : Msg -> Route -> ( Route, Cmd Msg )
update msg route =
case msg of
PushUrl newRoute ->
( route, pushUrl (stringFromRoute newRoute) )
UrlChanged url ->
( locationHrefToRoute url, renderMath () )
stringFromRoute : Route -> String
stringFromRoute route =
case route of
Home ->
"/"
Other ->
"/other"
locationHrefToRoute : String -> Route
locationHrefToRoute locationHref =
case Url.fromString locationHref of
Nothing ->
Home
Just url ->
Maybe.withDefault Home (Url.Parser.parse routeParser url)
routeParser : Url.Parser.Parser (Route -> a) a
routeParser =
Url.Parser.oneOf
[ Url.Parser.map Home Url.Parser.top
, Url.Parser.map Other (Url.Parser.s "other")
]
view : Route -> Document Msg
view route =
{ title = "SPA and Katex"
, body =
[ Element.layout
[ Element.Font.color (Element.rgb 1 1 1)
]
(Element.el
[ Element.centerX, Element.centerY ]
(viewPage route)
)
]
}
viewPage : Route -> Element Msg
viewPage route =
case route of
Home ->
Element.column [ Element.spacing 20 ]
[ link Other "Link to Other"
, Element.text "some text"
, "\\mathrm{home} = 6.2 \\times 10^{-34}"
|> Katex.inline
|> Katex.print
|> Element.text
]
Other ->
Element.column [ Element.spacing 20 ]
[ link Home "Link to Home"
, "\\mathrm{other} = 1.3 \\times 10^{-6}"
|> Katex.inline
|> Katex.print
|> Element.text
, Element.text "other text"
]
linkBehaviour : Route -> Attribute Msg
linkBehaviour route =
Element.htmlAttribute
(Html.Events.preventDefaultOn "click"
(D.succeed
( PushUrl route, True )
)
)
link : Route -> String -> Element Msg
link route labelText =
Element.link
[ linkBehaviour route
, Element.Font.color (Element.rgb255 119 35 177)
, Element.Font.underline
]
{ url = stringFromRoute route
, label = Element.text labelText
}
subscriptions : Route -> Sub Msg
subscriptions route =
Sub.batch
[ onUrlChange UrlChanged
]
port onUrlChange : (String -> msg) -> Sub msg
port pushUrl : String -> Cmd msg
port renderMath : () -> Cmd msg
I start my app with elm-live src/Main.elm -u --open -- --output=main.js --debug:
Related
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
}
How do I get an element's inner HTML from an elementId using browser object?
Is there something like elementIdHtml available in the WebdriverIO API?
The getHTML link for v4 is returning 403 Forbidden.
my goal is that i need to get all text inside all a._3cnp from an elementId
example html
<div class="container">
<a class="_3cnp">first link</a>
<a class="_3cnp">second link</a>
<a class="_3cnp">third link</a>
</div>
need to convert that to ["first link", "second link", ..]
i have the .container elementId already
this is what i did
.then(() => browser.elementIdElements(someElementId, 'a._3cnp'))
.then(buttonElem => {
console.log('->', buttonElem)
console.log('-->', buttonElem.getHTML)
buttonElem.getHTML().then(x => console.log('---->', x))
return buttonElem.value
})
result of elementIdElements is
buttonElem
{ sessionId: '2e2f144c8895a03da1b8df92f4613a33',
status: 0,
value:
[ { ELEMENT: '0.6603119466268468-24',
'element-6066-11e4-a52e-4f735466cecf': '0.6603119466268468-24' } ],
selector: 'a._3cnp' }
but buttonElem.getHTML is undefined
im using webdriverio standalone from botium-webdriverio-connector
LE:
Change your code accordingly to the following:
.then(() => browser.elementIdElements(someElementId, 'a._3cnp'))
.then(buttonElem => {
// buttonElem's 'value' will contain a list of all the matching elements
// thus, you have to call getHTML() on each item from 'value'
// the following will return the HTML of the first matching element
console.log('\nPrint element HTML: ' + buttonElem.value[0].getHTML());
return buttonElem.value[0].getHTML();
})
A better approach would be to loop between them & print the HTML:
.then(() => browser.elementIdElements(someElementId, 'a._3cnp'))
.value.forEach(buttonElem => {
console.log('\nPrint element HTML: ' + buttonElem.getHTML());
return buttonElem.getHTML();
})
The .getHTML() property is scoped to all the ELEMENT objects. For the sake of more didactical approach, I'm going to consider the task to be manipulating the HTML code found in a series of list items (<li>), from am unordered list (<ul>).
So you can do the following:
browser.getHTML('ul.ourList li.ourListItems') (this will return a list of all the <li>'s HTML code)
browser.element('ul.ourList li.ourListItems').getHTML() (this will return the first <li>'s HTML code)
$('ul.ourList li.ourListItems').getHTML() (this is the equivalent of the command above, only a relaxed version)
If you need to iterate through all the <li>s & get the HTML, do this:
let locator = 'ul.ourList li.ourListItems';
browser.elements(locator).value.forEach(elem => {
let elemHTML = elem.getHTML();
console.log( JSON.stringify(elemHTML) );
elemHTML.doSomethingWithIt();
})
where, elem will is an object with the following format:
{ ELEMENT: '0.285350058261731-1',
'element-6066-11e4-a52e-4f735466cecf': '0.285350058261731-1',
selector: 'ul li.fw-item.fz-16.lh-36.pos-r',
value: { ELEMENT: '0.285350058261731-1' },
index: 0
}
I have a report which I'm using as a basis to perform a number of Http calls to get details for each row.
LoadReport ->
( model
, Http.toTask (loadReport model.token model.page)
|> Task.andThen
(\report ->
Task.map (addProductDetailsResultsToReport report) (Task.sequence (prepareRequests model.token report))
)
|> Task.map filterOnlyMissingBarcodes
|> Task.attempt ProductData
)
The calls are sequenced and perform one after another which is very slow as I need to perform 20 calls in a row. I would like to do something analogues to JavaScript
Promise.all(prepareRequests)
I used to have them being processed using Cmd.Batch but then I couldn't find a way to know when the whole batch is finished loading, I need to load another batch if there are not enough rows on the screen.
I believe the solution already posted by Murph is correct. The following code is an example that demonstrates that solution by first getting a collection of photos from flickr and then getting captions for all those photos by batching a bunch of http get tasks. Two lists are maintained in the model - untitled photos and titled photos. As the responses to the http gets come in, the appropriate photo is added to titled photos with the title assigned.
In this example the code can tell that all the gets have been responded to when the length of the titled list is the same as the length of the untitled list but it could just as easily have been done by removing photos from the untitled list until it is empty.
Here's a working demo
module Main exposing (..)
import Browser
import Html exposing (Html, div, text)
import Html.Attributes as HA
import Http
import Json.Decode as DC
import Svg
import Svg.Attributes as SA
import Task
type Msg
= SetPhotos (Result Http.Error (List Photo))
| SetDescription (Result Http.Error ( String, String ))
main : Program () Model Msg
main =
Browser.element
{ init = init
, view = view
, update = update
, subscriptions = \m -> Sub.none
}
type alias Model =
Result Http.Error
{ untitled : List Photo
, titled : List Photo
}
decodeUser : DC.Decoder String
decodeUser =
DC.at [ "user", "id" ] DC.string
type alias Photo =
{ id : String
, secret : String
, server : String
, farm : Int
, description : Maybe String
}
-- Create a Photo record from info retrieved from flickr api.
-- Get description later
initPhoto : String -> String -> String -> Int -> Photo
initPhoto id sec ser farm =
Photo id sec ser farm Nothing
decodePhotoList : DC.Decoder (List Photo)
decodePhotoList =
DC.list <|
DC.map4 initPhoto
(DC.at [ "id" ] DC.string)
(DC.at [ "secret" ] DC.string)
(DC.at [ "server" ] DC.string)
(DC.at [ "farm" ] DC.int)
-- Decode photos from "flickr.people.getPublicPhotos" request.
decodePhotos : DC.Decoder (List Photo)
decodePhotos =
DC.at [ "photos", "photo" ] decodePhotoList
-- Decode descripion of photo from "flickr.photos.getInfo" request.
decodePhotoDescription : DC.Decoder String
decodePhotoDescription =
DC.at [ "photo", "description", "_content" ] DC.string
-- api key from flickr. Anyone who clones this project should
-- get their own api key.
apiKey : String
apiKey =
"e9d3fdd5c2e26f9ebd13f4983cf727db"
flickrRestServices : String
flickrRestServices =
"https://api.flickr.com/services/rest/?"
noJsonCallback : String
noJsonCallback =
"&format=json&nojsoncallback=1"
userUrl : String -> String
userUrl name =
flickrRestServices
++ "&method=flickr.people.findByUserName"
++ "&api_key="
++ apiKey
++ "&username="
++ name
++ noJsonCallback
publicPhotosUrl : String -> String
publicPhotosUrl uid =
flickrRestServices
++ "&method=flickr.people.getPublicPhotos"
++ "&api_key="
++ apiKey
++ "&user_id="
++ uid
++ noJsonCallback
photoInfoUrl : String -> String
photoInfoUrl photo =
flickrRestServices
++ "&method=flickr.photos.getInfo"
++ "&api_key="
++ apiKey
++ "&photo_id="
++ photo
++ noJsonCallback
-- Cmd to get photo description from flickr.
-- Package results as SetDescription message.
-- Save the photo id with Task.map to apply the description to the right photo
setDescriptionCmd : Photo -> Cmd Msg
setDescriptionCmd dp =
case dp.description of
Nothing ->
Task.attempt SetDescription (Task.map (\s -> ( dp.id, s )) <| Http.toTask <| Http.get (photoInfoUrl dp.id) decodePhotoDescription)
Just des ->
Cmd.none
-- Cmd to get users public photos from flickr.
-- Package results as SetPhotos message.
getPhotosCmd : String -> Cmd Msg
getPhotosCmd name =
let
req =
Http.get (userUrl name) decodeUser
userTask =
Http.toTask req
publicPhotosTask uid =
Http.toTask (Http.get (publicPhotosUrl uid) decodePhotos)
userPhotosTask =
userTask |> Task.andThen publicPhotosTask
in
Task.attempt SetPhotos userPhotosTask
init : () -> ( Model, Cmd Msg )
init _ =
( Ok
{ untitled = []
, titled = []
}
, getPhotosCmd "elmDemo" -- flickr user name
)
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SetPhotos (Ok photos) ->
( Ok
{ untitled = photos
, titled = []
}
, Cmd.batch <| List.map setDescriptionCmd photos
)
SetPhotos (Err e) ->
( Err e
, Cmd.none
)
-- Update description of the photo with matching id.
SetDescription (Ok ( photoId, desc )) ->
case model of
Ok photos ->
let
justTitled =
photos.untitled
|> List.filter (\ph -> ph.id == photoId)
|> List.map (\ph -> { ph | description = Just desc })
newTitled = photos.titled ++ justTitled
newPhotos = { photos | titled = newTitled }
in
( Ok newPhotos
, if
List.length newPhotos.titled
== List.length newPhotos.untitled
then
Cmd.none -- Could do something else here.
else
Cmd.none
)
Err e ->
( Err e
, Cmd.none
)
SetDescription (Err e) ->
( Err e
, Cmd.none
)
-- Compute a photo URL from a Photo record.
-- per: https://www.flickr.com/services/api/misc.urls.html
photoUrl : Photo -> String
photoUrl ps =
"https://farm"
++ String.fromInt ps.farm
++ ".staticflickr.com/"
++ ps.server
++ "/"
++ ps.id
++ "_"
++ ps.secret
++ "_b.jpg"
-- show an image and description if available.
viewPhoto : Photo -> Html Msg
viewPhoto ps =
div
[ HA.style "height" "20%"
, HA.style "width" "20%"
, HA.style "margin" "0"
]
[ div
[ HA.style "height" "90%"
, HA.style "width" "100%"
, HA.style "margin" "0"
]
[ Svg.svg
[ SA.version "1.1"
, SA.width "100%"
, SA.height "100%"
, SA.viewBox "-1 -0.6 2 1.2"
, SA.preserveAspectRatio "none"
]
[ Svg.image
[ SA.xlinkHref (photoUrl ps)
, SA.x "-1"
, SA.y "-0.6"
, SA.width "2"
, SA.height "1.2"
]
[]
]
]
, div
[ HA.style "height" "10%"
, HA.style "width" "100%"
, HA.style "margin" "0"
]
[ div
[ HA.style "text-align" "center" ]
[ text <| Maybe.withDefault "" ps.description ]
]
]
-- Draw an image or display the reason the image is not available.
view : Model -> Html Msg
view model =
case model of
Err s ->
text "Error: "
Ok photos ->
div []
[ div [] [ text "UNTITLED" ]
, div [] (List.map viewPhoto photos.untitled)
, div [] [ text "TITLED" ]
, div [] (List.map viewPhoto photos.titled)
]
Random thought:
Given that you will get a response back for each call you can keep track of the calls received by creating a collection of expected responses before calling batch and then removing the appropriate item from the collection each time a response is received.
At the point at which that collection is empty you've received all the responses and can fire off the next batch.
There are any number of variations on this pattern that should achieve the desired result. (And probably other patterns that would work just as well.)
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)
]
]])
I am very new to Elm and have just looked at the Elm tutorial app https://github.com/sporto/elm-tutorial-app
I was wondering how I could change page when clicking on a link specifying the Route instead of the path.
This is the routing
type Route
= HomeRoute
| NotFoundRoute
matchers : Parser (Route -> a) a
matchers =
oneOf
[ map HomeRoute top ]
parseLocation : Location -> Route
parseLocation location =
case (parsePath matchers location) of
Just route ->
route
Nothing ->
NotFoundRoute
Now in the view I would like to pick HomeRoute when clicked on the menu link
menuItems : List MenuItem
menuItems =
[ { text = "Dashboard", iconName = "dashboard", route = HomeRoute }
]
viewDrawerMenuItem : Model -> MenuItem -> Html Msg
viewDrawerMenuItem model menuItem =
Layout.link
[ Layout.onClick (NavigateTo menuItem.route)
, (Color.background <| Color.color Color.BlueGrey Color.S600) when (model.route == menuItem.route)
, Options.css "color" "rgba(255, 255, 255, 0.56)"
, Options.css "font-weight" "500"
]
[ Icon.view menuItem.iconName
[ Color.text <| Color.color Color.BlueGrey Color.S500
, Options.css "margin-right" "32px"
]
, text menuItem.text
]
I would like to implement the Msg NavigateTo but am not sure how.
[ Layout.onClick (NavigateTo menuItem.route)
I could create an update NavigateTo that takes a route as string and then have Navigation create a new Url. Like
NavigateTo path ->
(model, Navigation.newUrl path)
But instead of using a path as string I'd rather use the union type Route.
In any case you need a function for converting route to string:
pageToString : Route -> String
pageToString route =
case page of
HomeRoute -> "home"
AboutRoute -> "about"
ContactRoute -> "contact"
LoginRoute -> "login"
DashboardRoute -> "dashboard"
NotFoundRoute -> "404"
And then you can do something like:
NavigateTo : Route -> (Model, Cmd a)
NavigateTo route ->
(model, (Navigation.newUrl <| pageToString route))
You're probably not going to want to go down the Hop route as it is deprecated for Elm v0.18. Navigation has crude example of how it should be done currently.
Adding to a different comment, it may be wiser to do a String.join on a List String since you can build a Parser that can handle many slashes.
reverse : Route -> String
reverse route =
String.join "/"
<< (::) ""
<| case route of
Index ->
[ "" ]
Foo ->
[ "foo" ]
FizzBuzz x ->
[ "fizz", "buzz", x ]
(I'd leave this as a comment, but StackOverflow has that silly reputation limit)