Can you use a string for record access in elm? - elm

If I have a string that looks like the name of a field in a record, can I use it to get the data somehow? Something like :
."name".toKey bill
bill.(asSymbol "name")
-
song =
{ title = "foot", artist = "barf", number = "13" }
fieldsILike : List String
fieldsILike =
[ "title", "artist" ]
val song key =
.key song
foo = List.map (val song) fieldsILike --> should be ["foot", "barf"]

No, but you could use a Dict
import Dict exposing (get ,fromList)
song = fromList [ ("title", "foot"), ("artist", "barf") ]
get "title" song -- Just "foot"
get "artist" song -- Just "barf"
get "test" song -- Nothing

Not the way you want it but you can have a function that pattern matches on a string to access a part of a record. You need to be explicit about what it should do in case you give it something invalid.
import Html exposing (..)
type alias Song = { artist : String, number : String, title : String }
song : Song
song =
{ title = "foot", artist = "barf", number = "13" }
fieldsILike : List String
fieldsILike =
[ "title", "artist" ]
k2acc : String -> (Song -> String)
k2acc key =
case key of
"title" -> .title
"artist" -> .artist
"number" -> .number
_ -> always ""
val : Song -> String -> String
val = flip k2acc
-- `flip` was removed in elm 0.19, so you'll need to
-- do something like the following going forward:
-- val song field = (k2acc field) song
foo = List.map (val song) fieldsILike
main = text <| toString foo

I solved this broblem by adding attribute's name and explicit type declaration in AttrValue type:
type alias TypedRecord =
List Attr
type alias Attr =
( String, AttrValue )
type AttrValue
= String String
| Int Int
| Record (List Attr)
So now i can retrieve attributes by "key" (even "key.key" for nested) from my TypedRecord type:
getAttrByKey : String -> TypedRecord -> Maybe Attr
getAttrByKey searchKey item =
-- imitation of searching for attributes likewise in JS Record
let
checkAttrKey =
\k attr ->
first attr == k
in
case String.split "." searchKey of
[ key ] ->
List.head <| List.filter (checkAttrKey key) item
key :: rest ->
case List.head <| List.filter (checkAttrKey key) item of
Just attr ->
case attr of
( _, Record subAttr ) ->
getAttrByKey (String.join "." rest) subAttr
( _, _ ) ->
Nothing
Nothing ->
Nothing
[] ->
Nothing
And conver it to String by checking Attr type and calling respected Elm String module function:
attrToString : Maybe Attr -> String
attrToString is_attr =
case is_attr of
Nothing ->
""
Just attr ->
case second attr of
String value ->
value
Int value ->
String.fromInt value
Record attrs ->
List.map (\a -> Just a) attrs
|> List.map (\a -> attrToString a)
|> String.join " "
These examples for String Int and Record Elm types, but it is also can be extended fo Float Bool and Array.
You can check src/lib/TypedRecord.elm file for another functions and even Json Decoder in my example app repository

Related

Kotlin: How to convert list of Grandparents, parents, children to map with list of parents, children?

I am attempting to flatten and unflatten a tree structure to enable me to persist the tree structure within an sqlite database.
the tree structure is not in my control.
the tree can contain nodes that are have no children, nodes that have multiple children and nodes that have multiple children with children.
for example
A
B -> C
D -> E -> F
G
H -> I
|
-> J
|
-> K
L -> M
|
-> N
|
-> O
|
-> P
|
-> Q
In the above example
A has no children
B has one child C
D has two children E & F
G has no children
H has one child I & two grandchildren J & K
I has two children J & K
L has one child I & one grandchild N & three great grandchildren O, P, Q
each entry in the tree is persisted in my data base as a single row
#Entity(
tableName = "tree_data_table",
indices = [
Index(value = ["id", "parent_id"], unique = false),
Index(value = ["id"], unique = false),
]
)
data class TreeDataDO(
#ColumnInfo(name = "id") val id: String,
#ColumnInfo(name = "is_leaf") val isLeaf: Boolean,
#ColumnInfo(name = "name") val name: String,
#ColumnInfo(name = "parent_id") val parentId: String?
) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "tree_data_local_id")
var treeDataLocalId: Long = 0L
}
I would like to transform the above tree in the following map
{A=[], B=[C], D=[E, F], G=[], H=[I, J, K], L=[M, N, O, P, Q]}
The closest I have got is this
rows.map { (if (it.parentId == null) it.id else it.parentId) to TREE_DATA_UI_MAPPER.map(it) }.groupBy({ it.first }, { it.second })
however this "solution" gives the following map
{A=[], B=[C], D=[E, F], G=[], H=[I], I=[J, K], L=[M], N=[O, P, Q]}
what am i missing?
how can i achieve the desired map for the list of distinct rows?
Assume you have an item, like this
Item(val id: String, val parentId; String?)
And what you want is to get this structure:
Map<String, List<Item>>()
where you will have a list of all children of parent items (or top items, where parentId == null).
I think it can be achieved with code like this:
private val items: List<Item> = ...
fun map(): Map<String, List<Item>> {
val result = Map<String, List<Item>>()
val topItems = items.filter { it.parentId == null }
topItems.forEach {
val ancestors = mutableListOf<Item>()
getAllAncestors(it.id, ancestors)
result[it.id] = ancestors
}
return result
}
fun getAllAncestors(itemId: String, into: MutableList<Item>) {
items.forEach {
if (itemId == it.parentId) {
into.add(it)
into.addAll(getAllAncestors(it.id, into))
}
}
}

Efficiently matching two Flux

I have two Flux with 2 different data types as shown below:
Flux<Dog> dogs = loadDogsFromFile()
Flux<Man> men = loadMenFromFile()
data class Dog(
val name: String,
val ownerName: String,
)
data class Man(
val name: String,
val dogOwnerName: String,
)
As you can see the one field we can use to match these two Flux objects is dogOwnerName. Right now this is how I am comparing them
val disposable = dogs.flatMap { dog ->
men.map { man->
val isEqual = comparator(dog, man)
Triple(dog, man, isEqual)
}
}.filter {x -> x.third === true }
This gets the job done but it is nowhere efficient, it keeps looping even after the desired fields are found and because of that, we have to use a filter operator to only get what we need.
Edit
Based on #marstran comment on the user input, I have large JSON files that contain dogs and men that I'm loading here:
Flux<Dog> dogs = loadDogsFromFile()
Flux<Man> men = loadMenFromFile()
After matching the dogs to their owners/men I'm building an object that I'm saving to the database like this:
val disposable = dogs.flatMap { dog ->
men.map { man->
val isEqual = comparator(dog, man)
Triple(dog, man, isEqual)
}
}.filter {x -> x.third === true }
.map{(dog,man,isEqual) ->
DogOwner(man,dog)
}.doOnNext{dogOwner -> dogOwnerRepository.save(dogOwner)}
Consider using method take(long n, boolean limitRequest) from Flux:
public final Flux<T> take(long n,
boolean limitRequest)
Take only the first N values from this Flux, if available.
using it will allow you to break iterating over man once owner would be found.
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#take-long-boolean-
Take a look at this java example. I assume that dog and his owner have same value.
#Test
void dog_owner_test() {
int COUNT = 10000;
Flux<Integer> dogs = Flux.range(0, COUNT);
Flux<Integer> man = Flux.range(0, COUNT);
dogs.flatMap(
dog ->
man.map(m -> Tuples.of(dog, m))
.filter(tuple -> tuple.getT1().equals(tuple.getT2()))
.map(tuple -> new DogOwner(tuple.getT1(), tuple.getT2()))
.take(1, true)
)
.doOnNext(System.out::println)
// here you can save DogOwner to db
.as(StepVerifier::create)
.expectNextCount(COUNT)
.verifyComplete();
}
private static class DogOwner {
DogOwner(Integer dog, Integer owner) {
this.dog = dog;
this.owner = owner;
}
Integer dog;
Integer owner;
#Override
public String toString() {
return "DogOwner{" +
"dog=" + dog +
", owner=" + owner +
'}';
}
}

How to convert Html.Html msg to Html.Html Msg?

I'm trying to write a TabControl in Elm as follows
module TabControl exposing (..)
import Html
import Html.Attributes as Attributes
import Html.Events
import Array
type Msg
= OnSelectedTabChanged
| NoOp
type alias Model msg =
{ tabs : List (Tab msg)
, selectedIndex : Int
}
constructor : Model msg
constructor =
{ tabs = []
, selectedIndex = 0
}
type alias Tab msg =
{ title : String
, content : Html.Html msg
}
withTab : Tab msg -> Model msg -> (Model msg)
withTab tab model =
{ model | tabs = model.tabs ++ [tab] }
render : Model msg -> Html.Html msg
render model =
let
header = renderTabHeaders model
content = renderSelectedTabContent model
in
Html.div [] [ header, content ]
renderTabHeaders : Model msg -> Html.Html msg
renderTabHeaders model =
Html.div []
[
Html.ul []
(
model.tabs
|> List.map (\(tab) -> renderTabHeader tab)
)
]
renderTabHeader : Tab msg -> Html.Html msg
renderTabHeader tab =
Html.li [Html.Events.onClick OnSelectedTabChanged] [Html.text tab.title]
renderSelectedTabContent : Model msg -> Html.Html msg
renderSelectedTabContent model =
let
array =
Array.fromList model.tabs
item =
Array.get model.selectedIndex array
in
case item of
Just value ->
value.content
Nothing ->
Html.text ""
which is rendered by
module Main exposing (..)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import TabControl
main =
Browser.sandbox { init = 0, update = update, view = view }
view model =
div []
[
TabControl.constructor
|> TabControl.withTab (TabControl.Tab "title 1" (Html.text "html 1"))
|> TabControl.withTab (TabControl.Tab "title 2" (Html.text "html 2"))
|> TabControl.render
]
However, I can't seem to get the return types correct.
If I refactor renderTabHeader : Tab msg -> Html.Html msg to renderTabHeader : Tab msg -> Html.Html Msg then
Something is off with the body of the `renderTabHeaders` definition:
This `div` call produces:
Html.Html Msg
But the type annotation on `renderTabHeaders` says it should be:
Html.Html msg
if I don't then
Something is off with the body of the `renderTabHeader` definition:
This `li` call produces:
Html.Html Msg
But the type annotation on `renderTabHeader` says it should be:
Html.Html msg
If I make every function return Html.Html Msg then
Something is off with the 1st branch of this `case` expression:
67| value.content
^^^^^^^^^^^^^
The value at .content is a:
Html.Html msg
But the type annotation on `renderSelectedTabContent` says it should be:
Html.Html Msg
How can I have elements in a div, some of which return Html.Html msg and some of which return Html.Html Msg? Or alternatively, how can I convert between Html.Html msg and Html.Html Msg
If you're wanting to expose your Msg type, you'll need the user of your module to supply a function which translates your Msg into their msg.
So, if the top-level Msg type looks like this:
type Msg
= NoOp
| TabControlMsg TabControl.Msg
| SomethingElse
Then render can take a function which accepts a TabControl.Msg and returns a Msg, which, in this case, would be the TabControlMsg constructor.
view model =
div []
[
TabControl.constructor
|> TabControl.withTab (TabControl.Tab "title 1" (Html.text "html 1"))
|> TabControl.withTab (TabControl.Tab "title 2" (Html.text "html 2"))
|> TabControl.render TabControlMsg
]
Then you would adjust the code in your module to use that function:
render : (Msg -> msg) -> Model msg -> Html.Html msg
render toMsg model =
let
header = renderTabHeaders model
content = renderSelectedTabContent toMsg model
in
Html.div [] [ header, content ]
renderSelectedTabContent : (Msg -> msg) -> Model msg -> Html.Html msg
renderSelectedTabContent toMsg model =
let
array =
Array.fromList model.tabs
item =
Array.get model.selectedIndex array
in
case item of
Just value ->
Html.map toMsg value.content
Nothing ->
Html.text ""
Ah.. so I can map value.content to return a Msg like so
renderSelectedTabContent : Model msg -> Html.Html Msg
renderSelectedTabContent model =
let
array =
Array.fromList model.tabs
item =
Array.get model.selectedIndex array
in
case item of
Just value ->
Html.map (\_ -> NoOp) value.content
Nothing ->
Html.text ""

Kotlin replace multiple words in string

How to replace many parts of a string with something else in Kotlin using .replace()
For example, we can do it only by replacing one word
fun main(args: Array<String>) {
var w_text = "welcome his name is John"
println("${w_text.replace("his","here")}")
}
and the result will be " welcome here name is John " .
finally we need the result be " welcome here name is alles "
by replacing his to here and john to alles using .replace()
You can do it using multiple consecutive calls to replace():
w_text.replace("his", "here").replace("john", "alles")
You could write an extension that overloads String::replace:
fun String.replace(vararg replacements: Pair<String, String>): String {
var result = this
replacements.forEach { (l, r) -> result = result.replace(l, r) }
return result
}
fun main(args: Array<String>) {
val sentence = "welcome his name is John"
sentence.replace("his" to "here", "John" to "alles")
}
If you have many of those replacement rules, then create a mapping of them and call the replace method in a loop:
val map = mapOf("his" to "here", "john" to "alles", ...)
val sentence = "welcome his name is John"
var result = sentence
map.forEach { t, u -> result = result.replace(t, u) }
println(result)
For the ones interested in replacing a map of values in a text:
private fun replaceText(text: String, keys: Map<String, String>): String =
val replaced = map.entries.fold(text) { acc, (key, value) -> acc.replace(key, value) }
Here is a one liner:
fun String.replace(vararg pairs: Pair<String, String>): String =
pairs.fold(this) { acc, (old, new) -> acc.replace(old, new, ignoreCase = true) }
Test:
#Test fun rep() {
val input = "welcome his name is John"
val output = input.replace("his" to "her", "john" to "alles")
println(output)
output shouldBeEqualTo "welcome her name is alles"
}
Similar to other responses but using Kotlin extension and overloading String::replace to accept a map of oldValue to newValue.
fun String.replace(mapping: Map<String, String>): String {
var str = this
mapping.forEach { str = str.replace(it.key, it.value) }
return str
}
Usage:
val mapping = mapOf("his" to "here", "John" to "alles")
"his dad is John".replace(mapping) // here dad is alles
The issue with just using replace without any regex is:
Let's say I want to replace the occurrence of "here" with "there" inside the string "Where is my bag? Your bag is here." As you can imagine the result will be "Wthere is my bag? Your bag is there." which will not be correct. The solution is to use a regex like given below.
var str = "Where is my bag? Your bag is here."
val replacements = setOf("\\bhere\\b" to "there",
"\\bjohn\\b" to "alles")
replacements.forEach {
str = str.replace(Regex(it.first), it.second)
}

Making HTTP call to modify data with elm

As pictured here, I've created a small sample program that has some controls to modify parts of the model.
What I've been unsuccessfully trying to do is to make a HTTP request to get the initial data (it's hardcoded right now), or later on replace the data with the response from said HTTP request when a Reset message is received. I did read the HTTP chaper of the introduction to Elm, but I can't seem to piece things together.
The goal is to have a loadTraits function that takes a String (SomeId) and returns a List of type TraitWithRelevance, so I can replace the model with that incoming data.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode as Decode
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
}
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg" } Nothing, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "some default id"
, traits =
[ ( "name for trait a", "a", "1" )
, ( "this is the name for trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| Reset
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
let
_ =
Debug.log "model: " model
in
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
Reset ->
-- ( replaceTraits model <| loadTraits model.contentWithTraits.someId, Cmd.none )
{-
NOTE: I'm stuck here...
should make HTTP GET request, then replace the model.contentWithTraits.traits with the decoded JSON's traits field
-}
( model, Cmd.none )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
_ =
Debug.log "updatedTrait: " updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text someId
, valueRange "range" trait
, valueRange "number" trait
, text name
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 300 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
]
Here's an example response from the http server. I've chosen this format, because I thought it'd map easiest to the elm model. I can easily change the response if there's a better way to consume this data in elm.
{"traits":[["name for trait a","a",1],["this is the name for trait b,"b",50]]}
P.S. Even though there's plenty of lines of code, please be aware that I tried to strip down the problem as much as possible, while keeping enough context.
You need to have a message which returns the data. Assuming it's a simple list of Traits:
type Msg
= EditTrait SomeId Relevance
| Reset
| OnFetchTraits (Result Http.Error (List Traits))
Then you need a command to send the request, something like
fetchTraits : Cmd Msg
fetchTraits =
Http.get "http://localhost:4000/traits" traitListDecoder
|> Http.send OnFetchTraits
and you need to implement traitListDecoder, to decode your JSON into the list that is returned in the msg.
Then instead of returning Cmd.none in your update function where you are stuck, you return fetchTraits. Elm will then make the request and you will get an OnFetchTraits msg passed into update. You need a separate case to handle this. You unpack the Result type and extract your data if the request was successful, or handle the error if not.
So basically you need to do two things, the reset button should call a command to get the traits. Then, in the update you have to handle the response from the command. After getting the Result back you can use it to update your model.
Here is an update to your code. I added a person to the model which gets updated when the user presses the reset button.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode exposing (Decoder, string)
import Json.Decode.Pipeline exposing (decode, required)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- Commands
type alias Person =
{ name : String
, gender : String
}
decodePerson : Decoder Person
decodePerson =
decode Person
|> required "name" string
|> required "gender" string
getTraits =
let
url =
"http://swapi.co/api/people/1/"
request =
Http.get url decodePerson
in
Http.send GetTraitResponse request
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
, person : Person
}
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg"} Nothing {name = "", gender=""}, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "some default id"
, traits =
[ ( "name for trait a", "a", "1" )
, ( "this is the name for trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| GetTraitResponse (Result Http.Error Person)
| Reset
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
let
_ =
Debug.log "model: " model
in
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
-- handle the response
GetTraitResponse resp ->
let
_ =
Debug.log "response" resp
person =
case resp of
Ok val ->
val
Result.Err e ->
{name = "", gender=""}
in
( {model | person = person }, Cmd.none )
Reset ->
-- ( replaceTraits model <| loadTraits model.contentWithTraits.someId, Cmd.none )
{-
NOTE: I'm stuck here...
should make HTTP GET request, then replace the model.contentWithTraits.traits with the decoded JSON's traits field
-}
-- call the command to get the traits
( model, getTraits )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
_ =
Debug.log "updatedTrait: " updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text someId
, valueRange "range" trait
, valueRange "number" trait
, text name
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 300 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
, text <| toString model
]
While both answers have been correct and helpful, it took me still another mile to reach my destination.
First off, I didn't want to rely on a non elm-lang package.
Json.Decode.Pipeline therefore doesn't meet that requirement.
With the examples posted, I still had the trouble of decoding the list of traits. What I ended up doing was changing the response from the server so that the traits list is not of type list, but of type object with name, someId, and relevance as keys and its values as strings. This helped me distinguish between what's coming back from the server and what's being represented internally in my elm model.
So the Reset functionality works nicely, the next step is to get those values into the initial state of the model without user interaction.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode as Decode exposing (Decoder)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
}
type alias TraitInfo =
{ traits :
List TraitObject
}
type Traits
= TraitInfoFromServer (List TraitObject)
type alias TraitObject =
{ name : String, someId : String, relevance : String }
fetchTraits : String -> Cmd Msg
fetchTraits someId =
Http.get
("http://localhost:8000/traits/" ++ someId)
decodeTraits
|> Http.send OnFetchTraits
decodeTraits : Decoder TraitInfo
decodeTraits =
Decode.map TraitInfo
(Decode.field "traits" (Decode.list decodeTrait))
decodeTrait : Decoder TraitObject
decodeTrait =
(Decode.map3 TraitObject
(Decode.field "name" Decode.string)
(Decode.field "someId" Decode.string)
(Decode.field "relevance" Decode.string)
)
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg" } Nothing, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "someIdToStartWith"
, traits =
[ ( "trait a", "a", "1" )
, ( "trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| Reset
| OnFetchTraits (Result Http.Error TraitInfo)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
Reset ->
( model, fetchTraits model.contentWithTraits.someId )
OnFetchTraits resp ->
let
newTraits =
case resp of
Ok val ->
val.traits
Result.Err e ->
[]
in
( { model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = List.map traitObjToTuple newTraits
}
}
, Cmd.none
)
traitObjToTuple : TraitObject -> TraitWithRelevance
traitObjToTuple obj =
( obj.name, obj.someId, obj.relevance )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text name
, valueRange "range" trait
, valueRange "number" trait
, text someId
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 100 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
]