Local Storage or other data persistence in elm - elm

I am just beginning to look at Elm with the idea of building a simple web application with it. My idea would require to persist some user data in the browser.
Is there a way to handle data persistence directly with Elm? For example in browser session or even local storage? Or should I use ports to do it with JavaScript?

I would suggest to use localStorage. There is no official support for it in the latest elm (by this time it is 0.17), but you can simply do it via ports. This is a working example (based on an example from the offical docs) of using localStorage via ports for elm 0.17
port module Main exposing (..)
import Html exposing (Html, button, div, text, br)
import Html.App as App
import Html.Events exposing (onClick)
import String exposing (toInt)
main : Program Never
main = App.program
{
init = init
, view = view
, update = update
, subscriptions = subscriptions
}
type alias Model = Int
init : (Model, Cmd Msg)
init = (0, Cmd.none)
subscriptions : Model -> Sub Msg
subscriptions model = load Load
type Msg = Increment | Decrement | Save | Doload | Load String
port save : String -> Cmd msg
port load : (String -> msg) -> Sub msg
port doload : () -> Cmd msg
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Increment ->
( model + 1, Cmd.none )
Decrement ->
( model - 1, Cmd.none )
Save ->
( model, save (toString model) )
Doload ->
( model, doload () )
Load value ->
case toInt value of
Err msg ->
( 0, Cmd.none )
Ok val ->
( val, Cmd.none )
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick Increment ] [ text "+" ]
, br [] []
, button [ onClick Save ] [ text "save" ]
, br [] []
, button [ onClick Doload ] [ text "load" ]
]
And the index.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="js/elm.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
var storageKey = "token";
var app = Elm.Main.fullscreen();
app.ports.save.subscribe(function(value) {
localStorage.setItem(storageKey, value);
});
app.ports.doload.subscribe(function() {
app.ports.load.send(localStorage.getItem(storageKey));
});
</script>
</html>
Now, by pressing buttons +/- you change the int value. When you press "save" button, the app stores the actual value into localStorage (by "token" key). Then try to refresh the page and press "load" button - it takes the value back from localStorage (you should see the HTML text control actualized with the restored value).

The official answer is "use ports" (for 0.18 - 0.19.1) while awaiting re-publication of Elm's official local storage library: https://github.com/elm-lang/persistent-cache

You could have a look at TheSeamau5's elm-storage. It makes it possible to store data in local storage.

Related

Sending messages between modules

Can't quite figure out how to go about sending messages between modules when working with reusable components.
I have an expanding text area that I'd like to use on a number of different sections on a site. The Text Area accepts a portion of HTML that makes up the user actions. Typically handling submit, cancel, upload icons, etc..
Tried to write up a quick example of what I'm talking about without throwing a ton of code on here. So essentially, I'd like to just plug and play peices of HTML that are already attached.
I'm assuming CancelNote is getting fired as a TextArea msg, so it never sees a Cancel Note msg. Not sure how I would use Html.map here (or even if I would).....feel like plug and play method is probably a bad approach, but not sure how else I could achieve decent reusability .
SEPERATE MODULE
update model msg =
case msg of
CancelText ->
( { model | note = (Just "") }
, Cmd.none
)
view: stuff
view stuff =
......
TextArea.view
(button [ Html.Events.onClick CancelText] [])
TEXT AREA MODULE
view : Html.Html msg -> Html msg
view actionHtml =
div [ class "extended_text_area_container" ] [
textarea [] [
]
, actionHtml
]
Messages are just values like any other. You can pass them around directly:
-- SEPERATE MODULE
view: stuff
view stuff =
......
TextArea.view CancelText
-- TEXT AREA MODULE
view : msg -> Html msg
view msg =
div [ class "extended_text_area_container" ]
[ textarea [] []
, button [ onClick msg ] []
]
Edit: If you need to also maintain internal state, just use another message to tell the parent to update the state:
-- Main module
type msg =
...
SetTextAreaState TextArea.state
update model msg =
case msg of
...
SetTextAreaState state ->
{ model | textAreaState = state }
view : Model -> Html msg
TextArea.view SetTextAreaState model.textAreaState
-- TextArea module
type State =
...
type Msg =
Clicked
update : State -> Msg -> State
update state msg =
case msg of
Clicked ->
{ state | clicked = True }
view : (State -> msg) -> State -> Html msg
view toMsg state =
let
updateAndWrap msg =
toMsg (update state msg)
in
div [ class "extended_text_area_container" ]
[ textarea [] []
, button [ onClick (updateAndWrap Clicked) ] []
]
Here, instead of passing a msg to onClick directly in TextArea.view, call a function that updates the state and then wraps it in the msg constructor passed in from the parent, which will produce a message of a type that we don't know anything about.
Also, while I use an internal Msg type and update function similarly to the overall Elm architecture, that is in no way mandatory. It's just a nice way of doing it since it's familiar and scales well.

Elm no data except isTrusted in JSON events

I wrote a simple program based on time example, to test what data are in events. It decodes JSON to value then encodes it back to JSON, then show it in SVG text element. And the only thing I get is {"isTrusted":true}.
Why that happens? How do I get another data of event? I'm using Firefox 49 and online compiler:
import Html exposing (Html)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Svg.Events exposing(on)
import Json.Decode as Json
import Json.Encode exposing (encode)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model = String
init : (Model, Cmd Msg)
init =
("No event", Cmd.none)
-- UPDATE
type Msg
= Event String
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Event event ->
(event, Cmd.none)
subscriptions model = Sub.none
stringifyEvent: Json.Encode.Value -> Msg
stringifyEvent x =
Event (encode 2 x)
-- VIEW
view : Model -> Svg Msg
view model =
svg [ viewBox "0 0 300 300", width "300px", on "mousedown" (Json.map stringifyEvent Json.value) ] [
text_ [x "0", y "30"] [text model]
]
When I try in console
svgElement.addEventListener('click', function(e) {console.log(e)})
It works with all the attributes.
I do not know a way to achieve your goal.
But I can give you an answer why it does the way you described.
If you look at the source code, you'll find that Elm runtime uses
JSON.stringify() for converting Value to String.
And guess what...
svgElement.addEventListener('click', function(e) {console.log(JSON.stringify(e))})
will give you {"isTrusted":true} when you click...

How can I add event handlers to the body element in Elm?

I'm trying with the Html.App.beginnerProgram and I want to add event handlers (onKeyDown, etc.) to the <body> element.
Unfortunately everything I put in view becomes the children of <body>. Returning Html.body from view doesn't do the trick. This code:
main = beginnerProgram { model = 0, view = view, update = update }
view model = body [] []
update _ model = model
will generate:
<html>
<head>...</head>
<body>
<body></body>
</body>
</html>
So, how do I get the control of the <body> element?
Since Elm renders as a descendant of <body/>, you cannot bind event handling to it in the normal Elm way (e.g. button [ onClick SomeMsg ] []). Instead you'll have to use ports. And because you're using ports and subscriptions, you will need to use Html.App.program rather than beginnerProgram.
You'll need a port function inside your port module:
port bodyKeyPress : (String -> msg) -> Sub msg
And then you'll be able to send key presses to that port from javascript:
app.ports.bodyKeyPress.send(...);
Here's a full example:
Main.elm
port module Main exposing (main)
import Html.App as App
import Html exposing (..)
main = App.program
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
type alias Model = { message : String }
type Msg = BodyKeyPress String
init = { message = "Press some keys: " } ! []
update msg model =
case msg of
BodyKeyPress c ->
{ model | message = model.message ++ c } ! []
view model = text model.message
subscriptions _ =
bodyKeyPress BodyKeyPress
port bodyKeyPress : (String -> msg) -> Sub msg
And the html behind the scenes (assuming you built using elm make Main.elm --output=Main.js):
<html>
<body>
<script src="Main.js"></script>
<script>
var app = Elm.Main.fullscreen();
document.body.onkeypress = function(e) {
app.ports.bodyKeyPress.send(String.fromCharCode(e.keyCode));
};
</script>
</body>
</html>
You will need to create and handle a port function for every body event you want to send.

how to implement loading image when any http request occure

I have implemented the HTTP service request using elm architecture.but i am also want to implement any loading gif image so that user will know that something is happen in the back ground.
can any please help me to implement to this implementation.
You can add a property in your model, whether it is loading or not.
Then you can let your view reflect the status by show spinner or text, or whatever.
In case of the example at http example, you can modify
following code, for example by adding iswaiting property in the Model ...
In this example, FetchSucceed message gets fired when ajax call is complete.
type alias Model =
{ topic : String
, gifUrl : String
, iswaiting : Bool
}
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
MorePlease ->
({ model | iswaiting = True }, getRandomGif model.topic)
FetchSucceed newUrl ->
(Model model.topic newUrl False, Cmd.none)
FetchFail _ ->
(model, Cmd.none)
view : Model -> Html Msg
view model =
div []
[ h2 [] [text model.topic]
, text <| if model.iswaiting then "waiting" else ""
, button [ onClick MorePlease ] [ text "More Please!" ]
, br [] []
, img [src model.gifUrl] []
]
You'll have to adjust other parts as well.
But, I hope you can see what is going on.
You can add a loading gif image on body and make it appear on top of the page by z-index and after http request is completed then delete that image from body.

Elm modal dialog box

How do I integrate this sweet dialog into my Elm code. I've included the JS and CSS in my index.html. How do I call this JavaScript function in my update function?
update : Action -> Model -> (Model, Effects Action)
update action model =
case action of
Submit ->
let valid = length model.name > 0
in
if valid
then (model, swal({"title": "Invalid name"}))
else (model, swal({"title": "Valid name"}))
It's tricky to rig up a full example without all the view code to check with, but I'm hoping this simpler version helps! Cribbed to some extend from this repo...
index.html
<!DOCTYPE html>
<html>
<head>
<script src="Main.js" type="text/javascript"></script>
<script type="text/javascript"
src="bower_components/sweetalert/dist/sweetalert.min.js"
></script>
<link rel="stylesheet"
href="bower_components/sweetalert/dist/sweetalert.css"
/>
</head>
<body>
<div id="main"></div>
<script type="text/javascript">
var so = Elm.embed(Elm.Main, document.getElementById('main'));
so.ports.callSwal.subscribe(doAlert);
function doAlert(space) {
if (space) swal("Hey, a spacebar!");
}
</script>
</body>
</html>
modal.elm
import Graphics.Element
import Keyboard
port callSwal : Signal Bool
port callSwal =
Keyboard.space
main = Graphics.Element.show "How about pressing a spacebar?"
stuff I did to make it work
$ bower install sweetalert
$ elm-make modal.elm --output=Main.js
Note
Embed the Elm application, to give js an object to access ("so" here)
In js, subscribe to a named port and give it a callback function.
create the port in elm. This one takes a simple Bool, but I guess yours will want at least a String.
A better answer
The trick turns out to be noticing that startApp has a mailbox baked into it, which you can access through app.model.
The alert message becomes part of your model. If it's an empty string, we interpret that as meaning "don't trigger any alerts".
NB. I've no idea why update needs to return a tuple with an Events Action in it. That's not been used here..
Here's an example of it all put together:
var so = Elm.embed(Elm.Main, document.getElementById('main'));
so.ports.alert.subscribe(function(text) {
if (text.length > 0) swal(text);
});
import StartApp
import Task exposing (Task)
import Effects exposing (Effects, Never)
import Html exposing (Html, div, input, button, text)
import Html.Events exposing (on, onClick, targetValue)
import String exposing (length)
app :
{ html : Signal Html
, model : Signal Model
, tasks : Signal (Task Never ())
}
app =
StartApp.start
{ init = init
, update = update
, view = view
, inputs = []
}
port alert : Signal String
port alert =
Signal.map (\n -> n.alert) app.model
main : Signal Html
main =
app.html
-- MODEL
type alias Model =
{ name : String
, alert : String
}
init : (Model, Effects Action)
init =
( { name = ""
, alert = ""
}
, Effects.none
)
-- UPDATE
type Action
= Submit
| TextEntry String
update : Action -> Model -> (Model, Effects Action)
update action model =
case action of
Submit ->
if length model.name > 0 then
({ model | alert = "Valid name" }, Effects.none)
else
({ model | alert = "Invalid name" }, Effects.none)
TextEntry txt ->
({ model | name = txt, alert = "" }, Effects.none)
-- VIEW
view : Signal.Address Action -> Model -> Html
view address model =
let f = (\str -> Signal.message address (TextEntry str)) in
div
[]
[ input
[ on "input" targetValue f ]
[]
, button
[ onClick address Submit ]
[ text "Submit" ]
]