From 84fb2dcbe6738f28333662cb35eb8c2448eb0fc2 Mon Sep 17 00:00:00 2001 From: Massimo Date: Thu, 10 Aug 2023 20:07:51 +0200 Subject: [PATCH 1/5] File handling documentation --- docs-src/0.4/en/reference/user_input.md | 18 +++++++++++ src/doc_examples/input_fileengine.rs | 26 ++++++++++++++++ src/doc_examples/input_fileengine_async.rs | 36 ++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/doc_examples/input_fileengine.rs create mode 100644 src/doc_examples/input_fileengine_async.rs diff --git a/docs-src/0.4/en/reference/user_input.md b/docs-src/0.4/en/reference/user_input.md index cdad7fc63..73e52a553 100644 --- a/docs-src/0.4/en/reference/user_input.md +++ b/docs-src/0.4/en/reference/user_input.md @@ -41,3 +41,21 @@ DemoFrame { ``` Submitted! UiEvent { data: FormData { value: "", values: {"age": "very old", "date": "1966", "name": "Fred"} } } ``` + +## Handling files +You can insert a file picker by using an input component e.g. `input {"type":"file"}`. This component supports the `multiple` attribute, to let you pick more files at the same time. You can select an entire folder by adding the `webkitdirectory` attribute: beware that, even if it's currently accepted in major browsers, it is not part of the HTML standard and may break in the future. + +Pay attention that `type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:"file"` or as a string as in `"type":"file"`. + +Extracting the selected files is a bit different compared to how you're used to do in Javascript. +The `FormData` struct in the fired event contains an additional hidden field named `FileEngine`. This struct lets you fetch the filenames selected by the user. The example saves the filenames of the selected files to a `Vec`: + +```rust, no_run +{{#include src/doc_examples/input_fileengine.rs:component}} +``` + +If you're planning to read the file content, make sure to do it asynchronously, in order to not freeze the UI in the meantime. This example loads the content of the selected files in an async closure. + +```rust, no_run +{{#include src/doc_examples/input_fileengine_async.rs:component}} +``` \ No newline at end of file diff --git a/src/doc_examples/input_fileengine.rs b/src/doc_examples/input_fileengine.rs new file mode 100644 index 000000000..2fac9b4df --- /dev/null +++ b/src/doc_examples/input_fileengine.rs @@ -0,0 +1,26 @@ +#![allow(non_snake_case)] +use dioxus::prelude::*; + +// ANCHOR: component +pub fn App(cx: Scope) -> Element { + let filenames: &UseRef> = use_ref(cx, Vec::new); + + cx.render(rsx! { + input { + // tell the input to pick a file + r#type:"file", + // list the accepted extensions + accept: ".txt, .rs", + multiple: true, + onchange: |evt| { + if let Some(file_engine) = &evt.files { + let files = file_engine.files(); + for file_name in &files { + filenames.write().push(file_name); + } + } + } + } + }) +} +// ANCHOR_END: component diff --git a/src/doc_examples/input_fileengine_async.rs b/src/doc_examples/input_fileengine_async.rs new file mode 100644 index 000000000..514688e0c --- /dev/null +++ b/src/doc_examples/input_fileengine_async.rs @@ -0,0 +1,36 @@ +#![allow(non_snake_case)] +use dioxus::prelude::*; + +// ANCHOR: component +pub fn App(cx: Scope) -> Element { + let files_uploaded: &UseRef> = use_ref(cx, Vec::new); + + cx.render(rsx! { + input { + // we tell the component what to render + value: "{name}", + // tell the input to pick a file + r#type:"file", + // list the accepted extensions + accept: ".txt, .rs", + multiple: true, + onchange: |evt| { + // A helper macro to use hooks in async environments + to_owned![files_uploaded]; + async move { + if let Some(file_engine) = &evt.files { + let files = file_engine.files(); + for file_name in &files { + // Make sure to use async/await when doing heavy I/O operations, + // to not freeze the interface in the meantime + if let Some(file) = file_engine.read_file_to_string(file_name).await{ + files_uploaded.write().push(file); + } + } + } + } + } + } + }) +} +// ANCHOR_END: component From 74618b7075dbdb53feb1f5794def41ab93f78ace Mon Sep 17 00:00:00 2001 From: Massimo Date: Sat, 12 Aug 2023 11:57:20 +0200 Subject: [PATCH 2/5] Fix field name --- docs-src/0.4/en/reference/user_input.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-src/0.4/en/reference/user_input.md b/docs-src/0.4/en/reference/user_input.md index 73e52a553..df1ab7955 100644 --- a/docs-src/0.4/en/reference/user_input.md +++ b/docs-src/0.4/en/reference/user_input.md @@ -48,7 +48,7 @@ You can insert a file picker by using an input component e.g. `input {"type":"fi Pay attention that `type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:"file"` or as a string as in `"type":"file"`. Extracting the selected files is a bit different compared to how you're used to do in Javascript. -The `FormData` struct in the fired event contains an additional hidden field named `FileEngine`. This struct lets you fetch the filenames selected by the user. The example saves the filenames of the selected files to a `Vec`: +The `FormData` struct in the fired event contains an additional hidden field named `files`. This field contains a `FileEngine` struct which lets you fetch the filenames selected by the user. The example saves the filenames of the selected files to a `Vec`: ```rust, no_run {{#include src/doc_examples/input_fileengine.rs:component}} From ba9a69e3d61047e1c0c8f6e180d56042636413c9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 14 Aug 2023 14:57:12 -0500 Subject: [PATCH 3/5] clean up some wording --- docs-src/0.4/en/reference/user_input.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs-src/0.4/en/reference/user_input.md b/docs-src/0.4/en/reference/user_input.md index df1ab7955..28c86e6fd 100644 --- a/docs-src/0.4/en/reference/user_input.md +++ b/docs-src/0.4/en/reference/user_input.md @@ -43,18 +43,21 @@ Submitted! UiEvent { data: FormData { value: "", values: {"age": "very old", "da ``` ## Handling files -You can insert a file picker by using an input component e.g. `input {"type":"file"}`. This component supports the `multiple` attribute, to let you pick more files at the same time. You can select an entire folder by adding the `webkitdirectory` attribute: beware that, even if it's currently accepted in major browsers, it is not part of the HTML standard and may break in the future. +You can insert a file picker by using an input element e.g. `input {"type":"file"}`. This element supports the `multiple` attribute, to let you pick more files at the same time. You can select an entire folder by adding the `directory` attribute. -Pay attention that `type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:"file"` or as a string as in `"type":"file"`. +> Dioxus will map this attribute to browser specific attributes. Because there is no standardized way to allow multiple files to be uploaded. -Extracting the selected files is a bit different compared to how you're used to do in Javascript. -The `FormData` struct in the fired event contains an additional hidden field named `files`. This field contains a `FileEngine` struct which lets you fetch the filenames selected by the user. The example saves the filenames of the selected files to a `Vec`: +`type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:"file"`. + +Extracting the selected files is a bit different from what you may typically use in Javascript. + +The `FormData` event contains a `files` field with data about the uploaded files. This field has a `FileEngine` struct which lets you fetch the filenames selected by the user. This example saves the filenames of the selected files to a `Vec`: ```rust, no_run {{#include src/doc_examples/input_fileengine.rs:component}} ``` -If you're planning to read the file content, make sure to do it asynchronously, in order to not freeze the UI in the meantime. This example loads the content of the selected files in an async closure. +If you're planning to read the file content, you need to do it asynchronously, to keep the rest of the UI interactive. This example loads the content of the selected files in an async closure: ```rust, no_run {{#include src/doc_examples/input_fileengine_async.rs:component}} From 74e4ef81662ee8c7bd2be12fc15ea531954df215 Mon Sep 17 00:00:00 2001 From: Massimo Date: Tue, 15 Aug 2023 13:56:33 +0200 Subject: [PATCH 4/5] Directory attribute fix --- docs-src/0.4/en/reference/user_input.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-src/0.4/en/reference/user_input.md b/docs-src/0.4/en/reference/user_input.md index 28c86e6fd..2b0c94388 100644 --- a/docs-src/0.4/en/reference/user_input.md +++ b/docs-src/0.4/en/reference/user_input.md @@ -45,7 +45,7 @@ Submitted! UiEvent { data: FormData { value: "", values: {"age": "very old", "da ## Handling files You can insert a file picker by using an input element e.g. `input {"type":"file"}`. This element supports the `multiple` attribute, to let you pick more files at the same time. You can select an entire folder by adding the `directory` attribute. -> Dioxus will map this attribute to browser specific attributes. Because there is no standardized way to allow multiple files to be uploaded. +> Dioxus will map this attribute to browser specific attributes. Because there is no standardized way to allow a directory to be selected. `type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:"file"`. From dfdcd9da4a36f50dd085a60ca7d424ea27fd5a96 Mon Sep 17 00:00:00 2001 From: Massimo Date: Sat, 26 Aug 2023 16:58:43 +0200 Subject: [PATCH 5/5] added folder-select example and cleaned up code --- docs-src/0.4/en/reference/user_input.md | 16 ++++++++----- src/doc_examples/input_fileengine.rs | 4 +++- src/doc_examples/input_fileengine_async.rs | 6 ++--- src/doc_examples/input_fileengine_folder.rs | 26 +++++++++++++++++++++ 4 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 src/doc_examples/input_fileengine_folder.rs diff --git a/docs-src/0.4/en/reference/user_input.md b/docs-src/0.4/en/reference/user_input.md index 2b0c94388..70dc58a08 100644 --- a/docs-src/0.4/en/reference/user_input.md +++ b/docs-src/0.4/en/reference/user_input.md @@ -43,22 +43,26 @@ Submitted! UiEvent { data: FormData { value: "", values: {"age": "very old", "da ``` ## Handling files -You can insert a file picker by using an input element e.g. `input {"type":"file"}`. This element supports the `multiple` attribute, to let you pick more files at the same time. You can select an entire folder by adding the `directory` attribute. - -> Dioxus will map this attribute to browser specific attributes. Because there is no standardized way to allow a directory to be selected. +You can insert a file picker by using an input element of type `file`. This element supports the `multiple` attribute, to let you pick more files at the same time. You can select a folder by adding the `directory` attribute: Dioxus will map this attribute to browser specific attributes, because there is no standardized way to allow a directory to be selected. `type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:"file"`. Extracting the selected files is a bit different from what you may typically use in Javascript. -The `FormData` event contains a `files` field with data about the uploaded files. This field has a `FileEngine` struct which lets you fetch the filenames selected by the user. This example saves the filenames of the selected files to a `Vec`: +The `FormData` event contains a `files` field with data about the uploaded files. This field contains a `FileEngine` struct which lets you fetch the filenames selected by the user. This example saves the filenames of the selected files to a `Vec`: ```rust, no_run {{#include src/doc_examples/input_fileengine.rs:component}} ``` -If you're planning to read the file content, you need to do it asynchronously, to keep the rest of the UI interactive. This example loads the content of the selected files in an async closure: +If you're planning to read the file content, you need to do it asynchronously, to keep the rest of the UI interactive. This example event handler loads the content of the selected files in an async closure: + +```rust, no_run +{{#include src/doc_examples/input_fileengine_async.rs:onchange_event}} +``` + +Lastly, this example shows you how to select a folder, by setting the `directory` attribute to `true`. ```rust, no_run -{{#include src/doc_examples/input_fileengine_async.rs:component}} +{{#include src/doc_examples/input_fileengine_folder.rs:rsx}} ``` \ No newline at end of file diff --git a/src/doc_examples/input_fileengine.rs b/src/doc_examples/input_fileengine.rs index 2fac9b4df..cdb9fd7f4 100644 --- a/src/doc_examples/input_fileengine.rs +++ b/src/doc_examples/input_fileengine.rs @@ -3,14 +3,15 @@ use dioxus::prelude::*; // ANCHOR: component pub fn App(cx: Scope) -> Element { + // ANCHOR: rsx let filenames: &UseRef> = use_ref(cx, Vec::new); - cx.render(rsx! { input { // tell the input to pick a file r#type:"file", // list the accepted extensions accept: ".txt, .rs", + // pick multiple files multiple: true, onchange: |evt| { if let Some(file_engine) = &evt.files { @@ -22,5 +23,6 @@ pub fn App(cx: Scope) -> Element { } } }) + // ANCHOR_END: rsx } // ANCHOR_END: component diff --git a/src/doc_examples/input_fileengine_async.rs b/src/doc_examples/input_fileengine_async.rs index 514688e0c..3da36ed65 100644 --- a/src/doc_examples/input_fileengine_async.rs +++ b/src/doc_examples/input_fileengine_async.rs @@ -7,13 +7,10 @@ pub fn App(cx: Scope) -> Element { cx.render(rsx! { input { - // we tell the component what to render - value: "{name}", - // tell the input to pick a file r#type:"file", - // list the accepted extensions accept: ".txt, .rs", multiple: true, + // ANCHOR: onchange_event onchange: |evt| { // A helper macro to use hooks in async environments to_owned![files_uploaded]; @@ -30,6 +27,7 @@ pub fn App(cx: Scope) -> Element { } } } + // ANCHOR_END: onchange_event } }) } diff --git a/src/doc_examples/input_fileengine_folder.rs b/src/doc_examples/input_fileengine_folder.rs new file mode 100644 index 000000000..15d8228d6 --- /dev/null +++ b/src/doc_examples/input_fileengine_folder.rs @@ -0,0 +1,26 @@ +#![allow(non_snake_case)] +use dioxus::prelude::*; + +// ANCHOR: component +pub fn App(cx: Scope) -> Element { + let filenames: &UseRef> = use_ref(cx, Vec::new); + cx.render(rsx! { + // ANCHOR: rsx + input { + r#type:"file", + // Select a folder by setting the directory attribute + directory: true, + onchange: |evt| { + if let Some(file_engine) = &evt.files { + let files = file_engine.files(); + for file_name in &files { + println!("{}", file_name); + // Do something with the folder path + } + } + } + } + // ANCHOR_END: rsx + }) +} +// ANCHOR_END: component