I am trying to learn elm from the past week and want build a simple Hacker News client by calling the official Hacker News API.
I'm calling https: //hacker-news.firebaseio.com/v0/topstories.json to get the top stories which would return an array of story Ids. Once I have the Ids I need to make subsequent calls to https ://hacker-news.firebaseio.com/v0/item/[/* Id goes here */].json fetch the details of each story item.
I have a Main.elm file which would fetch list of top stories.
type Msg = Request
| RequestSuccess (List Int)
| RequestFail Http.Error
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Request ->
(model, getTopNews)
RequestSuccess list->
(Model list, Cmd.none)
RequestFail error->
(Model [], Cmd.none)
Next part is where I am confused, fetching details for each of the item returned. I also have a NewsItem component to display the details of each news item.
How can I solve this, by creating union types inside NewsItem component(child component) to fetch details? If thats how I should do it..
How can I can call the fetch details api from the NewsItem component as soon as the first api call inside Main.elm is complete?
Or am I missing something obvious here? That's not the correct approach at all?
You can see what I have tried so far here.
Here's my recommendation. It assumes that you'll be loading each NewsItem independently and that they can all fail independently as well. If this isn't the case then we can definitely come up with something that works better for you.
1) Represent your NewsItem not as just a record but a record wrapped in a type to represent the possibility that loading the details could fail. See http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipattern.html for more info.
module NewsItem
-- imports and such
type Model = Loading | Success NewsItem | Failure Http.Error
2) Write an init function in your NewsItem module that accepts an Int and returns a (NewsItem.Model, Cmd NewsItem.Msg) pair
init : Int -> (Model, Cmd Msg)
init newsItemId =
( Loading
, getNewsItem newsItemId |> Task.perform GetItemFailure GetItemSuccess
)
3) In your Main module, once you've fetched your list of IDs, map them onto a List (NewsItem.Model, Cmd NewsItem.Msg) using your init function and use the techniques of the Elm architecture to store them as children in your parent model. I recommend storing them as a Dict Int NewsItem.Model which maps ID onto child model.
RequestSuccess list ->
let
children =
list |> List.map (\id -> (id, NewsItem.init id))
childModels =
children
|> List.map (\(id, (model, cmd)) -> (id, model))
|> Dict.fromList
childCmds =
children
|> List.map (\(id, (model, cmd)) -> Cmd.map (NewsItemMsg id) cmd)
|> Cmd.batch
in
(Model childModels, childCmds)
Related
I have a component created in Elm where users can create a list of different criteria. For this component, users should be able to create criteria to search for contacts. These contacts will be shown on a different (non-Elm) page. If the users return to the criteria builder, the previous filled in criteria should be shown again.
To do this, I use the JSON that was used to create the query in Elm. This should be decoded to create the objects that will show the input that the user has made before.
One of the objects I use is a list. This list contains of tuples with id and name. For the query builder, I only send the id of the objects in the JSON to the back-end. This means that, if a user returns to the criteria builder, the decoder can only decode the list of id's. For my list selection, I also want to fetch the names of the objects with the id's.
Now this is where I have some problems. To make an http-request, I have to catch the result with an Cmd.Msg. In the update function, I must then update my Model. The problem is, I don't have a model yet, because I'm still decoding my initial model. Also, I guess using a Decoder (for the result of the http-request) within a Decoder (for my initial model) is not the best of ideas.
Is there a way to solve this problem where I am making an http-request within a Decoder for my initial Model?
There are a handful of options here, depending on how complicated your situation is. At the end of the day, init produces a (Model, Cmd Msg) value, and your HTTP request(s) can be part of that Cmd.
Option 1: Generate HTTP requests based on the decoded model
init : Decode.Value -> (Model, Cmd Msg)
init json =
let
initialRequests model =
List.map initialRequest model.items
in
case Decode.decodeValue flagsDecoder json of
Ok model ->
( model, initialRequests model |> Cmd.batch )
Err _ ->
( initialModel, Cmd.none )
Option 2: Generate HTTP requests while decoding
init : Decode.Value -> ( Model, Cmd Msg )
init json =
let
flagsDecoder =
Decode.map (\items -> ({ items = List.map Tuple.first items }, List.map Tuple.second items))
(Decode.field "items" (Decode.list itemDecoder))
itemDecoder =
Decode.field "name" Decode.string
|> Decode.map (\name -> (name, initialRequest name))
in
case Decode.decodeValue flagsDecoder json of
Ok ( model, cmds ) ->
( model, cmds |> Cmd.batch )
Err _ ->
( initialModel, Cmd.none )
Your init function will need to be able to return a Model value that is "incomple", waiting on these responses to come back. Sometimes that's as simple as modeling values that you're waiting on as Maybe values, sometimes you might want to wrap a large portion of your model in a value that indicates if it's valid or not.
type Model
= Initializing String (Maybe Thing1) (Maybe Thing2)
| Ready { baseUrl : String, thing1 : Thing1, thing2 : Thing2 }
I am working through the elm guide.
In the effects subchapter there is an example with a Time-subscription
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every second Tick
and an example which handles Web-Sockets-subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
WebSocket.listen "ws://echo.websocket.org" NewMessage
But in these examples, there is only ever one subscription.
How could I handle multiple subscriptions?
You may use Sub.batch, providing a list of subscriptions, it returns a batched subscription
Reference:
Elm 0.19 docs
Pre-0.19 docs
I am using Hop at the moment and I am wondering how you can route to another page outside the main / app module. If you follow the Hop documentation, there are two specific types of messages, well - they are called that in the docs, NavigateTo and SetQuery. How do you raise those messages from sub-modules?
I have tried the following:
view =
button [ onClick (Main.Types.NavigateTo "test") ] []
However, this would mess up the typing.
The problem wasn't with Hop, but my understanding of how parent-child communication works within Elm. To say lightly, you need to be careful for what and when you use this type of communication, but in my case, I read a few good blog posts by Brian Thicks and Alex Lew talking about this form of communication. Especially Alex' post is typical for my use case.
What I did is the following: I added a separate update statement for the type of message I want to route. This is not the best implementation and it can be done more elegantly like Alex describes with the translator pattern.
update msg model =
case msg of
NavigateTo path ->
let
command =
Hop.outputFromPath hopConfig path
|> Navigation.newUrl
in
( model, command )
SetQuery query ->
let
command =
model.address
|> Hop.setQuery query
|> Hop.output hopConfig
|> Navigation.newUrl
in
( model, command )
ExampleMsg InterestingMsg exampleInteger ->
update (NavigateTo "<path here>") model --Update model in let-statement
ExampleMsg subMsg ->
let
( updatedExampleModel, pageCmd ) =
Page.Example.State.update subMsg model.exampleModel
in
( { model | exampleModel = updatedExampleModel }, Cmd.map ExampleMsg pageCmd )
I was looking at some Elm examples and I found "Example 3: A Dynamic List of Counters". I wanted to alter it to include checkboxes, so a single list with counters and/or checkboxes. Each having their own actions and model (such as a name, and a checked bool). On top of that I would like to order the list, so that checked checkboxes all appear at the top, unchecked at the bottom and the counters in the middle.
I couldn't find any examples on how to do and structure this though. Any help?
Note: I'm very new to Elm (and functional programming in general).
Thanks!
One way to achieve this is to use a union type for the item (untested, I don't have a Elm environment right now to check this):
type Item = Checkbox String Bool | Counter
type alias Model =
{ items : List ( ID, Item )
, nextID : ID
}
Depending on the type of item, you'd then render it as
viewItem item =
case item of
Counter value -> renderCounter value
CheckBox name state -> renderCheckbox name state
All your view has to do is call viewItem for each Item, sort the items and put everything in a container (e.g. a div):
view address model =
let
items = List.map (viewItem address) model.items
|> List.sort (your custom sorting here)
in
div [] ([remove, insert] ++ items)
I would like to return a DTO from my data layer which would also contain child collections...such as this:
Audio
- Title
- Description
- Filename
- Tags
- TagName
- Comments
- PersonName
- CommentText
Here is a basic query so far, but i'm not sure how to transform the child collections from my entity to the DTO.
var query = Session.CreateCriteria<Audio>("audio")
.SetProjection(
Projections.ProjectionList()
.Add(Projections.Property<Audio>(x => x.Title))
.Add(Projections.Property<Audio>(x => x.Description))
.Add(Projections.Property<Audio>(x => x.Filename))
).SetResultTransformer(new AliasToBeanResultTransformer(typeof(AudioDto)))
.List<AudioDto>();
Is this even possible, or is there another reccomended way of doing this?
UPDATE:
Just want to add a little more information about my scenario...I want to return a list of Audio items to the currently logged in user along with some associated entities such as tags, comments etc...these are fairly straight forward using MultiQuery / Future.
However, when displaying the audio items to the user, i also want to display 3 other options to the user:
Weather they have added this audio item to their list of favourites
Weather they have given this audio the 'thumbs up'
Weather the logged in user is 'Following' the owner of this audio
Favourites : Audio -> HasMany -> AudioUserFavourites
Thumbs Up : Audio -> HasManyToMany -> UserAccount
Following Owner : Audio -> References -> UserAccount ->
ManyToMany -> UserAccount
Hope this makes sense...if not i'll try and explain again...how can I eager load these extra details for each Audio entity returned...I need all this information in pages of 20 also.
I looked at Batch fetching, but this appears to fetch ALL thumbs ups for each Audio entity, rather than checking if only the logged in user has thumbed it.
Sorry for rambling :-)
Paul
If you want to fetch your Audio objects with both the Tags collection and Comments collections populated, have a look at Aydende Rahien's blog: http://ayende.com/blog/4367/eagerly-loading-entity-associations-efficiently-with-nhibernate.
You don't need to use DTOs for this; you can get back a list of Audio with its collections even if the collections are lazily loaded by default. You would create two future queries; the first will fetch Audio joined to Tags, and the second will fetch Audio joined to Comments. It works because by the time the second query result is being processed, the session cache already has the Audio objects in it; NHibernate grabs the Audio from the cache instead of rehydrating it, and then fills in the second collection.
You don't need to use future queries for this; it still works if you just execute the two queries sequentially, but using futures will result in just one round trip to the database, making it faster.