How to use elm/file?

I’m loving Elm but currently struggling to understand the elm/file package and how to use it.

Background

Martin Janiczek’s Elm ports and JavaScript solution to ‘How can I access a file on my local drive?’ works fine. I read the code, made sure I understood it, and got it working in my app, all in about an hour. But I then discovered Elm File, and decided I should be doing it purely in Elm. Too much time later (I’m embarrassed to say how long) I’m still trying.

What I’ve achieved

As I understand it, the process of File is Request Files -> Load Files -> Extract Data.

In my implementation:

  1. a user-click sends a FilesRequested message

  2. update’s FilesRequested branch runs
    Select.files ["text/csv","text/csv"] FilesLoaded

  3. update’s FilesLoaded branch adds the files to the model and then runs
    Task.perform TextLoaded (File.toString file) on each file

I’m calling functions from update — (model, function call) — and starting to think this might be ‘wrong’ (?). Still learning.

Stages 2 and 3 are/were unreliable — sometimes worked sometimes didn’t.

I fixed stage 3 by recognizing that I had to ensure that the model was updated with the new file data BEFORE running the code to extract the data. The andThen function from the brilliant elm-update-extra package came to my rescue: update model andThen process it’s content.

I haven’t included my code because it’s long, complicated, and probably wrong/irrelevant :slight_smile:

My misunderstandings

I have read Evan’s document Working with Files, and his File load code.

Looking at Evan’s code, it occurs to me that maybe the reason that stage 2 — loading the file(s) in — only sometimes works is because I’m not quite grabbing the file immediately but am instead going round through update.

Looking more closely, I see that Evan doesn’t use File.Select at all, but just File.decoder. Maybe because of the ‘Limitations’ mentioned on the File.Select page.

But this is very confusing because surely a file must be selected(/loaded) before it can be decoded. And, if Select is both unreliable and unnecessary, why does it exist at all?

Further, surely the elm/file package contains all the necessary functions to load files and extract the content, without having to resort to Json.Decode to unpack what elm/file has packaged up?

As you can see, I’m lost.

3 Likes

File.Select allows you to use a button with any style you want. It doesn’t even have to be a <button> element, just clickable.

Using an <input type="file"> like Evan’s demo is easy, but the file input is kind of annoying to style, especially in a way that works across browsers.

Fun fact: <input type="file"> is the only way to get the OS file-selection dialog, so File.Select creates an invisible <input type="file"> and dispatches a click event on it!

3 Likes

There currently a bug in how File.Select.file works which explains the unreliability you’re seeing.

4 Likes

I think I’m having the same struggle as @AlanQ, which is how to actually get the contents of the file into a form that we can work with. I too got Evan’s Ellie example running great, to get a list of files loaded into the model. And the package docs example for File.toString sort of makes sense. But I can’t seem to work out how to put them together.

In the Working with Files blog post mentioned, Evan casually notes “[m]aybe you want File.toBytes [to] start working with the file content?” YES! That is what I want (well, toString anyway). But the Ellie example only gets you the file names, not file contents, and I’m struggling to see how to get from one to the other, especially with Task.perform involved.

Just to update, this issue should have been largely fixed today, as Evan released elm/file 1.0.2 in response to our discussion in Cross-browser compatibility fix for File.Select.file(s) in elm/file.

2 Likes

Once you have a File, using an input or using File.Select, you can ask the runtime to read it by returning a Cmd using toString or toBytes, for example:

type Msg
    = OpenFileClicked
    | FileSelected File
    | FileRead String


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        FileSelected file ->
            ( model, Task.perform FileRead (File.toString file) )

If you have a List File, you can do the same with Cmd.batch and List.map.

Then you will receive the content with the FileRead message:

type alias Model =
    String

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ...
        FileRead content ->
            ( content, Cmd.none )

Here is the full example that allows to open a file and display it as a string, using File.Select and File.toString:

https://ellie-app.com/4BDmZ3zwKFCa1 (it uses the new elm/file 1.0.2, so report any issue)

module Main exposing (main)

import Browser
import File exposing (File)
import File.Select as Select
import Html exposing (Html, button, div, p, text)
import Html.Events exposing (onClick)
import Task


type alias Model =
    String


type Msg
    = OpenFileClicked
    | FileSelected File
    | FileRead String


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        OpenFileClicked ->
            ( model, Select.file [] FileSelected )

        FileSelected file ->
            ( model, Task.perform FileRead (File.toString file) )

        FileRead content ->
            ( content, Cmd.none )


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick OpenFileClicked ] [ text "Open file" ]
        , p [] [ text model ]
        ]


main : Program () Model Msg
main =
    Browser.element
        { init = always ( "", Cmd.none )
        , view = view
        , update = update
        , subscriptions = always Sub.none
        }

You could also have a look at the source code of elm-doc-preview for a “real-world” example with several files:

3 Likes

Many thanks for all the replies.

@Sidney, that helps clarify File.Select vs File.decoder.
@jessta, knowing there is (was) a bug makes me feel better :slight_smile:

@christian, brilliant, I’ll upgrade and try again…
It works! In both Chromium and Firefox on Ubuntu :smiley: — it might need some automated tests to ensure it works every time.
I upgraded the File package using @jerith’s upgrade approach.

@kwindow, I guess from your task comment you too would very much appreciate a tutorial article on Tasks :wink:

@dmy, that’s how I did it, good to have the confirmation. Thanks also for the Cmd.batch and List.map pointers.

1 Like

Many thanks indeed! @dmy, your explanation made sense and the Ellie example worked a treat.

It was the receiving of the file contents with the FileRead message, and the message itself being an argument into Task.perform, that I was getting hung up on. It was a great feeling, after all my own thrashing around, to see how simple it was! The Ellie code was easy to adapt and would make a great addition to the File docs. :thinking:

@AlanQ you are not wrong, a tutorial on Task would be something I’d appreciate too. :grin: The beginner examples make sense but it is hard to fully grok the concept (probably from too many years of OO development).

1 Like

I put a more elaborate example in the README of elm/file just now, see here.

There is another patch pending to further improve File.Select as described in this thread, so this should all be out with 1.0.3 once I get some feedback on the change there.

7 Likes

Thank you, @evancz, that example helps a lot.
And I see 1.0.3 is already available :slight_smile:
Both 1.0.2 and 1.0.3 work just fine for me.

2 Likes

It might be helpful for someone who is trying to understand how to work with files and maybe drag&drop. I experienced with this recently

Example
Demo

1 Like

Thank you, @ivadzy. It’s good to see a variety of approaches.

Very handy to have a file-drop example, especially to note preventDefaultOn "drop" — an interesting reference regarding this here Is call to preventDefault() really necessary on drop event?.

Didn’t think I’d need drag’n’drop but it’s a common way to load files in desktop applications so a good option to offer users.


Edit – final note before this thread closes (this is also linked by @kwindow above):
Probably the definitive set of examples is at Evan’s article Working with Files.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.