Elm : Pass value to outgoing port from the update function - elm

I have an update function that adds an Answer to a Question
Once the question has been updated with an answer, I'd like to send it to an outgoing port, while also updating my model.
port emitQuestion : Question -> Cmd msg
update msg model =
AnswerQuestion answer ->
case model.question of
Nothing ->
( model, Cmd.none)
Just question ->
let
updatedQuestion =
{ question | answer = Just answer }
in
( { model | question = updatedQuestion } , Cmd.none)
How could I pass updatedQuestion to emitQuestion in this scenario ?

You define the outgoing port signature, but without a body, like this:
port questionUpdated : Question -> Cmd msg
(assuming that you have an Question type or alias; your question didn't specify)
Then, in the calling javascript, you define your port handler after calling Elm init:
var app = Elm.Main.init({ node: document.querySelector('main') })
app.ports.questionUpdated.subscribe(function(data) {
// your javascript for handling updated question
});
To pass that new question value to the port on update, just pass it in the second value of the return type from update:
( { model | question = updatedQuestion } , questionUpdated updatedQuestion )

Related

Receive message from an Elm process

I'm toying around with Elm processes in order to learn more about how they work. In parts of this, I'm trying to implement a timer.
I bumped into an obstacle, however: I can't find a way to access the result of a process' task in the rest of the code.
For a second, I hoped that if I make the task resolve with a Cmd, the Elm runtime would be kind enough to perform that effect for me, but that was a naive idea:
type Msg
= Spawned Process.Id
| TimeIsUp
init _ =
( Nothing
, Task.perform Spawned (Process.spawn backgroundTask)
)
backgroundTask : Task.Task y (Platform.Cmd.Cmd Msg)
backgroundTask =
Process.sleep 1000
-- pathetic attempt to send a Msg starts here
|> Task.map ( always
<| Task.perform (always TimeIsUp)
<| Task.succeed ()
)
-- and ends here
|> Task.map (Debug.log "Timer finished") -- logs "Timer finished: <internals>"
update msg state =
case msg of
Spawned id ->
(Just id, Cmd.none)
TimeIsUp ->
(Nothing, Cmd.none)
view state =
case state of
Just id ->
text "Running"
Nothing ->
text "Time is up"
The docs say
there is no public API for processes to communicate with each other.
I'm not sure if that implies that a process can't cummunicate with the rest of the app.
Is there any way to have update function receive a TimeIsUp once the process exits?
There is one way but it requires a port of hell:
make a fake HTTP request from the process,
then intercept it via JavaScript
and pass it back to Elm.
port ofHell : (() -> msg) -> Sub msg
subscriptions _ =
ofHell (always TimeIsUp)
backgroundTask : Task.Task y (Http.Response String)
backgroundTask =
Process.sleep 1000
-- nasty hack starts here
|> Task.andThen ( always
<| Http.task { method = "EVIL"
, headers = []
, url = ""
, body = Http.emptyBody
, resolver = Http.stringResolver (always Ok "")
, timeout = Nothing
}
)
Under the hood, Http.task invokes new XMLHttpRequest(), so we can intercept it by redefining that constructor.
<script src="elm-app.js"></script>
<div id=hack></div>
<script>
var app = Elm.Hack.init({
node: document.getElementById('hack')
})
var orig = window.XMLHttpRequest
window.XMLHttpRequest = function () {
var req = new orig()
var orig = req.open
req.open = function (method) {
if (method == 'EVIL') {
app.ports.ofHell.send(null)
}
return orig.open.apply(this, arguments)
}
return req
}
</script>
The solution is not production ready, but it does let you continue playing around with Elm processes.
Elm Processes aren't a fully fledged API at the moment. It's not possible to do what you want with the Process library on its own.
See the notes in the docs for Process.spawn:
Note: This creates a relatively restricted kind of Process because it cannot receive any messages. More flexibility for user-defined processes will come in a later release!
and the whole Future Plans section, eg.:
Right now, this library is pretty sparse. For example, there is no public API for processes to communicate with each other.

Set a variable messagingSenderId in service worker (Firebase) [duplicate]

This question already has answers here:
Passing state info into a service worker before `install`
(2 answers)
Closed 5 years ago.
I would like to set a variable messagingSenderId value in my service worker, not an hardcoded one. Is it possible?
I register my service worker like this:
navigator.serviceWorker.register( 'firebase-messaging-sw.js' )
.then( function( registration ) {
messaging.useServiceWorker( registration );
});
And in my firebase-messaging-sw.js, I initialize firebase like this
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js' );
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js' );
firebase.initializeApp({
'messagingSenderId': 'my-id' // <- I want this to be variable
});
The problem is I can't find how to pass data to my service worker file. Any idea?
Thanks
As mentionned, Passing state info into a service worker before 'install' answered the question. Thanks.
Here is the answer for this use case:
You need to pass the variable in the URL like so:
var myId = 'write-your-messaging-sender-id-here';
navigator.serviceWorker.register( 'firebase-messaging-sw.js?messagingSenderId=' + myId )
.then( function( registration ) {
messaging.useServiceWorker( registration );
});
And then, in firebase service worker (firebase-messaging-sw.js), you can get this variable like so:
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js' );
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js' );
var myId = new URL(location).searchParams.get('messagingSenderId');
firebase.initializeApp({
'messagingSenderId': myId
});
This works. But URL.searchParams is a very new tool. It is less compatible than Firebase itself.
URL.searchParams: Chrome 51+, Firefox: 52+, Opera: unknown
Firebase: Chrome 50+, Firefox 44+, Opera 37+
So instead of:
var myId = new URL(location).searchParams.get('messagingSenderId');
I suggest using:
var myId = get_sw_url_parameters( 'messagingSenderId' );
function get_sw_url_parameters( param ) {
var vars = {};
self.location.href.replace( self.location.hash, '' ).replace(
/[?&]+([^=&]+)=?([^&]*)?/gi, // regexp
function( m, key, value ) { // callback
vars[key] = value !== undefined ? value : '';
}
);
if( param ) {
return vars[param] ? vars[param] : null;
}
return vars;
}

HTTP request without request in Elm 0.18

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.

Elm seed for Random.initialSeed - prefer current time [duplicate]

This question already has answers here:
elm generate random number
(2 answers)
Closed 7 years ago.
What's a simple way to do this?
The documentation for Random.initialSeed says:
"A good way to get an unexpected seed is to use the current time."
http://package.elm-lang.org/packages/elm-lang/core/2.1.0/Random#initialSeed
After a ton of reading, I can only find "solutions" that are well beyond my understanding of Elm and Functional Programming. They also don't seem to be solutions to this problem.
I'm currently hardcoding:
Random.initialSeed 314
If you use a library, please include the name used to get it from elm package. I've seen a solution that says use Native.now but I can't figure out how to get that one.
stackoverflow is suggesting this one but I can't understand how to apply it to my usecase Elm Current Date
You can try case nelson's answer from How do I get the current time in Elm?
From elm repl:
> import Now
> import Random
> Now.loadTime |> round -- get current time in Int
1455406828183 : Int
> Now.loadTime |> round |> Random.initialSeed -- get the Seed
Seed { state = State 1560073230 678, next = <function>, split = <function>, range = <function> }
: Random.Seed
I also have the code on my repo here.
Note: don't forget "native-modules": true in elm-package.json.
Edit:
to try the code,
git clone https://github.com/prt2121/elm-backup.git
cd elm-backup/now
elm make Now.elm
add "native-modules": true in elm-package.json
elm repl
The simplest way I can think of is to use the Elm Architecture and Effects.tick mechanism to initialise the seed with a time value.
Here is an example of how this works:
import Html exposing (..)
import Html.Events exposing (onClick)
import Random exposing (Seed, generate, int, initialSeed)
import Time exposing (Time)
import Effects exposing (Effects, Never)
import Task exposing (Task)
import StartApp
type alias Model = { seed : Seed, value : Int}
type Action = Init Time | Generate
init : (Model, Effects Action)
init = (Model (initialSeed 42) 0, Effects.tick Init)
modelFromSeed : Seed -> (Model, Effects Action)
modelFromSeed seed =
let
(value', seed') = generate (int 1 1000) seed
in
(Model seed' value', Effects.none)
update : Action -> Model -> (Model, Effects Action)
update action model =
case action of
Init time ->
modelFromSeed (initialSeed (round time))
Generate ->
modelFromSeed model.seed
view : Signal.Address Action -> Model -> Html
view address model =
div []
[ text ("Current value: " ++ (toString model.value))
, br [] []
, button [onClick address Generate] [text "New Value"]
]
app : StartApp.App Model
app = StartApp.start
{ init = init
, update = update
, view = view
, inputs = []
}
main : Signal Html
main = app.html
port tasks : Signal (Task Never ())
port tasks = app.tasks

How do I make two independent actions inside one function?

How can I make two independent action inside one function in Elm? Is there any pattern or explicit function?
Generally speaking I'm not sure how to implement ajax data loading inside Elm Architecture.
For example I'd like to make Http.get and return modified argument function like this
fetchCustomers model =
Http.get parseCustomers "/customers" `andThen` setCustomers
{ model | fetchingCustomers <- True }
TL;DR
You do so by returning both in a tuple. Then you split the signal from your update in foldp, put the model part in the view function and put the tasks in a port to execute. This is alluded to in the Architecture post at the end under One Last Pattern.
Longer answer
Since you link to the Elm Architecture, let me link to that too, but in particular the last part: One Last Pattern.
What you want to do here is part of the "update" of your program, where you not only update your model but also do something else on the side. Therefore you do not just return the new model, but also the extra thing you want to do (in this case an Http request):
fetchCustomers model =
( { model | fetchingCustomers <- True }
, Http.get parseCustomers "/customers" `andThen` setCustomers
)
Instead of using StartApp like the architecture page does, you can paste in the start function from the package. Now you have access to the mailbox where the actions are coming from, so you can pass it to your update so you can send your Http results there too. And you can split the tuple that you're returning from the update function to actually execute the tasks:
start app =
let
actions =
Signal.mailbox Nothing
address =
Signal.forwardTo actions.address Just
model =
Signal.foldp
(\(Just action) (model,_) -> app.update actions action model)
-- ignore task: ^ ^^^ ^^^^^^^^:add mailbox
app.model
actions.signal
in
(Signal.map (fst >> app.view address) model, Signal.map snd model)
-- ^ ^^^^^^^ :split model: ^^^^^^^^^^^^^^^^^^^^^^^
fetchCustomers actions model =
( { model | fetchingCustomers <- True }
, Http.get parseCustomers "/customers"
`andThen` (SetCustomers >> Signal.send actions.address)
-- use mailbox to send Http results as an input action again
)
-- larger update function you probably have
update actions action model = case action of
-- ...
SetCustomers cust -> setCustomers cust
-- ...
-- fetchCustomers actions model
(output, tasks) = start { model: model, view: view, update: update }
-- task execution:
port httpGets = tasks
-- output your view
main = output
You can find more examples of doing Http on the website under "tasks".