I have used the http post in elm in following manner but it is not working and on server side I am getting the empty body.
Can anybody tell me what is wrong with this code.this is the code of my command module
module SignupForm.Commands exposing (..)
import Http exposing(..)
import Json.Decode as Decode exposing (field)
import SignupForm.Models exposing (..)
import SignupForm.Messages exposing (..)
import Json.Encode as Encode
import Debug exposing (log)
memberDecoder : Decode.Decoder UserDetails
memberDecoder =
Decode.map4 UserDetails
(field "fname" Decode.string)
(field "lname" Decode.string)
(field "email" Decode.string)
(field "password" Decode.string)
saveUrl : String
saveUrl =
"http://localhost:4000/userSignup"
saveRequest : UserDetails -> Http.Request UserDetails
saveRequest d =
Http.request
{ body = memberEncoded d |> Http.jsonBody
, expect = Http.expectJson memberDecoder
, headers = defaultRequestHeaders
, method = "POST"
, timeout = Nothing
, url = saveUrl
, withCredentials = False
}
defaultRequestHeaders : List Header
defaultRequestHeaders =
[ Http.header "Content-Type" "application/x-www-form-urlencoded"
]
fetchAll : UserDetails -> Cmd Msg
fetchAll data =
saveRequest data
|> Http.send OnSignUp
memberEncoded : UserDetails -> Encode.Value
memberEncoded data =
let
list =
[ ( "fname", Encode.string data.fname )
, ( "lname", Encode.string data.lname )
, ( "email", Encode.string data.email )
, ( "password", Encode.string data.password )
]
in
list
|> Encode.object
You are posting json, but declaring that you are sending formdata.
Try removing:
defaultRequestHeaders : List Header
defaultRequestHeaders =
[ Http.header "Content-Type" "application/x-www-form-urlencoded"
]
(Note that expectJson will add Http.header "Content-Type" "application/json" automatically for you
Related
I have this endpoint with this structure:
uri = http://127.0.0.1:9090/tables/mask
and this payload:
{
"_id" : "5d66c9b6d5ccf30bd5b6b541",
"connectionId" : "1967c072-b5cf-4e9e-1c92-c2b49eb771c4",
"name" : "Customer",
"columns" : [
{
"name" : "FirstName",
"mask" : true
},
{
"name" : "LastName",
"mask" : false
},
{
"name" : "City",
"mask" : false
},
{
"name" : "Phone",
"mask" : false
}
],
"parentId" : null
}
in my Kotlin code I have this structure to deserialize:
data class ColumnsMaskModel (val name:String, val mask:Boolean )
data class TablesMaskModel (val _id:String, val name:String, val connectionId:String, val columns:MutableList<ColumnsMaskModel?> )
and how can I use TablesMaskModel to make a HTTP post in Kotlin
You'll need an HTTP client to do that. Data classes themselves has nothing to do with HTTP, they are just data structures. There are a lot of HTTP clients available on Kotlin for JVM:
java.net.HttpURLConnection
Java 9's HttpClient
Apache HttpComponents
OkHttp
Ktor
Let's see how to make HTTP requests in Ktor:
data class ColumnsMaskModel(val name: String, val mask: Boolean)
data class TablesMaskModel(val _id: String, val name: String, val connectionId: String, val columns: MutableList<ColumnsMaskModel?>)
fun main() = runBlocking {
val client = HttpClient {
install(JsonFeature) {
serializer = JacksonSerializer()
}
}
val result = client.post<String> {
url("http://httpbin.org/post")
contentType(ContentType.Application.Json)
body = TablesMaskModel(
_id = "5d66c9b6d5ccf30bd5b6b541",
connectionId = "1967c072-b5cf-4e9e-1c92-c2b49eb771c4",
name = "Customer",
columns = mutableListOf(
ColumnsMaskModel(name = "FirstName", mask = true),
ColumnsMaskModel(name = "LastName", mask = false),
ColumnsMaskModel(name = "City", mask = false),
ColumnsMaskModel(name = "Phone", mask = false)
)
)
}
println(result)
client.close()
}
Note that Ktor uses suspending functions for HTTP requests, so you'll need a coroutine scope, runBlocking in this example.
Ktor supports various "backends" for HTTP clients – Apache, Coroutines IO, curl. It also has different "features" to enable on-the-flight payloads serializations and de-serializations, like in the example above.
I have a static page with two components.
One in the header that shows a menu handling user preferences/login and signup
One in the main page that is able to display a list of user images or a form about the user profile for instance.
Is there a way to share a state (let say the access_token to the API for instance) between those two components?
I could add ports to each components that would update a localStorage key and send the store to each other components.
But is there a better way for this?
The solution I ended-up with was to use two ports:
port saveStore : String -> Cmd msg
port storeChanged : (String -> msg) -> Sub msg
With decoders and encoders:
serializeStore : AccessToken -> Profile -> Cmd Msg
serializeStore access_token profile =
encodeStore access_token profile
|> saveStore
encodeStore : AccessToken -> Profile -> String
encodeStore access_token profile =
let
encoded =
Encode.object
[ ( "access_token", tokenToString access_token |> Encode.string )
, ( "profile", encodeUserProfile profile )
]
in
Encode.encode 0 encoded
deserializeStore : String -> Maybe Store
deserializeStore =
Decode.decodeString decodeStore >> Result.toMaybe
decodeStore : Decoder Store
decodeStore =
Decode.map2 Store
decodeToken
(Decode.field "profile" decodeProfile)
decodeToken : Decoder AccessToken
decodeToken =
Decode.field "access_token" Decode.string
|> Decode.andThen
(\token ->
stringToToken token
|> Decode.succeed
)
And then I use them to sync my component store and keep a copy on the localStorage:
<script src="app.js"></script>
<script>
// The localStorage key to use to store serialized session data
const storeKey = "store";
const headerElement = document.getElementById("header-app");
const headerApp = Elm.Header.init({
node: element,
flags: {
rawStore: localStorage[storeKey] || ""
}
});
const contentElement = document.getElementById("content-app");
const contentApp = Elm.Content.init({
node: element,
flags: {
rawStore: localStorage[storeKey] || ""
}
});
headerApp.ports.saveStore.subscribe((rawStore) => {
localStorage[storeKey] = rawStore;
contentApp.ports.storeChanged.send(rawStore);
});
contentApp.ports.saveStore.subscribe((rawStore) => {
localStorage[storeKey] = rawStore;
headerApp.ports.storeChanged.send(rawStore);
});
// Ensure session is refreshed when it changes in another tab/window
window.addEventListener("storage", (event) => {
if (event.storageArea === localStorage && event.key === storeKey) {
headerApp.ports.storeChanged.send(event.newValue);
contentApp.ports.storeChanged.send(event.newValue);
}
}, false);
</script>
I am trying to get the width and height of an image, so give a URL, what is the width and height of that image
I don't believe that there is a way to do this in elm. Ports are one possible solution. You can read about them here. I've written a small example of your use case which you can run your self on ellie. In this example I use the JS example you gave in your comment, but there are other possible solutions such as event listeners or querying the DOM.
Main.elm
port module Main exposing (main)
import Html exposing (..)
import Html.Attributes exposing (..)
main : Program Never Model Msg
main =
Html.program
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
type alias Model =
{ imageUrl : String
, dim : Maybe ( Int, Int )
}
testImg : String
testImg =
"https://images-na.ssl-images-amazon.com/images/I/71TcaVWvBsL._SY355_.jpg"
init : ( Model, Cmd msg )
init =
Model testImg Nothing
! [ getDim testImg ]
type Msg
= UpdateDim ( Int, Int )
update : Msg -> Model -> ( Model, Cmd msg )
update msg model =
case msg of
UpdateDim xy ->
{ model | dim = Just xy } ! []
view : Model -> Html msg
view model =
case model.dim of
Nothing ->
div [] []
Just dims ->
div []
[ img [ src model.imageUrl ] []
, text <|
"size: "
++ toString dims
]
subscriptions : Model -> Sub Msg
subscriptions model =
newDim UpdateDim
-- this port handles our incomming height and width
-- and passes it to a Msg constructor
port newDim : (( Int, Int ) -> msg) -> Sub msg
-- this port passes our string out of Elm and into
-- js land
port getDim : String -> Cmd msg
index.html
<html>
<head>
<style>
/* you can style your program here */
</style>
</head>
<body>
<script>
var app = Elm.Main.fullscreen()
// you can use ports and stuff here
app.ports.getDim.subscribe(function(url){
// recieve the url for the image through
// the `getDim` port in Main.elm
let img = new Image()
img.src = url
img.onload = function() {
// send the height and width back to elm through
// the `newDim` port in Main.elm
app.ports.newDim.send([img.height, img.width])
}
})
</script>
</body>
</html>
I mentioned in a comment that if you display the image, you can do this without ports. This is the solution I was talking about:
module Main exposing (main)
import Browser
import Debug exposing (toString)
import Html exposing (Html, div, img, text)
import Html.Attributes exposing (src)
import Html.Events exposing (on)
import Json.Decode as Decode
type alias Model =
{ imageDimensions : Maybe ImgDimensions }
initialModel : Model
initialModel =
{ imageDimensions = Nothing }
type Msg
= ImgLoaded ImgDimensions
update : Msg -> Model -> Model
update msg model =
case msg of
ImgLoaded dimensions ->
{ model | imageDimensions = Just dimensions }
type alias ImgDimensions =
{ width : Int
, height : Int
}
decodeImgLoad msg =
Decode.map msg <|
Decode.field "target" <|
Decode.map2 ImgDimensions
(Decode.field "width" Decode.int)
(Decode.field "height" Decode.int)
view : Model -> Html Msg
view model =
div []
[ text <| "Image Loaded = " ++ toString model.imageDimensions
, img
[ on "load" (decodeImgLoad ImgLoaded)
, src "https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bd07f82e-a30d-4e93-a2cf-0c16ea2b7f40/08-owl-opt.jpg"
]
[]
]
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
https://ellie-app.com/3FCdcDqy4gqa1
i have the following type User:
type alias User =
{ id : Int
, name : String
, age : Maybe Int
, deleted : Bool
}
User is a type used in my Model:
type alias Model =
{ users : List User
, name : String
, age : String
, message : String
}
When I iterate over "List User" using List.map like this...
Delete id ->
let
newUserList =
List.map
(\user ->
if user.id == id then
{ user | deleted = True }
else
user
)
model.users
in
( { model | users = newUserList }, Cmd.none )
... the Compiler tells me:
The 2nd argument to function `map` is causing a mismatch.
List.map
(\user ->
if user.id == id then
{ user | deleted = True }
else
user
)
model.users
Function `map` is expecting the 2nd argument to be:
List { a | id : Int, name : String }
But it is:
List (Bool -> User)
That is pretty strange for me.
Why does my map function change the Type User...?
I do not change it, I just iterate over, map each user and if I found the right one, by its id, I change deleted value to True...
I am a bit confused...
Can anyone help?
kind regards :)
UPDATE: It does not seem to me a problem of the List.map function but of the type alias User declaration.
As soon as I add another value this breaks...
Here is the whole code for it. It is kept pretty simple.
Note: As soon as you uncomment the Users property "deleted" the compiler throws an error
module Main exposing (..)
import Html exposing (Html, text, h1, div, img, input, form, ul, li, i, hr, br)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Html.App as App
import String
import Random
--import Debug
--import Uuid
main : Program Never
main =
App.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
--MODEL
type alias Model =
{ users : List User
, name : String
, age : String
, message : String
}
type alias User =
{ id : Int
, name : String
, age :
Maybe Int
-- , deleted : Bool
}
init : ( Model, Cmd Msg )
init =
( initModel, Cmd.none )
initModel : Model
initModel =
{ users = []
, name = ""
, age = ""
, message = ""
}
--UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
InsertName username ->
( { model | name = username }, Cmd.none )
InsertAge age ->
let
newAge =
case String.toInt age of
Err err ->
""
Ok value ->
toString value
newMessage =
case String.toInt age of
Err err ->
"Age must be a number!"
Ok int ->
""
in
( { model | age = newAge, message = newMessage }, Cmd.none )
InitNewUser ->
( model, Random.generate AddNewUser (Random.int 1 9999) )
AddNewUser randomId ->
if String.isEmpty model.name then
( { model | message = "Please give a name" }, Cmd.none )
else
let
ageAsInt =
case String.toInt model.age of
Err err ->
Nothing
Ok int ->
Just int
newUser =
User randomId model.name ageAsInt
newUserList =
newUser :: model.users
in
( { model | users = newUserList, name = "", age = "" }, Cmd.none )
Delete id ->
let
newUserList =
List.map
(\user ->
if user.id == id then
{ user | name = "--deleted--" }
else
user
)
model.users
in
( { model | users = newUserList }, Cmd.none )
--VIEW
type Msg
= InsertName String
| InsertAge String
| AddNewUser Int
| InitNewUser
| Delete Int
userListView : Model -> Html Msg
userListView model =
let
newList =
List.filter (\user -> (user.name /= "--deleted--")) model.users
in
newList
|> List.sortBy .name
|> List.map userView
|> ul []
userView : User -> Html Msg
userView user =
let
ageAsString =
case user.age of
Just val ->
val |> toString
Nothing ->
"-"
in
li []
[ div [] [ text ("ID: " ++ toString user.id) ]
, div [] [ text ("Name: " ++ user.name) ]
, div [] [ text ("Age: " ++ ageAsString) ]
, input [ type' "button", value "Delete", onClick (Delete user.id) ] []
]
view : Model -> Html Msg
view model =
div [ class "wrapper" ]
[ h1 [] [ text ("We have " ++ toString (List.length model.users) ++ " Users") ]
, Html.form []
[ input [ type' "text", onInput InsertName, placeholder "Name", value model.name ] []
, input [ type' "text", onInput InsertAge, placeholder "Age", value model.age ] []
, input [ type' "button", onClick InitNewUser, value "Add new user" ] []
]
, div [] [ text model.message ]
, userListView model
, hr [] []
, div [] [ text (toString model) ]
]
The problem is this part of the AddNewUser message:
newUser =
User randomId model.name ageAsInt
It is missing False as the forth argument when you use the deleted property.
If you do not include it, the User function will return a partially applied function that still needs a Bool to return a proper user. The compiler seems to get thrown off by this even though all your types and functions have proper annotations.
Another way that leads to a better error message would be to define newUser like this:
newUser =
{ id = randomId
, name = model.name
, age = ageAsInt
, deleted = False
}
I am exploring ELM and trying to access web api. I followed the this link.
I am able to hit my service and getting the response (Showing in browser network tab), but failure code portion is executing from elm update.
Implementation
---Model
type alias Model =
{
message : String,
}
model:Model
model = {
message = "Hello"
}
--update
postRequest : Http.Request
postRequest =
{ verb = "POST"
, headers =
[("Content-Type", "application/json")
]
, url = "http://xyz/events/list"
, body = Http.string """{ "domainId": 1 }"""
}
getEventList: Cmd Msg
getEventList =
Task.perform Messages.FetchFail Messages.FetchSucceed (Http.fromJson decodeString (Http.send Http.defaultSettings postRequest))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Messages.NoOp ->
( model, Cmd.none )
Messages.FetchSucceed xy->
({model| message = "success"},Cmd.none)
Messages.FetchFail _->
({model |message = "fail"} ,Cmd.none)
API response :
{ "message": "", "data": [
{
"eventId": 104,
"title": "private Events",
"description": "abc",
"businessId": 51,
"businessTitle": "VampireDA_Adda",
"startDate": "2016-07-08"
},
{
"eventId": 107,
"title": "Event weekly Midnight",
"description": "xyz",
"businessId": 44,
"businessTitle": "Spanish Scotch",
"startDate": "2016-07-08"
}] }
Please help me, if i have implement any thing wrong.
and also how i can decode json to model (fill model with response json)?
you are probably getting the error because the decoding is not working out. But first try to get used to piping
Http.send Http.defaultSettings postRequest
|> Http.fromJson decodeString
|> Task.perform Messages.FetchFail Messages.FetchSucceed
To decode you need an Elm record
type alias DataItem =
{ eventId: Int
, title: String
, description : String
, businessId : Int
, businessTitle : String
, startDate: String
}
The decoder will then look something like
dataDecoder =
object6 DataItem
("eventId" := int)
("title" := string)
("description" := string)
("businessId" := int)
("businessTitle" := string)
("startDate" := string)
decoder = at ["data"] (list dataDecoder)
Decoders take a bit of getting used to, so I created a site to help you practise: http://simonh1000.github.io/decoder/