Elm browser application not displaying - elm

I copied the HTML from here, and the Elm code from here. The only change I made to the Elm code was the addition of the first line - module Main exposing (..). My IDE was complaining. Yet when I open index.html in a browser, I get a blank screen and the title of the page is still "Main". What am I doing wrong?
Here is my project structure
new-project
elm-stuff
src
Main.elm
elm.json
index.html
main.js
Here is index.html:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Main</title>
<script src="main.js"></script>
</head>
<body>
<script>var app = Elm.Main.init();</script>
</body>
</html>
Here is Main.elm:
module Main exposing (..)
import Browser
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Url
-- MAIN
main : Program () Model Msg
main =
Browser.application
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
, onUrlChange = UrlChanged
, onUrlRequest = LinkClicked
}
-- MODEL
type alias Model =
{ key : Nav.Key
, url : Url.Url
}
init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
( Model key url, Cmd.none )
-- UPDATE
type Msg
= LinkClicked Browser.UrlRequest
| UrlChanged Url.Url
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LinkClicked urlRequest ->
case urlRequest of
Browser.Internal url ->
( model, Nav.pushUrl model.key (Url.toString url) )
Browser.External href ->
( model, Nav.load href )
UrlChanged url ->
( { model | url = url }
, Cmd.none
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
-- VIEW
view : Model -> Browser.Document Msg
view model =
{ title = "URL Interceptor"
, body =
[ text "The current URL is: "
, b [] [ text (Url.toString model.url) ]
, ul []
[ viewLink "/home"
, viewLink "/profile"
, viewLink "/reviews/the-century-of-the-self"
, viewLink "/reviews/public-opinion"
, viewLink "/reviews/shah-of-shahs"
]
]
}
viewLink : String -> Html msg
viewLink path =
li [] [ a [ href path ] [ text path ] ]
EDIT per answer by #pdamoc. I am trying to use elm-live to compile and display the elm file. I am on Ubuntu 18.04.5 LTS, with npm version 6.14.9, node version v8.10.0.
I get this error using elm-live:
$ elm-live src/Main.elm --pushstate
events.js:239
throw new TypeError('"listener" argument must be a function');
^
TypeError: "listener" argument must be a function
at _addListener (events.js:239:11)
at Server.addListener (events.js:297:10)
at new Server (_http_server.js:269:10)
at Object.createServer (http.js:34:10)
at model (/usr/local/lib/node_modules/elm-live/lib/src/start.js:259:75)
at /usr/local/lib/node_modules/elm-live/node_modules/crocks/core/compose.js:8:14
at settle (/usr/local/lib/node_modules/elm-live/node_modules/crocks/Async/index.js:151:16)
at /usr/local/lib/node_modules/elm-live/node_modules/crocks/Async/index.js:27:62
at fork (/usr/local/lib/node_modules/elm-live/node_modules/crocks/Async/index.js:155:20)
at /usr/local/lib/node_modules/elm-live/node_modules/crocks/Async/index.js:224:16

You need a webserver that would serve the index.html on every path that is requested. The easiest way is to install elm-live globally and then start it like elm-live src/Main.elm --pushstate
Without serving index.html on every path (let's say you use live-server), if you navigate to an internal path and reload you will get a 404.

Related

Angular 6, catching ' error/not valid parameter ' in activated route

I am facing a problem with route parameter error catching. Here is the situation explained below.
The route params are as follows for displaying data in components of navbar:
http://localhost:4200/{ company_type }/{ company_name }/{ org-id }/{ component_name }
The website is opening even when I change the company_name to any string and company_id to null || 14cd156. I will get articles when I change company name in route. But, when I change id I get an error
core.js:1624 ERROR Error: Uncaught (in promise): HttpErrorResponse: {"headers":{"normalizedNames":{},"lazyUpdate":null},"status":404,"statusText":"Not Found","url" ...
The API doesn't check for company name. It only checks the company id company coming from route params. What I want to do is: to navigate to not-found page in case the company_name and company_id are invalid. Let's say,
company_type = consulting
company_name = ABC
id = 1
page=Article
In page Article when I change http://localhost:4200/consulting/ABC/5/articles to http://localhost:4200/consulting/3edsads/5/artciles the website shows data of Articles page. But, the data is route parameter is wrong.
articles.component.ts
getOrgArticles(page: number = 1) {
let queryParams = this.getQueryParams(page);
this.queryArticles =
this.service.getOrgArticles(queryParams).
subscribe((data: any) => {
this.page = page;
this.pageSize = queryParams['per-page'] || this.pageSize;
this.articles = this.articles.concat(data['articles']);
this.pageCount = data._meta.pageCount;
this.isLastPage() ? this.hideNextButton() : this.showNextButton();
this.totalCount = data._meta.totalCount;
},
error => {
});
}
service.ts
getOrgArticles(queryParams) {
const qpString = this.queryString(queryParams);
return this.http.get(`${this.api}/articles?${qpString}`);
}
I really wish to find some solution from you. Thank you

Sequence Http.get in Elm

Below I have a button that attempts to load remote content ...
import Post exposing (Post)
import Html exposing (..)
import Html.Events exposing (..)
import Http
import Json.Decode as Decode
type alias Model =
{ posts : List Post }
type Msg
= Search String
| PostsReceived (Result Http.Error (List Post))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Search s ->
let
cmd =
(Decode.list Post.decode)
|> Http.get ("/posts?author=" ++ s)
|> Http.send PostsReceived
in
( model, cmd )
PostsReceived (Ok posts) ->
{ model | posts = posts }
! []
PostsReceived (Err error) ->
( model, Cmd.none )
view : Model -> Html Msg
view model =
button
[ onClick (Search "amelia") ]
[ text "Read posts by Amelia" ]
This is a valid Elm program, only there's one little problem: The API doesn't allow me to search by string. This is not allowed
/posts?author=amelia => Malformed Request Error
However, this is allowed
/posts?author=2 => [ {...}, {...}, ... ]
So I must first fetch an author to get his/her id, and then I can fetch posts using the author's id...
/author?name=amelia => { id: 2, name: "amelia", ... }
/posts?author=2
How can I sequence one request after the next? Ideally I'd like to cache the authors somewhere in the model so we're only requesting ones that we haven't seen before.
You can use Task.andThen to chain two tasks together. Assuming that the /posts response includes the author ID, you can then add that author ID into you model when you handle the response.
Search s ->
let
getAuthor =
Author.decode
|> Http.get ("/author?name=" ++ s)
|> Http.toTask
getPosts author =
(Decode.list Post.decode)
|> Http.get ("/posts?author=" ++ author.id)
|> Http.toTask
cmd =
getAuthor
|> Task.andThen getPosts
|> Task.attempt PostsReceived
in
( model, cmd )
I've got this compiling at https://ellie-app.com/DBJc6Kn3G6a1 if that helps
You can chain together tasks using Task.andThen. You'll first have to convert the web requests to tasks using Http.toTask:
postsByAuthorName : String -> Cmd Msg
postsByAuthorName name =
Http.get ("/author?name=" ++ name) (Decode.field "id" Decode.int)
|> Http.toTask
|> Task.andThen (\id ->
Http.get ("/posts?author=" ++ toString id) (Decode.list decodePost)
|> Http.toTask)
|> Task.attempt PostsReceived
A a dictionary and a couple more Msg options should do it.
You'll have to write the decoder for the Author response, but other than that this should work
type alias Model =
{ posts : List Post
, authors : Dict String Int }
type Msg
= Search String
| SearchAuthor String
| AuthorReceived (Result Http.Error Int String)
| PostsReceived (Result Http.Error (List Post))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Search author ->
case (Dict.get author model.authors) of
Nothing ->
let
cmd =
(Decode.list Post.decode)
|> Http.get ("/author?name=" ++ author)
|> Http.send AuthorReceived
in
(model,cmd)
Just num ->
let
cmd =
(Decode.list Author.decode)
|> Http.get ("/posts?author=" ++ num)
|> Http.send PostsReceived
in
( model, cmd )
AuthorReceived (Ok number name) ->
let
updatedAuthors = Dict.inster name number model.authors
cmd =
(Decode.list Post.decode)
|> Http.get ("/posts?author=" ++ number)
|> Http.send PostsReceived
in
{model | authors = updatedAuthors } ! [cmd]
AuthorReceived (Err error) ->
(mode, Cmd.none )
PostsReceived (Ok posts) ->
{ model | posts = posts }
! []
PostsReceived (Err error) ->
( model, Cmd.none )
view : Model -> Html Msg
view model =
button
[ onClick (Search "amelia") ]
[ text "Read posts by Amelia" ]

Elm 0.18: How to route messages with Sub.map based on their content?

I have dynamically created Javascript components that communicate via ports with Elm model. They send data through a port of form port sendData : ((ComponentId, String) ...
On the elm side there is a ChildComponent module that represents their models:
type alias ComponentId =
Int
port sendData : ((ComponentId, String) -> msg) -> Sub msg
type Msg
= ProcessData String
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch [
sendData ProcessData
]
In the Parent I have:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ChildMessage componentId msg -> ...
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ Sub.map (ChildMessage componentId) (ChildComponent.subscriptions model.child) ]
Obviously this results in
Cannot find variable `componentId`
310| Sub.map (ChildMessage componentId) (ChildComponent.subscriptions (getChildModel (componentId model))) ]
^^^^^^^^^^^
How do I "extract" componentId from data coming from port? Or perhaps what I am doing is completely wrong?
Update
I have isolated the problem (with a few additions suggested by #ChadGilbert) into a project https://github.com/lguminski/ElmMultipleComponentsSubscription
When using Sub.map, you need a function that accepts a child model as its parameter. You could instead define ChildMessage as
-- in Parent.elm
type Model =
...
| ChildMessage Child.Model
Your parent subscriptions function will need to be updated:
-- in Parent.elm
subscriptions model =
Sub.batch
[ Sub.map ChildMessage (ChildComponent.subscriptions model.child) ]
When it comes to handling the data from the port in the child, you will need a Msg that has as its first parameter the same type defined in your port, mainly (ComponentId, String). Consider adding something like this to your child, where the new RouteData message can then call the ProcessData update case when needed, or ignore it otherwise.
-- in Child.elm
type Msg
= ProcessData String
| RouteData (ComponentId, String)
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch [
sendData RouteData
]
update msg model =
case msg of
RouteData (componentId, val) ->
if componentId == "my-component-id" then
update (ProcessData val) model
else
model ! []
ProcessData val -> ...
Update
Based on your additional detail, I would propose that you move the port and child Msg routing out of the Child module and into the Parent. You could then add the following update cases to the Parent:
ChildMessage componentId childMessage ->
let
childUpdates =
List.map (\c -> if c.id == componentId then updateChild c else c ! []) model.children
updateChild child =
let
(updatedChildModel, cmd) =
Child.update childMessage child
in
updatedChildModel ! [ Cmd.map (ChildMessage child.id) cmd ]
in
{ model | children = List.map Tuple.first childUpdates }
! (List.map Tuple.second childUpdates)
RouteData (componentId, val) ->
update (ChildMessage componentId (Child.ProcessData val)) model
I have created a pull request for your repository here

Elm 0.17: Task.perform and Maybe

I'm hoping someone could help me with Task.perform as I don't really understand how to handle a Maybe response - and the docs aren't making things clearer for me.
In my model I have results which Maybe a list of items or Nothing.
-- model
type alias Item =
{ name : String}
type alias Model =
{ results : Maybe (List Item) }
model = {
results = Nothing
}
I perform a Task and decode it like so:
-- Task
fetch : String -> Cmd Msg
fetch query =
let url =
"https://some_url" ++ query
in
Task.perform FetchFail FetchSuccess (Http.get decode url)
-- decoder
decoder: Json.Decoder (List Item)
decoder =
Json.at ["data"] (Json.list nestedListDecoder)
-- nestedListDecoder
nestedListDecoder : Json.Decoder Item
nestedListDecoder =
Json.object1 Item
("name" := Json.string)
I then handle the response in update:
-- update
type Msg
= FetchSuccess (Maybe (List Item))
| FetchFail Http.Error
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
FetchSuccess results ->
case results of
Nothing ->
( { model | results = Nothing}, Cmd.none)
Just res ->
( { model | results = res }, Cmd.none)
FetchFail err ->
-- ... handle error
And cater for the Maybe in the view:
-- view
result : Item -> Html Msg
result item =
li [] [ text item.name ]
view : Model -> Html Msg
view model =
ul [ ] (List.map result (Maybe.withDefault [] model.results))
I am getting this error when dealing with Maybe of results.
198| Task.perform FetchFail FetchSuccess (Http.get repos url)
^^^^^^^^^^^^^^^^^^
Function `perform` is expecting the 3rd argument to be:
Task Http.Error (Maybe (List Repo))
But it is:
Task Http.Error (List Repo)
Can anyone advise where else I need to cater for the Maybe ?
A simple tweak to your decoder should fix it. The decoder just needs to use Json.Decode.maybe:
decoder: Json.Decoder (Maybe (List Item))
decoder =
Json.maybe <| Json.at ["data"] (Json.list nestedListDecoder)

editMe extension is not working because of scriptMap

The performance of my web site was poor due multiple times import of jquery.js(shown by pagespeed plugin) and other scripts. So in my main layout, I added
<?php
$cs=Yii::app()->clientScript;
$cs->scriptMap=array(
'jquery.js'=>false,
'jquery.ui.js' => false,
);?>
...
...
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.7/jquery-ui.min.js"></script>
..
</head>
But as soon as, I added the above scriptMap in main layout, editMe rich text box editor is not working properly.
Can anybody tell me how to resolve this problem?
Also please suggest me how do I resolve multiple script import issue on my website?
I can see following code in ExtEditMe.php
public function run() {
// Register JavaScript files
Yii::app() -> clientScript -> registerCoreScript('jquery');
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl.'/js/jquery.js');
Yii::app() -> clientScript -> registerScriptFile(self::$_ckeAssetUrl . '/' . md5(self::$_ckeAssetUrl) . '.js');
Yii::app() -> clientScript -> registerScriptFile(self::$_ckeAssetUrl . '/ckeditor.js');
Yii::app() -> clientScript -> registerScriptFile(self::$_ckeAssetUrl . '/adapters/jquery.js');
// Generate textarea
$nameId = $this -> resolveNameID();
$this -> htmlOptions['id'] = $nameId[1];
if ($this -> hasModel()) {
echo CHtml::activeTextArea($this -> model, $this -> attribute, $this -> htmlOptions);
} else {
echo CHtml::textArea($this -> name, $this -> value, $this -> htmlOptions);
}
// Load CKEditor
$jquerySelector = CJavaScript::encode('#' . $this -> htmlOptions['id']);
$ckeConfig = CJavaScript::encode($this -> _ckeGenerateConfig());
Yii::app() -> clientScript -> registerScript('editMe_' . $this -> htmlOptions['id'], 'jQuery(' . $jquerySelector . ').ckeditor(' . $ckeConfig . ');', 2);
}
As a workaround, I have modified my main layout as below
<?php
$cs=Yii::app()->clientScript;
$cs->scriptMap=array(
'jquery-ui.min.js' => false,
'jquery.min.js'=>false,
);?>
...
...
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.7/jquery-ui.min.js"></script>
..
</head>
whereas,
ExtEditMe.php is kept as is.Also at all places where I was using jquery.js now uses jquery-min.js.