Calling Task Never x to help construct an init function


#1

This is a small detail, but it’s been annoying me incessantly for a long time:

There are times when I need the current time in the initial model, and times when I need the window size in the initial model.

I know, I can pass them in through flags or I can grab make them the initial Cmd msg. These are the two approaches I follow. But…

  1. I make a lot of small applications that are full screen and don’t use ports or flags at all, and thus, just setting up the embedding of the elm app in an empty html document is a minor annoyance (also requires an extra request to the server, versus compiling to an index.html).

2.) getting it with the first Cmd msg means my first frame doesn’t have the information, leading to a flash or requiring me to make a loading animation (and even then, getViewport and Time.now work so fast that there’s no time to see the animation.

Like I said, this is a minor annoyance, but nonetheless, it is annoying me.

I wonder if we could provide an alternative init field somehow, that instead of producing a flags -> (model, Cmd msg) (or whatever variant), produced a `Cmd (Task Never x) -> (model, Cmd msg)’?

The reason for restricting it to Task Never x, instead of any task, is that tasks that might fail or have latency probably shouldn’t be used here, but it does seem silly that there’s not an easy way to load window size and current time, both of which the runtime can be guaranteed to grab extremely fast before our first rendering of view.

Am I missing something?

This is the sort of thing like a door that pulls when it clear should push that really annoys me.


#2

Function in Elm are stateless so accessing any data that might change needs to be asynchronous.
If you have effects that you need to perform before rendering anything to the screen you can handle this by having this Initializing be a state that your model can be in. While in the Initializing state your view function would simply render nothing.
If this Initializing could take an arbitrary amount of time (eg. if http requests are required) you can have the Initializingstate contain how long it’s taken and have the view display a loading animation if it takes longer than 1 second or so.

I’m unsure what having init: Cmd (Task Never x) -> (model, Cmd msg) would mean.
Perhaps you could expand more on what you expect it would do.


#3

This won’t solve the brief ‘flash’ on startup, but…

I think you could make your own Program constructor(s), that wrap the ones in Browser. These would get the current time and screen size and give them to the new program type. Then every time you write one of these little full screen apps, use this custom program constructor.

I also would find this useful as I frequently make fullscreen SVG apps to try out rendering various things in SVG, and I like to start by getting the viewport size so I can make my SVG viewport map onto that 1:1 for the sharpest rendering.

Lets see if I can try sketching out how this works…

https://ellie-app.com/45KKFStw94ha1

That shows how to write a custom element program, and also a little example to try it out.

Also, I don’t find this is ‘flashing’ on startup. The view starts out as an empty div, then fills in on the first animation frame - same as normal when Elm takes over part of the DOM.


#4

Thank you, Rupert. I had thought about doing this and wasn’t sure if it was possible or if it was too intertwined with the runtime.


#5

Call synchronous tasks, namely Time.now and Browser.Dom.getViewport, hence the restriction to Task Never x, instead of Task x y


#6

I think your suggested init field wants to be something like: Task Never x -> (x -> (model, Cmd msg)) no?

Just to add that in the case of 1, sometimes I know the Elm task to get the information I need, but turning that into a bit of Javascript to call to get an equivalent value to pass in as a flag is occasionally non-trivial, or at least non-obvious. Having done so it’s non-obvious that the solution will still work for later versions of Elm. Whereas if I can do this initialisation in Elm, it is less likely to break in a future version of Elm, and if it does the compiler will tell me. Admittedly, since this is initialisation code, if it does break, it’s likely immediately obvious.

A further point; things done at “initialisation” have a habit of turning into things we might need to do later. When that happens, and you’ve chosen approach number 1, you really wish you had chosen approach number 2, because that’s easier to update into something that happens generally rather than just at startup. But if you had used an init field with some task as you are suggesting, it would be easier to factor that task out and use it in a command.

One downside would be that you would need to update Browser.element, Browser.document, and Browser.application because you could conceivably wish to run an initialising task in any of those three scenarios. I mean it couldn’t just be a single additional variant.


#7

I don’t think you need to for element, because element is inherently embedded in html, and the only values I think can be added are the synchronous trivial ones (time and getViewport are the only two I can think of).