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.
Related
I have an Elm file with some HTML content in a function called view.
Part of the HTML content is dynamically generated via a map function that extracts data from a record.
Below is the code from that file.
module PhotoGroove exposing (main)
import Html exposing (div,h1,img,text)
import Html.Attributes exposing (..)
--model
initialModel : { photos : List { url : String }, selectedPhoto : String }
initialModel =
{ photos =
[ { url = "1.jpeg" }
, { url = "2.jpeg" }
, { url = "3.jpeg" }
]
, selectedPhoto = "1.jpeg"
}
--view
urlPrefix : String
urlPrefix = "http://elm-in-action.com/"
viewThumbnail : String -> { a | url : String } -> Html.Html msg
viewThumbnail selectedPhoto thumb =
if selectedPhoto == thumb.url
then img [src (urlPrefix ++ thumb.url), class "selected"] []
else img [src (urlPrefix ++ thumb.url)] []
view : List { a | url : String } -> Html.Html msg
view model =
div [class "context"]
[h1 [] [text "Photo Groove"]
,div [id "tumbnail"] (List.map viewThumbnail model) -- error message on "model"
]
--main
main = view initialModel -- error message on "initialModel"
However, I got error messages on the words "model" and "initialModel".
Below is the error message for "model":
TYPE MISMATCH - The 2nd argument to `div` is not what I expect:
29| ,div [id "tumbnail"] (List.map viewThumbnail model)
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^#
This `map` call produces:
List #({ a | url : String } -> Html.Html msg)#
But `div` needs the 2nd argument to be:
List #(Html.Html msg)#
#Hint#: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
And for initialModel:
TYPE MISMATCH - The 1st argument to `view` is not what I expect:
35| main = view initialModel
#^^^^^^^^^^^^#
This `initialModel` value is a:
#{ photos : List { url : String }, selectedPhoto : String }#
But `view` needs the 1st argument to be:
#List { a | url : String }#
I got that I need to somehow convert the objects directly into HTML msg types, but I don't know how.
I even tried to change the type for view, but that did not work either.
How do I fix this problem?
This is a great example of Elm's type system (or a good static type system in general) helping you figure out logical errors in your code.
The problems here are all due to your view function, and in particular with faulty assumptions it makes about what is in your model.
You are trying to map a function over the model argument, which assumes model is a list - but it isn't, it's a record containing both a list of all photos and a string identifying the selected photo. That makes logical sense as a model to use in this situation, so rather than changing the model type (which would otherwise be a sensible option) I would suggest changing view so that it works with your actual model type.
Another problem, which is highlighted in the first of the 2 error messages you quote, is that viewThumbnail can't be mapped over a list of photos anyway because it actually takes 2 arguments, the first of which isn't a list. You do want to use this function to produce the list of images in view's output - but you also need to provide it with the selectedPhoto, which of course comes from the other field in your model that you've ignored so far.
So the only changes needed to your original code to get this to compile are:
change the type signature of view to actually match your model's type. (This is what the second of the two error messages is about). It should be view : { photos : List { url : String }, selectedPhoto : String } -> Html.Html msg (You probably want to make a type alias for this model type, at least if you expand this app to any much greater size.)
inside the function body,
change this
List.map viewThumbnail model
to
List.map (\photo -> viewThumbnail model.selectedPhoto photo) model.photos
Hopefully you can see what the above is doing - the \ is for an anonymous function, that takes one of the photos and calls viewThumbnail with that as second argument and model.selectedPhoto always as the first argument.
And note finally that you can simplify the above by taking advantage of Elm's built-in function currying, to just
List.map (viewThumbnail model.selectedPhoto) model.photos
which I and probably most others familiar with functional programming find much cleaner and easy to read.
Looking for some review on this to let me know if this is the right approach to check for disabled checkboxes.
Part of my page model here:
class eligibleAccountType {
constructor (text) {
this.label = label.withText(text);
this.checkbox = this.label.find('input[type=checkbox]');
}
}
class ProgramOptionsSubscriptionRulesPage{
constructor(){
this.contractsatAccountLevel = Selector("#program_option_allow_participant_account_contracts")
this.eligibleAccountTypesList = [
new eligibleAccountType("Residential"),
new eligibleAccountType("Commercial"),
new eligibleAccountType("Industrial")
];
Part of my test here
if (userdata.userrole == "Read Only") {
for (const eligibleAccountType of programOptionsSubscriptionRulesPage.eligibleAccountTypeList) {
await t.expect(eligibleAccountType.hasAttribute('disabled')).ok()
}
}
Getting error such as:
ReferenceError: label is not defined
I think I found out the problem, I had not defined the
const label = Selector('label');
I see no label definition in your example. You can try to rewrite your eligibleAccountType constructor by using Selector:
class eligibleAccountType {
constructor (text) {
this.label = Selector(...).withText(text);
this.checkbox = Selector(...).find('input[type=checkbox]');
}
}
In this situation it may be useful to check the markup of required elements. Please refer to the "TestCafe Examples" repository: https://github.com/DevExpress/testcafe-examples/blob/master/examples/element-properties/check-element-markup.js
Update:
and now I see that my list is actually not even building and I get this error " 1) TypeError: programOptionsSubscriptionRulesPage.eligibleAccountTypeList is not iterable"
It seems like you have a naming mistake in your loop:
for (const eligibleAccountType of programOptionsSubscriptionRulesPage.eligibleAccountTypeList) {
According to your ProgramOptionsSubscriptionRulesPage class definition, the list name should be eligibleAccountTypesList (with the "s" character).
I'm teaching myself elm and see (of course) many references to Html msg--
I understand that this is a 'parameterised type', that is, that (as I understand it), the constructor of the type Html takes a parameter -- much as List Char does.
OK. But then in following along in some tutorials, I see they quickly change the msg to be a custom type, often something like this (I'm doing this from memory, so please forgive me):
Html Msg
where Msg might be defined as
type Msg =
Something | SomethingElse
I also see that here -- https://discourse.elm-lang.org/t/html-msg-vs-html-msg/2758 -- they say
The lower case msg is called a type variable. The upper case Msg is called a concrete type - its something your application has defined.
That answers my question part way, but could someone elaborate on what this means exactly? Because when I see something like List String, I understand what String is in this context, but I don't understand what msg means in Html msg.
Also, are we not changing the type of the return value of the function? That is, if the Elm runtime is expecting view to return a certain type, and that type is Html msg, if we change the return type to Html Whatever, why does that work? (For instance, we can't change a function's return value from List String to List Number arbitrarily, right?)
Coming from a background in OOP and typed languages like C, TypeScript, etc, I would think that any Msg would need to somehow be related to msg, that is 'extend' it in some way to allow polymorphism. Obviously I'm looking at this the wrong way, and any explanation would be appreciated!
tl;dr: as long as every function agrees on the type of msg, and model, they can be anything you want.
Just like the type parameter of List, msg can be anything. There is no restriction. To demonstrate, here's the classic increment/decrement example with the msg being just an Int:
-- These type aliases aren't needed, but makes it easier to distinguish
-- their roles in later type signatures
type alias Model = Int
type alias Msg = Int
update : Msg -> Model -> Model
update msg model =
model + msg
view : Model -> Html Msg
view model =
div []
[ button [ onClick 1 ] [ text "+1" ]
, div [] [ text <| String.fromInt model.count ]
, button [ onClick (-1) ] [ text "-1" ]
]
main : Program () Model Msg
main =
Browser.sandbox { init = 0, view = view, update = update }
So how does that work? The key is in the Browser.sandbox function, as that's what connects everything together. Its type signature is:
sandbox :
{ init : model
, view : model -> Html msg
, update : msg -> model -> model
}
-> Program () model msg
where model and msg are type variables. Both of these can be replaced with any concrete type, as long as it matches up with the constraints given in this signature, which is that they must be the same across the init, view and update functions. Otherwise we'll get a type error here. If, for example, update requires a String instead of an Int, we'll get the error:
This argument is a record of type:
{ init : Model, update : String -> Model -> Model, view : Model -> Html Msg
}
But `sandbox` needs the 1st argument to be:
{ init : Model
, update : String -> Model -> Model
, view : Model -> Html String
}
It doesn't really know which is right or wrong, of course, just that there's a mismatch. But in an effort to be helpful it assumes String is right and expects view to return Html String instead.
I hope that sufficiently answers your question. If not, comment and subscribe!
Short and Simple Explanation:
I have an xmas function like this:
wrapPresents : presents -> String
But what is the type, presents? It can be anything that you want! You can substitute it with your own 'concrete type'. presents is just a place holder!
wrapPresents : Boxes -> String
Now we have the "type" - we know that the first parameter must be a Box type.
wrapPresents boxes = "All the boxes are now wrapped!"
Summary: msg (lowercase) is simply a placeholder
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) )
I need to pass a few parameters to Javascript, but for a strange reason it does not compile. I started with:
port check : String -> Cmd msg
this works fine (as taken directly from JavaScript Interop). But when I am adding another parameter
port check : Int -> String -> Cmd msg
I am getting
1| port check : Int -> String -> Cmd msg
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You are saying it should be:
Int -> String -> Platform.Cmd.Cmd msg
But you need to use the particular format described here:
http://guide.elm-lang.org/effect_managers/
I solved this simply by reducing arguments back to one
type alias Bundle = (Int, String)
port check : Bundle -> Cmd msg
but that would be cleaner if I could simply do
app.ports.check.subscribe(function(arg1, arg2) {
});
Why it doesn't compile?
ports can only have one parameter. If you need to pass multiple parameters then your only options are to pass a tuple (like you did with Bundle) or a record.
On the js side, you'll have to take one parameter but you can destructure it after with whatever var names you want
app.ports.check.subscribe(function(arg) {
var [arg1, arg2] = arg;
});
If you're using ES6, then you do have some nicer options for destructuring in the function params if you use a record like this:
app.ports.check.subscribe(function({arg1: arg1, arg2: arg2}) {
});
I noticed you don't really have to "decode" much on the Javascript side. So, you can package your single parameter up as a nice type in ELM.
Do something like this in your elm:
type alias MyObject =
{ name: String
, displayDate: String
, subTitle: String
, hashTag: String
}
port check : MyObject -> Cmd msg
Then in the javascript you can just do this:
app.ports.check.subscribe(function(myObject) {
alert( myObject.name);
alert( myObject.displayDate);
alert( myObject.subTitle);
alert( myObject.hashTag);
});