ELM-LANG how to make http request when clicking on browser back button - http

I have an url like this /user/3 where I make a http request to get the user information.
If I move to /user/6 I will make another http request to get this user data.
My problem is when I click on the browser back button the url move back to /user/3 but it still the user 6 information which are display.
How do I do this ? Is it possible to get my old model back ? Or do I have to make again a http request to get user 3.
My urlUpdate looks like this:
urlUpdate : Result String Route -> Model -> ( Model, Cmd Msg )
urlUpdate result model =
let
currentRoute =
Routing.routeFromResult result
in
( { model | route = currentRoute }, Cmd.none )
I'm a bit lost, I don't know where to do this.
If it's not clear I would like to "catch" an event when the user click on the browser back button to get the old url and then make a http request to get again this user information.
I use the elm-navigation package.
I have try to do this in my urlUpdate:
urlUpdate : Result String Route -> Model -> ( Model, Cmd Msg )
urlUpdate result model =
let
currentRoute =
Routing.routeFromResult result
command =
case currentRoute of
Routing.HomeRoute ->
getUsers
Routing.userRoute id ->
getUser id
Routing.NotFoundRoute ->
Cmd.none
in
( { model | route = currentRoute }, command )
Which is the same thing as my init function. It doesn't work because it make an infinite loop execute the urlUpdate

Assuming you are using elm-lang/navigation module.
urlUpdate gets called when url changes (including
hitting browser navigation button).
So, one way to handle the url change can be to return
Http access command instead of Cmd.none
in the second return value of your urlUpdate function.
When the Task resolves or fails, Msg (in this example, FetchFail or FetchScucceed) will be thrown, so
that you can react by updateing your model.
In the sample below, I updated the model so that you can show spinner
until API call resolves.
sample:
urlUpdate : Result String Route -> Model -> ( Model, Cmd Msg )
urlUpdate result model =
case result of
Ok url ->
let
userId = getUserId url
in
({ model | spinner = True }, getUserInfo userId )
Err _ ->
({ model | showErr = True }, Cmd.none )
-- API call
getUserInfo : String -> Cmd Msg
getUserInfo str =
let
decodeUserInfo = Json.at ["args", "userid"] Json.string
url = "https://httpbin.org/get?userid=" ++ str
in
Task.perform FetchFail FetchSucceed (Http.get decodeUserInfo url)

Related

Sending multipart request by aiohttp

I trying to make test case for my view, and now I would to send mutipart request with user credentials containing user image. I use MultipartWriter but, when I'm trying to read parts, I get part as None. I'm sure that my test case is wrong written.
Thanks for your help, and sorry for my bad english.
My test case:
async def test_users_post(app):
with open('user_photo.jpg', 'rb') as file:
user_data = dict(
name='Test1',
password='Password',
grand=True,
description='Blank',
email='test#test.ru',
file=file
)
with MultipartWriter() as mpwriter:
mpwriter.append_form([(key, value) for key, value in user_data.items()])
response = await app['client'].post('/users', data=mpwriter)
assert response.status == 201
Start lines of view:
async def post(self):
reader = await self.request.multipart()
async with self.request.app['db'].acquire() as conn:
data = {}
while True:
field = await reader.next()
print(field) # field is None here
if not field:
# If not fields were read
Okay I had found way, which doesn't need multipart writer.
Now I user aiohttp.FormData for packing user credentials.
async def test_users_post(app):
form = aiohttp.FormData()
user_data = dict(
name='Test1',
password='Password',
description='Blank',
email='test#test.ru',
)
for key, value in user_data.items():
form.add_field(key, value)
form.add_field('photo', open('user_photo.jpg', 'rb'))
response = await app['client'].post('/users', data=form)
assert response.status == 201

How to perform multiple Http requests (Tasks) in bulk in Elm lang

I want to load user profile before rendering something into the page but the whole user profile is composed of different parts that are loaded by multiple HTTP requests.
So far I'm loading user profile in sequence (one by one)
type alias CompanyInfo =
{ name: String
, address: ...
, phone: String
, ...
}
type alias UserProfile =
{ userName: String
, companyInfo: CompanyInfo
, ...
}
Cmd.batch
[ loadUserName userId LoadUserNameFail LoadUserNameSuccess
, loadCompanyInfo userId LoadCompanyInfoFail LoadCompanyInfoSuccess
...
]
But that's not very effective. Is there a simple way how to perform a bunch of Http requests and return just one complete value?
Something like this
init =
(initialModel, loadUserProfile userId LoadUserProfileFail LoadUserProfileSuccess)
....
You can achieve this using Task.map2:
Edit: Updated to Elm 0.18
Task.attempt LoadUserProfile <|
Task.map2 (\userName companyInfo -> { userName = userName, companyInfo = companyInfo })
(Http.get userNameGetUrl userDecoder |> Http.toTask)
(Http.get companyInfoGetUrl companyInfoDecoder |> Http.toTask)
You can then get rid of the individual LoadUserName... and LoadCompanyInfo... Msgs. In Elm 0.18, the need for separate Fail and Succeed Msgs is addressed by Task.attempt expecting a Result Error Msg type, so that LoadUserProfile is defined like this:
type Msg
= ...
| LoadUserProfile (Result Http.Error UserProfile)
map2 will only succeed once both tasks succeed. It will fail if any of the tasks fail.

Elm: Iterate list performing multiple HTTP requests

I'm wondering if I can get some help with iterating a list of groups, making a POST request for each group to create a 'room', iterating the users for each group and making a POST request to assign them to this specific room.
I have the following model.
model = {
groups = [
{
title = "Foo"
, users = [
{ name = "Joe" }
, { name = "Mary" }
]
},
{
title = "Bar"
, users = [
{ name = "Jill" }
, { name = "Jack" }
]
}
]
}
The desired result is that the room Foo was created and Joe and Mary were assigned, and Bar was created and Jill and Jack were assigned.
The view, for now, would just be a simple button that triggers an action.
div []
[ button [ onClick InviteUsersToRoom ] [ text "Invite users to room" ] ]
I've created 2 POST requests:
createRoom: take a title, create a room using the title and return the room_id
addUser: take a room_id and a user's name, add the the users to the room and return the status of ok
example:
-- create a room for each group
-- passing in `title` as the room name
-- which will return the room id from `decodeCreateRoomResponse`
createRoom : String -> String -> Cmd Msg
createRoom title =
Task.perform
CreateRoomsFail
CreateRoomsSuccess
(Http.post
decodeCreateRoomResponse
("https://some_api?room=" ++ title)
Http.empty
)
decodeCreateRoomResponse : Json.Decoder String
decodeCreateRoomResponse =
Json.at ["room", "id"] Json.string
-- add a user to a room using a `room_id` and the user's name
-- returns a bool from `decodeAddUserResponse`
addUser : String -> String -> Cmd Msg
addUser room_id user =
Task.perform
AddUserFail
AddUserSuccess
(Http.post
decodeCreateChannelResponse
("https://some_api?room=" ++ room_id ++ "&user=" ++ user)
Http.empty
)
decodeAddUserResponse : Json.Decoder String
decodeAddUserResponse =
Json.at ["ok"] Json.bool
I'm wondering how you'd go about stitching this up altogether, so that onclick :
iterate each group
make the POST to create the Room
take the room_id from the response and iterate the users
POST the room_id and the users name
Any help is appreciated.
You've got a few scattered errors that I won't explicitly point out because the compiler will help you, but you're off to a good start. You already have some Http handling Cmds built up so you just need to wire things up with your update function.
Let's define your Model explicitly (you may already be doing this but it isn't in your example):
type alias User =
{ name : String }
type alias Group =
{ title : String
, users : List User
}
type alias Model =
{ groups : List Group }
Based off your functions, here's how I interpret your Msg type, with one small change which is to add a list of users as a parameter to CreateRoomsSuccess.
type Msg
= InviteUsersToRoom
| CreateRoomsFail Http.Error
| CreateRoomsSuccess (List User) String
| AddUserFail Http.Error
| AddUserSuccess Bool
Now we can tweak createRoom in order to pass along the list of users to create. Note that this isn't creating any users at this time. It is using currying to create a partially-applied function so that when the CreateRoomsSuccess case is handled in the update function, it already has the list of users that need to be created (rather than having to look them up in the model list):
createRoom : Group -> Cmd Msg
createRoom group =
Task.perform
CreateRoomsFail
(CreateRoomsSuccess group.users)
(Http.post
decodeCreateRoomResponse
("https://some_api?room=" ++ group.title)
Http.empty
)
To create the list of rooms, you simply map the list of groups to a list of Cmds that perform the post. This will happen when the button is clicked:
case action of
InviteUsersToRoom ->
model ! List.map createRoom model.groups
...
You'll have to implement the update cases for when errors occur. Next up, we have to handle the CreateRoomsSuccess message. This is where you'll need to look up the list of users for a group. Again, you'll map over the function you already created that handles the Http task:
case action of
...
CreateRoomsSuccess users roomID ->
model ! List.map (addUser roomID << .name) users
...
You'll have to handle the AddUserFail and AddUserSuccess cases, but the above examples should help you understand how to post multiple messages and act accordingly based on the success or failure of each.

POST data empty ( or not exist ) when I receive post back from TPV provider

I'm trying to implement a service Redsys payments on my .net website.
The payment is successful (data are sent by post) but when the POST data back to my website ( to confirm the payment ) and i try to retrieve them with:
Request.form string value = [ "name"]
the value is always empty
I tried to count how many are in Request.Form.Keys.Count and always gives me zero values.
In the vendor documentation it indicated that the variables may be collected with Request.form [ "name"] and I called them to ask why I do not get the data and they dont know why...so I'm desperate,
What may be due?
I have reviewed the header I get from the server ( width Request.Headers ) and have the following parameters. HttpMethod:: GET Requestype: GET and contentlength: 0 . My bank tell me that they response POST not GET... so it´s a mistery. May be the error is becouse that sendings are made from a https to a htttp ?
You are receiving a POST without parameters.
The parameters are in the content of the call.
You should read the content and get the values of each parameter:
[System.Web.Http.HttpPost]
public async Task<IHttpActionResult> PostNotification()
{
string body = "";
await
Request.Content.ReadAsStreamAsync().ContinueWith(x =>
{
var result = "";
using (var sr = new StreamReader(x.Result))
{
result = sr.ReadToEnd();
}
body += result;
});
In body you can read the parameters (the order of them can change).

Serverside method throws error on method call, but not on client

On my client I have the following code
# If clicking on unit while target is set, activates action
'click .actor': () ->
character = Meteor.user().character()
target = Session.get('target')
skill = Session.get('selectedSkill')
if target and skill
console.log character.battle()
Meteor.call('useSkill', skill, character, target, (err) ->
if err then console.log err.reason
)
Here, when I invoke the character.battle(), it correctly returns the battle document. But when I call the same method, on the same object, in the useSkill() method, it throws the following error
Exception while invoking method 'useSkill' TypeError: Object #<Object> has no method 'battle'
Serverside method
useSkill: (skill, actor, target) ->
cost = skill.cost
console.log "battle: #{actor.battle()}"
....
and the association 'battle()' method
#Characters = new Meteor.Collection('characters',
transform: (entry) ->
entry.battle = () ->
Battles.findOne({active: true, $or: [{characterOneId: this._id}, {characterTwoId: this._id}]})
return entry
)
You can only pass serializable objects as method arguments. In other words, all the custom methods you added on the client side are not preserved when you pass the actor argument like that. To have access to your custom function on the server, you need to create the object on the server.
So instead of passing the whole character object, just pass its _id and find it again inside the method:
useSkill: (skill, actorId, target) ->
actor = Characters.findOne actorId
...
console.log "battle: #{actor.battle()}"
Functions are not serialized by EJSON and NOT send over DDP.
In Meteor.call pass id of character:
Meteor.call('useSkill', skill, character._id, target, (err) ->
if err then console.log err.reason
)
In useSkill method find actor :
useSkill: (skill, actorId, target) ->
cost = skill.cost
actor = Characters.findOne actorId
console.log "battle: #{actor.battle()}"

Resources