It is difficult to export console.log with Elm.
I want to output the console log by pushing button like below and with console log in function. How shold I do?
sampleView : Html Message
sampleView =
Html.div (class "sample-card" :: Styles.sampleCard)
[ Html.div
(class "sample-card-list" :: Styles.sampleCardList)
[
Html.button
(class "sample-card-button" :: Styles.sampleCardListButton)
[
Html.text "サンプルボタン"
]
]
]
If I use Javascript, I want to do like below.
<button class="sample-card-button" onclick="btnClick();">サンプルボタン</button>
/* JavaScript 側 */
function btnClick() {
console.log("クリックされました");
});
If it's just for debugging, you can use Debug.log, but you can't use the Debug module in production code (running the compiler with --optimize will fail to compile if you have Debug usages). Using it can look like this:
sampleView : Html Message
sampleView =
Html.div (class "sample-card" :: Styles.sampleCard)
[ Html.div
(class "sample-card-list" :: Styles.sampleCardList)
[
Html.button
([class "sample-card-button", onClick CardButtonClicked] ++ Styles.sampleCardListButton)
[
Html.text "サンプルボタン"
]
]
]
update : Message -> Model -> ( Model, Cmd Message )
update msg model =
case msg of
CardButtonClicked ->
let
_ = Debug.log "クリックされました" msg
in
( model, Cmd.none )
If you want to write to the console in a production app, you'll need to use a port. To create a port, you'll need to update the module it's in to be declare a port module (just add port to the beginning of of the module declaration on the first line of the file).
port module Main exposing (..)
port consoleLog : String -> Cmd msg
update : Message -> Model -> ( Model, Cmd Message )
update msg model =
case msg of
CardButtonClicked ->
( model, consoleLog "クリックされました" )
Then you'll need to wire up some JavaScript to handle that port message:
var app = Elm.Main.init({ node: document.querySelector('main') })
app.ports.consoleLog.subscribe(function (msg) {
console.log(msg);
});
Here's an example app using a port to call console.log.
Related
I am trying to build a SPA with Elm and create three pages, that should show the content, depends on URL.
The content of these three pages are similar, for example Page.elm:
module Page.NotFound exposing (Msg(..), content)
import Html exposing (..)
import Html.Attributes exposing (..)
---- UPDATE ----
type Msg
= NotFoundMsg
content : Html Msg
content =
p [] [ text "Sorry can not find page." ]
In the Main.elm, I have the following code:
module Main exposing (Model, Msg(..), init, main, update, view)
import API.Keycloak as Keycloak exposing (..)
import Browser
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Json.Decode as Decode
import Page.Account as Account
import Page.Home as Home
import Page.NotFound as NotFound
import Route
import Url
import Url.Parser exposing ((</>), Parser, int, map, oneOf, parse, s, string)
---- MODEL ----
type alias Model =
{ key : Nav.Key
, url : Url.Url
, auth : Result String Keycloak.Struct
}
init : Decode.Value -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
( Model key url (Keycloak.validate flags), Cmd.none )
---- ROUTE ----
type Route
= Account
---- UPDATE ----
type Msg
= PageNotFound NotFound.Msg
| PageAccount Account.Msg
| PageHome Home.Msg
| LinkClicked Browser.UrlRequest
| UrlChanged Url.Url
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkClicked urlRequest ->
case urlRequest of
Browser.Internal url ->
( model, Nav.pushUrl model.key (Url.toString url) )
Browser.External href ->
( model, Nav.load href )
UrlChanged url ->
( { model | url = url }
, Cmd.none
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
---- VIEW ----
info : Html Msg
info =
header [] [ text "Header" ]
createLink : String -> Html Msg
createLink path =
a [ href ("/" ++ path) ] [ text path ]
navigation : Html Msg
navigation =
ul []
[ li [] [ createLink "home" ]
, li [] [ createLink "account" ]
]
content : Model -> Html Msg
content model =
main_ []
[ case parse Route.parser model.url of
Just path ->
matchedRoute path
Nothing ->
NotFound.content
]
matchedRoute : Route.Route -> Html Msg
matchedRoute path =
case path of
Route.Home ->
Home.content
Route.Account ->
Account.content
body : Model -> List (Html Msg)
body model =
[ info
, navigation
, content model
]
view : Model -> Browser.Document Msg
view model =
{ title = "Cockpit"
, body = body model
}
---- PROGRAM ----
main : Program Decode.Value Model Msg
main =
Browser.application
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
, onUrlChange = UrlChanged
, onUrlRequest = LinkClicked
}
The compiler complains:
-- TYPE MISMATCH -------------- /home/developer/Desktop/elm/cockpit/src/Main.elm
The 2nd branch of this `case` does not match all the previous branches:
104| [ case parse Route.parser model.url of
105| Just path ->
106| matchedRoute path
107|
108| Nothing ->
109| NotFound.content
^^^^^^^^^^^^^^^^
This `content` value is a:
Html NotFound.Msg
But all the previous branches result in:
Html Msg
Hint: All branches in a `case` must produce the same type of values. This way,
no matter which branch we take, the result is always a consistent shape. Read
<https://elm-lang.org/0.19.0/union-types> to learn how to “mix” types.
-- TYPE MISMATCH -------------- /home/developer/Desktop/elm/cockpit/src/Main.elm
Something is off with the 2nd branch of this `case` expression:
120| Account.content
^^^^^^^^^^^^^^^
This `content` value is a:
Html Account.Msg
But the type annotation on `matchedRoute` says it should be:
Html Msg
-- TYPE MISMATCH -------------- /home/developer/Desktop/elm/cockpit/src/Main.elm
Something is off with the 1st branch of this `case` expression:
117| Home.content
^^^^^^^^^^^^
This `content` value is a:
Html Home.Msg
But the type annotation on `matchedRoute` says it should be:
Html Msg
Detected errors in 1 module.
I know that the type is wrong, but do not know, how to prove it.
How can I get it to work?
I also looked at the example from https://github.com/rtfeldman/elm-spa-example/blob/master/src/Main.elm but could not figure, how does it work.
You have multiple Msg types, which is OK, but it can lead to confusion. In short: Main.Msg is not the same type as NotFound.Msg.
The function matchedRoute returns a Html Main.Msg while the function NotFound.content returns a Html NotFound.Msg; completely different types.
You're already 99% of the way there because you have a PageNotFound NotFound.Msg type constructor which produces a Main.Msg. This allows you to wrap the NotFound.Msg in a Main.Msg. It should be a matter of doing PageNotFound NotFound.content in your Nothing -> branch.
The problem is that the Msg type referred to by NotFound.content is NotFound.Msg, the Msg type referred to by Main.matchedRoute is Main.Msg, and these do not unify automatically. So when you use these in different branches of a case expression, the compiler will tell you they are different and can't be unified into a single type for the case expression to return.
So you have to convert one to the other, and the usual way to do that is to add a variant to the "outer" msg type (Main.Msg) that wraps the "inner" msg type (NotFound.Msg). Fortunately you've already added that variant as PageNotFound NotFound.Msg, so we can move on.
The next step is to do the wrapping of NotFound.Msgs in PageNotFounds. Unfortunately, we rarely get to handle values of NotFound.Msg alone, it's usually wrapped in some other type like Html or Cmd, which is trickier to deal with. Fortunately, Evan was foreknowing enough to predict this scenario and added Cmd.map and Html.map for us to use. Just like List.map and Maybe.map, Cmd.map and Html.map takes a function a -> b and uses it to convert Html as or Cmd as to Html bs or Cmd bs respectively.
So, all you really need to do here is use Html.map with PageNotFound on NotFound.content:
content : Model -> Html Msg
content model =
main_ []
[ case parse Route.parser model.url of
Just path ->
matchedRoute path
Nothing ->
NotFound.content |> Html.map PageNotFound
]
Both branches will now return Main.Msg and the compiler should be happy :)
And btw, in elm-spa-example, this is being done here
Trying to get elm ports working to maintain the session.
In index.html, the script includes the following listener:
window.addEventListener("load", function(event) {
app.ports.onSessionChange.send(localStorage.session);
}, false);
localStorage.session looks like this (and it stays there until I've logged out):
{"email":"user#fake.com","token":"eyJhbG...","user_id":1,"handle":"me"}
The definition in Ports.elm is:
port onSessionChange : (Value -> msg) -> Sub msg
This port is connected to Main.elm here (let me know if I've forgotten to include some of the definitions below):
subscriptions : Model -> Sub Msg
subscriptions model =
Ports.onSessionChange sessionChange
sessionChange : Json.Decode.Value -> Msg
sessionChange value =
let
result =
Json.Decode.decodeValue sessionDecoder value
in
case result of
Ok sess ->
SetSession (Just sess)
Err err ->
SetSession Nothing
...
type alias Session =
{ email : String
, token : String
, user_id : Int
, handle : String
}
...
import Json.Decode as Decode exposing (..)
import Json.Decode.Pipeline as Pipeline exposing (decode, required)
sessionDecoder : Decode.Decoder Session
sessionDecoder =
Pipeline.decode Session
|> Pipeline.required "email" Decode.string
|> Pipeline.required "token" Decode.string
|> Pipeline.required "user_id" Decode.int
|> Pipeline.required "handle" Decode.string
...
type Msg
= NoOp
| SetSession (Maybe Session)
...
update msg model =
case msg of
SetSession session ->
case Debug.log "session = " session of
Just sess ->
({ model | session = sess } , Cmd.none)
Nothing ->
(model, Cmd.none)
Debug.log "session" displays Nothing in the console when the page loads, so JS is talking to elm, but the decoder seems to be failing. Any ideas?
I've plugged your code into a minimal working example and everything works fine. You might want to log the value of localStorage.session from inside the javascript portion to make sure it's a valid JSON value.
My app gets init model values from localstorage through flags. I added a new key to the model and it causes an error while starting the Elm app because of the missing key ("bar") in the value passed through flags. Considering that more new keys can be added in the future, and I don't want to have to clear localstorage every time it happens, is there a way to tell Elm to assign a default value when there is a missing key in the flag?
type alias Model =
{ foo : String, bar : Int }
update : msg -> Model -> ( Model, Cmd msg )
update _ model =
model ! []
view : Model -> Html msg
view model =
text <| toString model
main : Program Flags Model msg
main =
Html.programWithFlags
{ init = init
, update = update
, view = view
, subscriptions = always Sub.none
}
HTML code
<body>
<script>
var app = Elm.Main.fullscreen({foo: "abc"})
</script>
</body>
Here is a great solution that #ilias at the Elm Slack channel kindly provided.
https://ellie-app.com/mWrNyQWYBa1/0
module Main exposing (main)
import Html exposing (Html, text)
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Extra as Decode --"elm-community/json-extra"
type alias Model =
{ foo : String, bar : Int }
flagsDecoder : Decoder Model
flagsDecoder =
Decode.map2 Model
(Decode.field "foo" Decode.string |> Decode.withDefault "hello")
(Decode.field "bar" Decode.int |> Decode.withDefault 12)
init : Decode.Value -> ( Model, Cmd msg )
init flags =
case Decode.decodeValue flagsDecoder flags of
Err _ ->
Debug.crash "gracefully handle complete failure"
Ok model ->
( model, Cmd.none )
update : msg -> Model -> ( Model, Cmd msg )
update _ model =
model ! []
view : Model -> Html msg
view model =
text <| toString model
main : Program Decode.Value Model msg
main =
Html.programWithFlags
{ init = init
, update = update
, view = view
, subscriptions = always Sub.none
}
HTML
<body>
<script>
var app = Elm.Main.fullscreen({foo: "abc"})
</script>
</body>
I want to do something very un-functional and make an HTTP request in elm without processing any kind of response. Basically something like this:
testView : Html Msg
testView =
div [] [
button [onClick TestAction] [text "Test Action"]
]
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
...
TestAction ->
( model, testActionCmd )
...
import Http
import HttpBuilder exposing (..)
...
testActionCmd : Cmd Msg
testActionCmd =
( "http://localhost:4000/fakeurl" )
|> get -- this is a side effect; unrelated to the Msg below
Cmd.none -- this is what I want to return
Is there a way to do something like this in Elm?
In short, no, you won't be able to do that (not without writing your own effect manager or using ports).
The "problem" is that the Http module allows you to create a Task which you then need to convert into a Cmd to perform the task. But to go from a Task to a Cmd you need to provide a Msg. See http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Task
So what you'll need to do is create one of those Noop messages.
In my Elm program, I'd like to initialize my model based on the query string.
For example, if the query string is ?w=3&h=5 I'd like to have:
initialModel =
{ width = 3
, height = 5
}
Is that possible to achieve this in Elm, or the only way to do this is to get the query parameters in Javascript and pass them via a port?
Elm 0.19
For elm 0.19 the below concept is the same. Both of these packages still exist but have been moved and relabeled as the official elm/url and elm/browser libraries.
Elm 0.18
This example uses evancz/url-parser and elm-lang/navigation. There are a few kinks that aren't straightforward in the documentation, but I've explained them briefly below. The example should speak for itself.
module Main exposing (..)
import Html as H exposing (..)
import Navigation exposing (Location)
import UrlParser as UP exposing ((</>), (<?>), top, parsePath, oneOf, s, stringParam, Parser)
import Maybe.Extra as MaybeExtra exposing (unwrap)
type Route
= UrlRoute (Maybe String) (Maybe String)
| NotFoundRoute
type Msg
= UrlParser Navigation.Location
type alias Model =
{ location : Route
, w : String
, h : String
}
type alias SearchParams =
{ w : Maybe String, h : Maybe String }
main =
Navigation.program UrlParser
{ init = init
, view = view
, update = update
, subscriptions = (\_ -> Sub.none)
}
init : Location -> ( Model, Cmd Msg )
init location =
let
currentPath =
parseLocation location
in
( initialModel currentPath
, Cmd.none
)
parseLocation : Location -> Route
parseLocation location =
case (parsePath matchers location) of
Just route ->
route
Nothing ->
NotFoundRoute
matchers : Parser (Route -> a) a
matchers =
UP.map UrlRoute (UP.s "index" <?> UP.stringParam "w" <?> UP.stringParam "h")
initialModel : Route -> Model
initialModel route =
{ location = route
, w = MaybeExtra.unwrap "" (\x -> Maybe.withDefault "" x.w) (parseParams route)
, h = MaybeExtra.unwrap "" (\x -> Maybe.withDefault "" x.h) (parseParams route)
}
parseParams : Route -> Maybe SearchParams
parseParams route =
case route of
UrlRoute w h ->
Just { w = w, h = h }
NotFoundRoute ->
Nothing
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
UrlParser location ->
( model
, Cmd.none
)
view : Model -> Html msg
view model =
div []
[ h1 [] [ text "URL Info" ]
, div [] [ text ("W is: " ++ model.w) ]
, div [] [ text ("H is: " ++ model.h) ]
]
The "trick" is to create another type alias to place your query params inside of. In the above example I've created the type SearchParams. After creating this type we just use an initialModel that takes in the currentPath.
From there, our model can extract the query params with Maybe.withDefault (it needs to be a Maybe type because the params may not be there). Once we have our data in the model we just print it out in the view.
Hope this helps!
There is no built-in core library way to access the URL. You can use ports and the community library jessitron/elm-param-parsing.
If you also want to set the URL, you can again use ports, or you can use the History API, for which there are bindings in TheSeamau5/elm-history.
Unfortunately jessitron/elm-param-parsing doesn't work with Elm 0.18.
Use elm-lang/navigation package:
http://package.elm-lang.org/packages/elm-lang/navigation/latest/Navigation
https://github.com/elm-lang/navigation/tree/2.1.0
especially this function:
program
: (Location -> msg)
-> { init : Location -> (model, Cmd msg), update : msg -> model -> (model, Cmd msg), view : model -> Html msg, subscriptions : model -> Sub msg }
-> Program Never model msg
In the second parameter you can see "init : Location -> (model, Cmd msg)". This should handle reading of initial URL. To complement that, first parameter is a function which gets called every time URL changes.
(I am aware it's an old question, but this link popped out when I was looking for the solution to the same problem and accepted answer didn't help)