Search code examples
htmlfilereaderclojurescriptfileapireagent

Problem opening files with the FileReader API


This has been bugging me for days. I have a web app that lets the user open documents from their local machine. I'm using the FileReader API for the first time.

It works correctly except for one use case.

  • Open a document file.
  • Programmatically create a new document, overwriting the existing one.
  • Open the same file as above.

When this sequence is executed, the second attempt fails silently (except that the file is not loaded).

Here is an example Reagent program (created from the figwheel-main template) that illustrates the problem.

(ns a-bad-button.core
  (:require [reagent.core :as r]))

(def app-state-ratom (r/atom nil))

(defn new-doc []
  {:doc-text "Some MINIMAL text to play with."})

(defn add-new-button
  [aps]
  (fn [aps]
    [:input.tree-demo--button
     {:type     "button"
      :value    "New"
      :on-click #(reset! aps (new-doc))}]))

(defn load-doc-data!
  [aps file-data]
  (swap! aps assoc :doc-text file-data))

(defn handle-file-open-selection
  [aps evt]
  (let [js-file-reader (js/FileReader.)]
    (set! (.-onload js-file-reader)
          (fn [evt] (load-doc-data! aps (-> evt .-target .-result))))
    (.readAsText js-file-reader (aget (.-files (.-target evt)) 0))))

(defn add-open-button
  [aps]
  (fn [aps]
    [:div
     [:input {:type      "file" :id "file-open-id"
              :style     {:display "none"}
              :on-change #(handle-file-open-selection aps %)}]
     [:input {:type     "button"
              :value    "Open"
              :on-click #(.click (.getElementById js/document "file-open-id"))}]]))

(defn a-bad-button
  [aps]
  (fn [aps]
    [:div
     [:h4 "A Bad Button"]
     [:p#doc-text-p (or (:doc-text @aps) "Loaded text will go here.")]
     [add-new-button aps]
     [add-open-button aps]]))

(defn mount! [el]
  (reset! app-state-ratom (new-doc))
  (r/render-component [a-bad-button app-state-ratom] el))

(defn mount-app-element []
  (when-let [el (.getElementById js/document "app")]
    (mount! el)))

(mount-app-element)

(defn ^:after-load on-reload []
  (mount-app-element))

With println debugging messages, it appears that execution reaches the :on-click handler in the add-open-button function, but the handler, handle-file-open-selection, is never reached or executed.

The failure occurs on Safari, Opera, Brave, and Vivaldi browsers. Files open as expected on Firefox.

Has anyone seen this before and fixed it?


Solution

  • Similar questions:

    Filereader - upload same file again not working

    FileReader onload not getting fired when selecting same file in Chrome

    Basically, the problem is that onChange will not trigger when selecting the same file. One workaround is to set the value of the file input before the file browser opens to something like "", to always trigger an onChange event. In your case, it could look like changing your handle-file-open-selection function to:

    (defn handle-file-open-selection
      [aps evt]
      (let [js-file-reader (js/FileReader.)]
        (set! (.-onload js-file-reader)
              (fn [evt]
                (load-doc-data! aps (-> evt .-target .-result))))
        (.readAsText js-file-reader (aget (.-files (.-target evt)) 0))
        ;; add this
        (set! (.-value (.getElementById js/document "file-open-id")) "")
        ))