Elm Http Request on Init - elm

I'm pretty new to Elm, but slowly getting familiar with it. I'm doing some simple Http Requests, which are successful in every aspect (I get the data to display, etc.)
For the moment I can only get my fetchData to trigger onClick, I would like to initialize this on init, but I'm having trouble wrapping my head around this.
....here is my fetchData function:
fetchData : Cmd Msg
fetchData =
Http.get repoInfoListDecoder "http://example/api"
|> Task.mapError toString
|> Task.perform ErrorOccurred DataFetched
Which is currently trigger in the view by a onClick event:
.....
, button [ onClick FetchData ] [ text "Load Data" ]
.....
I want to avoid requesting the data by a button, instead I want the data to load when the app is initialized. Here is my init:
init =
let
model =
{ message = "Welcome", repos = [] }
in
model ! []
And my update (kind of stripped...for example's sakes):
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
NoOp ->
model ! []
...........
FetchData ->
{ model | message="hi" } ! [fetchData]
...........
DataFetched repos ->
{ model | repos = repos, message = "The data has been fetched!" } ! []
Does this makes sense? I'm just having difficulty initializing fetchData when the app loads, so I can display my data without having to hit a button to fetch it.
Thanks in advance!

When you use Html.program, your init function returns the initial data for your Model and Command to be executed.
Commands are side-effects, like HTTP requests.
Try changing init so it runs a Command right away:
init: (Model, Cmd Msg)
init =
let
model =
{ message = "Welcome", repos = [] }
in
model ! [ fetchData ]

Here is the type signature of Html.App.program.
init accepts Cmd, you simply give your Cmd here.
program
: { init : (model, Cmd msg), update : msg -> model -> (model, Cmd msg), subscriptions : model -> Sub msg, view : model -> Html msg }
-> Program Never

Another way is to use Task, which hasn't been mentioned yet. You could try something like
initWithFlags : Flags -> ( Model, Cmd Msg)
initWithFlags flags =
( { user = flags.user }, (Task.perform fetchData) )

Related

Intercepting 401 for any response in Elm

I'm building an application in Elm where most API calls are protected; i.e. the user needs to be logged in for the API call to work. If the user is not logged in, they will receive a 401 Unauthorized response. I want the application to redirect to the login page if any response is a 401.
Currently, I only have this redirect set up for a single API call. Here is a stripped-down version of the code to give an idea of how it's set up:
-- Util/Api.elm
type alias Data data =
{ data : data
}
-- Resources/Expense.elm
getExpenses : (Progress (Api.Data (List Expense)) -> msg) -> Sub msg
getExpenses msg =
(dataDecoder expenseListDecoder)
|> Http.get expensesEndpoint
|> Progress.track expensesEndpoint msg
-- Main/Msg.elm
type Msg
= ExpenseListMsg ExpenseListMsg
| RedirectToLogin
-- Main/Update.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ExpenseListMsg msg ->
ExpenseList.Update.update msg model
GoTo path ->
model ! [ Navigation.newUrl path ]
RedirectToLogin ->
model ! [ Navigation.load "path/to/login" ]
-- ExpenseList/Msg.elm
type ExpenseListMsg
= GetExpensesProgress (Progress (Api.Data (List Expense)))
| SetLoading
-- ExpenseList/Update.elm
update : ExpenseListMsg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SetLoading ->
{ model | expenses = setExpensesLoading model.expenses } ! []
GetExpensesProgress (Done { data }) ->
{ model | expenses = addExpenses model.expenses data } ! []
GetExpensesProgress (Fail (BadStatus { status })) ->
case status.code of
401 ->
model ! [ msgToCmd RedirectToLogin ]
_ ->
model ! []
GetExpensesProgress (Fail error) ->
model ! []
GetExpensesProgress progress ->
{ model | expenses = setExpensesLoading model.expenses } ! []
Essentially, I want to move the logic around 401 responses from ExpenseList/Update.elm up to Main/Update.elm so that I can use it for any request I want.
I attempted a number of things, but nothing quite worked with Elm's type system. For example, one thing I wanted to do was try to do a nested pattern match with missing specificity in the middle, e.g.:
-- Main/Update.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ApiCall (messageType (msg (Fail (BadStatus { status })))) ->
case status of ->
. . .
. . .
I was hoping something like this would work and would match a message that looked like: ApiCall (ExpenseListMsg (GetExpensesProgress (Fail (BadStatus)))). Unfortunately, it's not proper Elm syntax, so my code didn't compile.
How can I write something that will allow me to mark an API call as protected and catch 401 errors at the top level in Main.Update.update?
Currently, the API call is encapsulated by the ExpenseList/Update module. This encapsulation is what makes the API call results unavailable to the Main module. The interaction works like this: Main -> FeatureModule -> API
Because the API is what provides the information needed to determine whether the app should redirect to the login page, and you want the Main module to perform the redirect, the Main module needs access to the API. Hence, the encapsulation needs to go. Instead, you can:
Have an API module which provides the low-level API functionality by producing Tasks. Unlike producing Cmds, this allows the caller, such as the Main module to decide how to handle the result of the Task, which can be acquired by converting the Task to a Cmd and giving it to the Elm runtime for execution.
Have the ExpenseList.Update module use the API module to create Tasks.
With this arrangement:
The Main module sends high-level commands to a feature module, which then uses the API module to produce the low-level instructions, which are then provided to the Main module.
The Main module doesn't need to care what those low-level instructions are, it simply converts the Task to a Cmd and waits for the result.
When the result comes back it's in a low-level format (ex. Success/Fail). At this point the Main module can jump in and handle the redirect on a 401 error. Otherwise, it can pass the result to a feature module so it may handle the result.

How do I access the window object from within Elm?

Specifically, I am trying to initialize Elm with an already defined parameter. Something like:
initialModel =
{ me = window.user
, todos = window.todos
}
All I can find is how to get window dimensions using signals, but I'm on Elm 0.18 and it seems slightly outdated.
Edit: Just to be clear, the above code wouldn't work. Whatever was attached to the window object would have to be JS, so it'd have to go through a decoder.
You will need to use programWithFlags to pass initial values from javascript. The "flags" you pass from javascript should have equivalent record type if you want to use Elm's automatic type conversion:
Let's say your me is just a string, but your todos is a list of boolean flags and a label. Your Flags type could look like this:
type alias Todo =
{ done : Bool, label : String }
type alias Flags =
{ me : String, todos : List Todo }
Your init function would need to handle the flags value appropriately. Here is an example of just assigning the fields to Model fields of the same name:
type alias Model =
{ me : String, todos : List Todo }
main : Program Flags Model Msg
main =
Html.programWithFlags
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
init : Flags -> ( Model, Cmd Msg )
init flags =
{ me = flags.me, todos = flags.todos } ! []
Your javascript will need to be updated to pass in the flags. You do that by passing a json object as the first parameter to fullscreen or embed:
var app = Elm.Main.fullscreen({
"me": "John Doe",
"todos": [
{ done: true, label: "Do this thing" },
{ done: false, label: "And this thing" }
]
});
Here is a working example on ellie-app.com
If Elm's automatic json-to-mapping conversion isn't strong enough for your decoding, you could instead use the Json.Decode.Value type as your flag, then Json.Decode.decodeValue using your customer decoder. Here is an example on ellie-app.com of using a custom decoder.

Elm: Update a command coming from nested module

In my Elm app, there is a parent and a child. I would like the child to send an HTTP command and then the parent would update it on the fly by adding the backend config that the child model doesn't have.
The way I see it is:
The child sends the HTTP command (update function)
The command is transferred to the parent (update function)
The parent adds the config to command (is this possible??)
The command is finally executed with all the required parameters
I would like to implement HTTP actions in children modules so my app remains modular while keeping the config in one place.
Any help is welcome. Maybe a different approach can solve my problem too!
Keep all the configuration details in a module that handles the Http Requests. For example, you can have this code in Requests.elm
import Http
import Task
url = "http://ip.jsontest.com/"
getIP : (String -> msg) -> Cmd msg
getIP tagger =
Http.getString url
|> Task.perform (\e -> tagger (toString e)) tagger
and then in the Component code have something like
update msg model =
case msg of
GetIP -> model ! [Requests.getIP ShowIP]
ShowIP str -> {model | ipStr = str} ! []
all the configuration details are hidden from the component. Only an API of access is exposed.
LATER EDIT:
Another option would be to define and pass around a dynamic Context. You can manage this context at the top level and just pass the current version around when you update things. It would look like this
In Requests.elm
import Http
import Task
type alias Context =
{ url : String }
getIP : Context -> (String -> msg) -> Cmd msg
getIP ctx tagger =
Http.getString ctx.url
|> Task.perform (\e -> tagger (toString e)) tagger
in Components:
update ctx msg model =
case msg of
GetIP -> model ! [Requests.getIP ctx ShowIP]
ShowIP str -> {model | ipStr = str} ! []

How to use ports inside StartApp in Elm

In my app that based on the StartApp package I have a port to communicate from inside the to JS. At the moment I call this port using a mailbox
requestPalette :
{ address : Signal.Address String
, signal : Signal String
}
requestPalette = Signal.mailbox ""
requestPaletteFilter : Signal String
requestPaletteFilter =
Signal.filter (String.isEmpty >> not) "" requestPalette.signal
|> settledAfter (300 * Time.millisecond)
port request : Signal String
port request = requestPaletteFilter
and using it like this:
[on "input" targetValue (\str -> Signal.message requestPalette.address str)
I wonder if there is a way to this inside of the update function instead of sending the message from the view.
This applies to elm 0.16 (and before), in elm 0.17 subscriptions have changed into ports
In order to send a signal to a mailbox from an update, you'll need to use StartApp as opposed to StartApp.Simple, since the former allows for Effects in the update function.
At a bare minimum, you're going to probably have an Action like this, which defines a No-Op and an action for sending the string request:
type Action
= NoOp
| SendRequest String
Your update function will now include something like the following case for the new SendRequest action. Since you're using StartApp, which deals in Effects, you must call Effects.task, and the Task you're mapping to an Effect must be of type Action, which is why we have the Task.succeed NoOp return value.
update action model =
case action of
NoOp ->
(model, Effects.none)
SendRequest str ->
let
sendTask =
Signal.send requestPalette.address str
`Task.andThen` (\_ -> Task.succeed NoOp)
in
(model, sendTask |> Effects.task)
Now your click event handler in the view can go back to using the address passed into the view:
[ on "input" targetValue (Signal.message address << SendRequest) ]
I've got a working example of the above in this gist. You'll just need to subscribe to the request port in javascript to see it in action.

What is wrong this backbone.js code with an error "a._validate is not a function"?

I'm trying to develop a Todo-list application like a sample application for Backbone.js. The code is here (prototype branch is the latest). When text is input, a task with the text should be saved on local storage. But, the text failed to be saved with a below error.
a._validate is not a function
Why? Codes is following.
app/assets/javascripts/tasks.js.coffee
$ ->
tasks = new Todoapp.Collections.Tasks
appView = new Todoapp.Views.AppView(el: $("#todoapp"), collection: tasks)
app/assets/javascripts/backbone/views/app.js.coffee
Todoapp.Views.AppView = Backbone.View.extend
events:
"keypress #task_input" : "createTask"
initialize: ->
this.collection.bind("add", this.alertCreate, this)
createTask: (e) ->
text = $("#task_input").val()
return if !text or e.keyCode isnt 13
this.collection.create(content: text)
$("#task_input").val("")
alertCreate: ->
alert("Created!")
app/assets/javascripts/backbone/collections/tasks.js.coffee
Todoapp.Collections.Tasks = Backbone.Collection.extend
model: Todoapp.Models.Task
localStorage: new Store("tasks")
app/assets/javascripts/backbone/models/task.js.coffee
Todoapp.Models.Task = Backbone.Model.extend
Your problem is with your model definition:
Todoapp.Models.Task = Backbone.Model.extend
All that does is assigns the Backbone.Model.extend method to Todoapp.Models.Task, it doesn't execute the extend method. Add some parentheses and it will work:
Todoapp.Models.Task = Backbone.Model.extend()
Demo: http://jsfiddle.net/ambiguous/ejjHz/
Or better, write your CoffeeScript in CoffeeScript rather than some mixture of CoffeeScript and JavaScript:
class Todoapp.Models.Task extends Backbone.Model
class Todoapp.Collections.Tasks extends Backbone.Collection
model: Task
localStorage: new Store('tasks')
class Todoapp.Views.AppView extends Backbone.View
events:
'keypress #task_input' : 'createTask'
initialize: ->
#collection.bind('add', #alertCreate)
createTask: (e) ->
text = $('#task_input').val()
return if !text or e.keyCode isnt 13
#collection.create(content: text)
$('#task_input').val('')
alertCreate: =>
alert('Created!')
Demo: http://jsfiddle.net/ambiguous/HPHVG/
The changes:
Use class ... extends instead of C = B.extend. This alone would solve your problem.
Use # instead of this.
Use bound methods (=>) instead of supplying a context to bind.