How do I chain a Task and Nav.pushUrl in Elm? - task

In an Elm app I have a Msg handler LinkedClicked that returns a Cmd Msg:
I want to perform two action for LinkedClicked: 1. to update the DOM by updating my Model and 2. reset the page to display from the top by calling Dom.setViewport
Is it possible (and desirable) to do away with ChangeUrl Msg and instead do Nav.pushUrl in my LinkedClicked handler. My approach by firing off a Msg within a Msg feels brittle.
jump_to_url : Url.Url -> Cmd Msg
jump_to_url url =
Task.perform ( \_ -> ChangeUrl url) (Dom.setViewport 0 0)
on_birthdate_url : Model -> Url.Url -> ( Model, Cmd Msg )
on_birthdate_url model url =
( { model
| ...
}
, jump_to_url url
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkedClicked urlRequest ->
case urlRequest of
Browser.Internal url ->
on_birthdate_url model url
...
ChangeUrl url ->
( model
, Nav.pushUrl model.key (Url.toString url)
)

After reading everyone's comments I came to these conclusions:
I don't want the refreshing of the page url and the resetting of the viewport to happen concurrently. The former needs to happen before the latter otherwise the viewport reset can happen too early.
renaming the function jump_to_url to new_top_page encapsulates both the url update and viewport update.
I'm still wondering if encapsulating two actions into one function is the elm way.
new_top_page : Url.Url -> Cmd Msg
new_top_page url =
Task.perform ( \_ -> ChangeUrl url) (Dom.setViewport 0 0)
on_birthdate_url : Model -> Url.Url -> ( Model, Cmd Msg )
on_birthdate_url model url =
( { model
| ...
}
, new_top_page url
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkedClicked urlRequest ->
case urlRequest of
Browser.Internal url ->
on_birthdate_url model url
...
ChangeUrl url ->
( model
, Nav.pushUrl model.key (Url.toString url)
)

Related

How to cancel a `ResponseSocket` server?

module Main
open System
open System.Threading
open System.Threading.Tasks
open NetMQ
open NetMQ.Sockets
let uri = "ipc://hello-world"
let f (token : CancellationToken) =
use server = new ResponseSocket()
use poller = new NetMQPoller()
poller.Add(server)
printfn "Server is binding to: %s" uri
server.Bind(uri)
printfn <| "Done binding."
use __ = server.ReceiveReady.Subscribe(fun x ->
if token.CanBeCanceled then poller.Stop()
)
use __ = server.SendReady.Subscribe(fun x ->
if token.CanBeCanceled then poller.Stop()
)
poller.Run()
printfn "Server closing."
server.Unbind(uri)
let src = new CancellationTokenSource()
let token = src.Token
let task = Task.Run((fun () -> f token), token)
src.CancelAfter(100)
task.Wait() // Does not trigger.
My failed attempt looks something like this. The problem is that the poller will only check the cancellation token if it gets or sends a message. I guess one way to do it would be to send a special cancel message from the client rather than these tokens, but that would not work if the server gets into a send state.
What would be a reliable way of closing the server in NetMQ?

Downloading file from POST request with Elm

I'm in the position that I have an HTTP POST endpoint /render that returns a PDF document, and would like to present a button/link to the user that will cause this document to be downloaded and saved to a file without navigating away from my Elm app.
Ideally the POST will accept a text/plain body with a custom format, but I could rework the endpoint to accept multipart/form-data or application/x-www-form-urlencoded.
I can download the raw data to the Elm app successfully as follows, but I'm at a loss for how to save the file to disk.
import Http
render : String -> Http.Request String
render body =
Http.request
{ method = "POST"
, headers = []
, url = "/render"
, body = Http.stringBody "text/plain" body
, expect = expectString
, timeout = Nothing
, withCredentials = False
}
I did it using expectBytes rather then expectString
so my code is
import Bytes exposing (Bytes)
import File.Download as Download
Msg = .. | FormUploaded (Result Http.Error Bytes)
Http.post
{ url = "/passports"
, body =
Http.multipartBody...
, expect = Http.expectBytesResponse FormUploaded (resolve Ok)
}
downloadPdf : Bytes -> Cmd msg
downloadPdf pdfContent =
Download.bytes "form.pdf" "application/pdf" pdfContent
update : Msg -> Model -> ( Model, Cmd Msg )
update model =
...
FormUploaded (Ok response) ->
( model, downloadPdf response )
FormUploaded (Err err) ->
( model, Cmd.none )
-- this helper function copied from https://github.com/elm/http/blob/2.0.0/src/Http.elm#L514-L521
resolve : (body -> Result String a) -> Http.Response body -> Result Http.Error a
resolve toResult response =
case response of
BadUrl_ url ->
Err (BadUrl url)
Timeout_ ->
Err Timeout
NetworkError_ ->
Err NetworkError
BadStatus_ metadata _ ->
Err (BadStatus metadata.statusCode)
GoodStatus_ _ body ->
Result.mapError BadBody (toResult body)
it's not ideal but works
PS: I got help from Elm Slack Channel https://elmlang.slack.com/

How do I redirect from an exact url in golang?

Whenever a request is made from localhost:9000/ I want to redirect the user to localhost:9000/#/trade/gem/GEM . The problem I am getting is that I get infinite redirects because "/" preceeds every url. How do I make it so that the user is only redirected if they visit the exact url of localhost:9000/ ? My code is below:
var newUrl string = "/#/trade/gem/GEM"
func handleRedirect(rw http.ResponseWriter, req *http.Request) {
http.Redirect(rw, req, newUrl, http.StatusSeeOther)
}
func main() {
http.Handle("/#/trade/", fs)
http.HandleFunc("/", handleRedirect) //This is the exact url I want to redirect from
http.ListenAndServe(":9000", nil)
}
When the / is registered then all the url will redirect to this until the other pattern is not registered.To solve this and handling only specific patterns you can write a custom handler and you can implement a logic where it redirects to the specific url whenever the localhost:9000/ is opened.
package main
import (
"fmt"
"net/http"
)
type Handler struct{}
var NewUrl string = "/trade/gem/GEM"
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
uri := r.URL.Path
if uri == "/" {
http.Redirect(w, r, NewUrl, http.StatusSeeOther)
}
fmt.Fprintf(w, uri)
return
}
func main() {
handler := new(Handler)
http.ListenAndServe(":9000", handler)
}
For more details check http.handler
Note: You can not use # in the uri.

Add user to actioncable channel without page refresh.

When I'm referring to actioncable channels, i am referring to this:
.
After creating the chatroom + chatroom_users, the user has to refresh the page to connect to that specific chatroom_id channel. Is it possible to connect to that channel without a reloading the page?
It is possible to create connection upon a custom action in your view (instead of page refresh). Please have a look at the code below,
createconusmer = (send_params) ->
App.chatbot = App.cable.subscriptions.create { channel: "ChatbotChannel" , auth_token: send_params , url: string },
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
console.log(data)
speak: (data, responder, payload) ->
#perform 'speak' , message: data , responder: responder , payload: payload
Now you can define custom function in your coffee file as,
nameclick = (value) ->
createconusmer value
window["nameclick"] = nameclick
Now in your view, you can use function nameclick to create a new streaming. Also, I am adding my bit of code to make sure they are unique or not, so to avoid addition of repititive connections.
connections = []
addConnection = (id) ->
connections.push(id)
removeConnection = (id) ->
index = connections.indexOf(id)
connections.splice(index, 1) if index > -1
connectedTo = (id) ->
connections.indexOf(id) > -1
nameclick = (value) ->
if connectedTo(value) == false
addConnection(value)
createconusmer value

Using node modules from fable

I'm trying to import a node module in my fable code. Being new to fable I did expect so problems and understanding the import flow seems to be one of those. I have the below code which compiles fine but fails run time with Cannot read property 'request' of undefined on the line of the printfn statement
module Session =
let inline f (f: 'a->'b->'c->'d) = Func<_,_,_,_> f
[<Import("default","request")>]
type Http =
abstract request : string -> System.Func<obj,obj,obj,unit> -> unit
let http : Http = failwith "js only"
let start () =
http.request "http://dr.dk" (ff (fun error response body ->
printfn "%A" body
))
do
start()
I was able to get your example working in the Fable REPL:
open System
open Fable
open Fable.Core
open Fable.Core.JS
open Fable.Core.JsInterop
type RequestCallback = Func<obj, obj, obj, unit>
type Request = Func<string, RequestCallback, unit>
[<ImportDefault("request")>]
let request : Request = jsNative
let start () =
request.Invoke("http://dr.dk", (fun error response body ->
console.log("error", error)
console.log("response", response)
console.log("body", body)
))
start ()
And here is the JavaScript that it generated:
import request from "request";
import { some } from "fable-library/Option.js";
export function start() {
request("http://dr.dk", (error, response, body) => {
console.log(some("error"), error);
console.log(some("response"), response);
console.log(some("body"), body);
});
}
start();
Note that there are bindings for many modules already. For this particular task, I would suggest using Fable.Fetch. If you want a library that works in the browser and .NET, try Fable.SimpleHttp.

Resources