HTML5 и Javascript: открытие и чтение локального файла с помощью File API

Я использую Google Web Toolkit для проекта и хотел бы, чтобы пользователь выбрал текстовый файл для открытия в текстовом окне внутри браузера. Вот почти рабочий код:

 private DialogBox createUploadBox() {
     final DialogBox uploadBox = new DialogBox();
     VerticalPanel vpanel = new VerticalPanel();
     String title = "Select a .gms file to open:";
     final FileUpload upload = new FileUpload();
     uploadBox.setText(title);
     uploadBox.setWidget(vpanel);
     HorizontalPanel buttons = new HorizontalPanel();
     HorizontalPanel errorPane = new HorizontalPanel();
     Button openButton = new Button( "Open", new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            String filename = upload.getFilename();
            int len = filename.length();
            if (len < 5) {
                Window.alert("Please enter a valid filename.\n\tFormat: <filename>.gms");
            } else if (!filename.substring(len-4).toLowerCase().equals(".gms")) {
                Window.alert(filename.substring(len-4) + " is not valid.\n\tOnly files of type .gms are allowed.");
            } else {
                Window.alert(getFileText(filename));
            }
        }
        private native String getFileText(String filename) /*-{
            // Check for the various File API support.
            if (window.File && window.FileReader && window.FileList && window.Blob) {
                // Great success! All the File APIs are supported.
                var reader = new FileReader();
                var file = File(filename);
                str = reader.readAsText(file);
                return str;
            } else {
                alert('The File APIs are not fully supported in this browser.');
                return;
            }
        }-*/;
     });
     Button cancelButton = new Button( "Cancel", 
             new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            uploadBox.hide();               
        }
     });
     buttons.add(openButton);
     buttons.add(cancelButton);
     vpanel.add(upload);
     vpanel.add(buttons);
     vpanel.add(errorPane);
     uploadBox.setAnimationEnabled(true);
     uploadBox.setGlassEnabled(true);
     uploadBox.center();
     return uploadBox;
 }

Всякий раз, когда я пытаюсь использовать эту функцию в своей программе, я получаю:

(NS_ERROR_DOM_SECURITY_ERR): ошибка безопасности

Я уверен, что дело обстоит так:

var file = new File(filename, null);

Отказ от ответственности: я ни в коем случае не программист Javascript, пожалуйста, не стесняйтесь указывать на любые очевидные ошибки, которые я здесь делаю.


person holocron    schedule 12.04.2012    source источник
comment
re: правки - window/$wnd, вероятно, единственный момент, специфичный для gwt. Это и как передать экземпляр InputElement.   -  person Colin Alworth    schedule 13.04.2012


Ответы (2)


Вместо использования window вы почти всегда должны использовать $wnd. См. https://developers.google.com/web-toolkit/doc/latest/DevGuideCodingBasicsJSNI#writing для получения дополнительных сведений о JSNI.

Также может быть полезно добавить оператор debugger при использовании чего-то вроде Firebug или Chrome Inspector. Этот оператор остановит код JS в отладчике, как если бы вы поставили там точку останова, и позволит вам выполнять отладку в Javascript, шагая по одной строке за раз, чтобы увидеть, что именно пошло не так.

И, наконец, вы уверены, что файл, который вы читаете, разрешен браузером? Из http://dev.w3.org/2006/webapi/FileAPI/#dfn-SecurityError, эта ошибка может возникнуть из-за того, что браузеру не разрешен доступ к файлу. Вместо того, чтобы передавать строку, вы можете передать <input type='file' />, с которым взаимодействует пользователь, и получить оттуда файл, который он выбрал.


Обновление (извините за задержку, видимо, предыдущее обновление, которое я сделал, было выброшено, мне потребовалось немного, чтобы переписать его):

В исходном коде делается пара неверных предположений. Большая часть моего чтения взята с http://www.html5rocks.com/en/tutorials/file/dndfiles/, а также немного поэкспериментировать.

  • Во-первых, что вы можете получить реальный путь из поля <input type='file' /> вкупе с
  • что вы можете читать произвольные файлы из пользовательской файловой системы только по пути, и, наконец,
  • что FileReader API является синхронным.

Из соображений безопасности большинство браузеров не дают реальных путей, когда вы читаете имя файла — проверьте строку, которую вы получаете от upload.getFilename() в нескольких браузерах, чтобы увидеть, что она дает — этого недостаточно для загрузки файла. Вторая проблема также связана с безопасностью - очень мало пользы может дать разрешение чтения из файловой системы, просто используя строку для указания файла для чтения.

По этим первым двум причинам вместо этого вам нужно запросить у input файлы, над которыми он работает. Браузеры, поддерживающие FileReader API, разрешают доступ к этому, считывая свойство files элемента ввода. Два простых способа получить это — работа с NativeElement.getEventTarget() в jsni или просто работа с FileUpload.getElement(). Имейте в виду, что это свойство files по умолчанию содержит несколько элементов, поэтому в случае, подобном вашему, просто прочитайте нулевой элемент.

private native void loadContents(NativeEvent evt) /*-{
    if ($wnd.File && $wnd.FileReader && $wnd.FileList && $wnd.Blob) {
        // Great success! All the File APIs are supported.
        var reader = new FileReader();
        reader.readAsText(evt.target.files[0]);
//...

or

private native void loadContents(Element elt) /*-{
    if ($wnd.File && $wnd.FileReader && $wnd.FileList && $wnd.Blob) {
        // Great success! All the File APIs are supported.
        var reader = new FileReader();
        reader.readAsText(elt.files[0]);
//...

Наконец, FileReader API является асинхронным — вы не сразу получаете полное содержимое файла, а должны дождаться вызова обратного вызова onloadend (опять же, из http://www.html5rocks.com/en/tutorials/file/dndfiles/). Эти файлы могут быть достаточно большими, чтобы вы не хотели, чтобы приложение блокировалось во время чтения, поэтому, по-видимому, спецификация предполагает это по умолчанию.

Вот почему я закончил создание новых методов void loadContents вместо сохранения кода в вашем методе onClick — этот метод вызывается, когда поле ChangeEvent отключается, чтобы начать чтение в файле, хотя это можно было бы написать каким-то другим способом.

// fields to hold current state
private String fileName;
private String contents;
public void setContents(String contents) {
  this.contents = contents;
}

// helper method to read contents asynchronously 
private native void loadContents(NativeEvent evt) /*-{;
    if ($wnd.File && $wnd.FileReader && $wnd.FileList && $wnd.Blob) {
        var that = this;
        // Great success! All the File APIs are supported.
        var reader = new FileReader();
        reader.readAsText(evt.target.files[0]);
        reader.onloadend = function(event) {
            [email protected]::setContents(Ljava/lang/String;)(event.target.result);
        };
    } else {
        $wnd.alert('The File APIs are not fully supported in this browser.');
    }
}-*/;

// original createUploadBox
private DialogBox createUploadBox() {
  final DialogBox uploadBox = new DialogBox();
  VerticalPanel vpanel = new VerticalPanel();
  String title = "Select a .gms file to open:";
  final FileUpload upload = new FileUpload();
  upload.addChangeHandler(new ChangeHandler() {
    @Override
    public void onChange(ChangeEvent event) {
      loadContents(event.getNativeEvent());
      fileName = upload.getFilename();
    }
  });
  // continue setup

Затем кнопка «ОК» читает данные из полей. Вероятно, было бы разумно проверить, что содержимое не равно нулю в ClickHandler, и, возможно, даже обнулить его, когда ChangeEvent FileUpload отключается.

person Colin Alworth    schedule 12.04.2012
comment
Извините, но, как я уже говорил, я не программист javascript. Не могли бы вы привести конкретный пример использования этого бита ‹input type='file'/›? - person holocron; 13.04.2012
comment
Я думаю, вы правы в том, что браузер не разрешает доступ к файлу. Нужно ли отправлять файл в форме? - person holocron; 13.04.2012
comment
Не отправлять, а позволить пользователю перейти к файлу, а затем посмотреть на свой выбор. Взгляните на html5rocks.com/en/tutorials/file. /dndfiles/#toc-reading-files — вы добавите обработчик ChangeEvent в виджет FileUpload — пользователь нажимает кнопку просмотра, выбирает что-то, и вы сможете прочитать NativeEvent, как это делается в handleFileSelect. . Вам может понадобиться пользовательский виджет, отличный от FileUpload, я давно не играл с этим. Обновите Q с помощью почти рабочего примера кода, и я посмотрю, смогу ли я продолжить. - person Colin Alworth; 13.04.2012
comment
Колин, то, что ты говоришь, имеет для меня смысл. Мне определенно не хватает некоторых основ. Я обновил свой вопрос со всем блоком кода. Спасибо за помощь. - person holocron; 13.04.2012
comment
Просто поставьте обновление с в основном работающей импл, дайте мне знать, если у вас есть какие-либо вопросы. - person Colin Alworth; 16.04.2012
comment
Колин – Вау, это отлично работает! Это именно то, что мне было нужно, спасибо. - person holocron; 16.04.2012
comment
Один вопрос: зачем var that = this; переназначение? Я сам могу придумать одну причину, но я просто хочу услышать это от кого-то, кто знает, что они делают :) - person holocron; 16.04.2012
comment
that=this предназначен для обратного вызова onloadend - this на самом деле не то же самое в JS, что и в Java, поэтому вам необходимо учитывать тот факт, что в этом обратном вызове this будет означать то, для чего вызывается функция. Быстрый пример предполагает, что это будет FileReader, но во многих случаях обратные вызовы вызываются с window (в gwt, $wnd) как this. - person Colin Alworth; 16.04.2012

Насколько я вижу, вы можете использовать только new File(name) при написании расширений: https://developer.mozilla.org/en/Extensions/Using_the_DOM_File_API_in_chrome_code

person Kristoffer Sall-Storgaard    schedule 12.04.2012