I try to implement infinite for my app and decided to do this in the following way. Here code extracted from index.html
it binds Elm app to particular node and also define some code that will be triggered on scroll event:
(function() {
var loadMore = function () {
return $(window).scrollTop() === $(document).height() - $(window).height()
};
var node = document.getElementById('main');
var myApp = Elm.Main.embed(node);
$(window).bind('scroll', function () {
var isBottom = loadMore();
myApp.ports.scroll.send(isBottom);
});
})();
I try to implement infinite for my app and decided to do this in the following way. Here code extracted from index.html
it binds Elm app to particular node and also define some code that will be triggered on scroll event:
(function() {
var loadMore = function () {
return $(window).scrollTop() === $(document).height() - $(window).height()
};
var node = document.getElementById('main');
var myApp = Elm.Main.embed(node);
$(window).bind('scroll', function () {
var isBottom = loadMore();
myApp.ports.scroll.send(isBottom);
});
})();
I assume it does what I need, but I'm not 100% sure.
The part I don't understand is how to handle this in Elm code. My current approach (which doesn't work) is following. I provide it just to make my intent more clear of what I try to achieve.
-- SUBSCRIPTIONS
port scroll : (Bool -> msg) -> Sub msg
subscriptions : Model -> Sub Msg
subscriptions model =
scroll Scroll
Share
Improve this question
edited Nov 24, 2016 at 9:38
SuperManEver
asked Nov 19, 2016 at 8:57
SuperManEverSuperManEver
2,3626 gold badges30 silver badges39 bronze badges
3
-
Actually code is working. I just extracted ports into separate file include into main elm file. Add
return
keyword to JS functionloadMore
and that's all. Then just couple tweaks inupdate
function and everything working great :) – SuperManEver Commented Nov 19, 2016 at 10:30 - Could you please post an answer to your question? – halfzebra Commented Nov 21, 2016 at 9:20
-
all code is here. One thing that is missing is just update function that handles
Scroll
– SuperManEver Commented Nov 23, 2016 at 21:29
2 Answers
Reset to default 11A plete implementation
Here is a working implementation without ports: https://ellie-app./5R4Fw95QLQfa1.
When you scroll to the end of the list, it loads more list items.
Performance considerations
In this implementation our event decoder is fetching offsetHeight
to inform us about our container's height. This causes constant reflows and might impact your program's performance. A better alternative is to know the height of the scroll element beforehand, or remove the event listener as soon as you find the height value.
module Main exposing (main)
import Browser
import Html
import Html exposing (Html, Attribute, ul, li, text, button, div)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput, onClick, on)
import Json.Decode
import List
import String
type alias Model = List String
initialModel =
[ "Pamplemousse"
, "Ananas"
, "Jus d'orange"
, "Boeuf"
, "Soupe du jour"
, "Camembert"
, "Jacques Cousteau"
, "Baguette"
]
-- UPDATE
type Msg
= LoadMore
| ScrollEvent ScrollInfo
type alias ScrollInfo =
{ scrollHeight : Float
, scrollTop : Float
, offsetHeight : Float
}
update msg model =
case msg of
LoadMore ->
List.concat [ model, initialModel ]
ScrollEvent { scrollHeight, scrollTop, offsetHeight } ->
if (scrollHeight - scrollTop) <= offsetHeight then
List.concat [ model, initialModel ]
else
model
-- VIEW
view content =
div [ onScroll ScrollEvent ]
[ ul
[ class "grocery-list"
, style "height" "300px"
, style "display" "block"
, style "overflow" "scroll"
, onScroll ScrollEvent
]
(List.map listItem content)
, button [ onClick LoadMore ] [ text "load more" ]
]
listItem itemText =
li
[ style "height" "50px"
, style "display" "block"
]
[ text itemText ]
onScroll msg =
on "scroll" (Json.Decode.map msg scrollInfoDecoder)
scrollInfoDecoder =
Json.Decode.map3 ScrollInfo
(Json.Decode.at [ "target", "scrollHeight" ] Json.Decode.float)
(Json.Decode.at [ "target", "scrollTop" ] Json.Decode.float)
(Json.Decode.at [ "target", "offsetHeight" ] Json.Decode.float)
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
In order to implement infinite scroll using approach I chose you need few things. I'm going to give some high level overview of what is going on, what main ponents are and how this all fit together and then dive into some code.
Abstractly speaking I need following things:
- I need to somehow say to browser that I interested in
scroll
events - I need to trigger some code when this event occur.
How do I say browser that I'm interested in scroll
events ?
Because current implementation of scroll event in Elm either doesn't exist or hard to work with I decided to use jQuery to work with those events.
Here is you can see all code I use from index.html
. It does few essential things:
it loads Elm app and attaches it to some DOM element on the page it
binds a callback to
scroll
event that will be triggered every time this event occurs(function($) { var loadMore = function () { return $(window).scrollTop() === $(document).height() - $(window).height() }; var node = document.getElementById('main'); var myApp = Elm.Main.embed(node); $(window).bind('scroll', function () { var isBottom = loadMore(); myApp.ports.scroll.send(isBottom); }); })(jQuery);
I'd like to draw your attention to this line :
myApp.ports.scroll.send(isBottom);
Here is how I sent some data into Elm world.
myApp
is just name of variable that holds a reference to Elm's app, nothing fancy here.
ports
is just a keyword you have to use in order to implement this kind of things.
scroll
this is name of function that will be called on the Elm's side. It defined by you (I will show later how to do this)
send
is mandatory part. This is how you send data to Elm app.
Now all I need to do is somehow receive this data on Elm's side.
Again, high level overview. Now data is marching to my Elm app and all I need to do is to subscribe to this event (We don't have callbacks in Elm, we have subscriptions :) )
I did this in following steps. I created module called Ports
with following content:
port module Ports exposing (..)
port scroll : (Bool -> msg) -> Sub msg
port
keyword before module
keyword is mandatory if you want to be able to retrieve data from outside of Elm's world.
Next, I import this module in my App.elm
, this is essentially some root level module (main coordination node). I just need to add this line:
import Ports exposing (..)
Next, I need to define subscriptions
in my App.elm
like this:
subscriptions : Model -> Sub Msg
subscriptions model =
scroll Scroll
Essentially I subscribe to particular event and when this event occurs particular Msg
will be dispatched.
I need few other things to make this whole thing works:
I need to include
Scroll
message into myMsg
data typeHandle this case in
update
functiontype Msg = NoOp | Scroll Bool
As you can see I indicated in value constructor Scroll
that I'm expecting boolean value
And of course update
function. Depending on whether pos
true or false I trigger some code to load more articles, for example.
update msg model =
case msg of
NoOp ->
model ! []
Scroll pos ->
-- do something with it