mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add files uploading support
This commit is contained in:
parent
b1eee76e31
commit
654716fe7e
@ -8,6 +8,13 @@
|
||||
|
||||
- Added `start_app` method. It's a shortcut to initialize a component and mount it to the body.
|
||||
|
||||
- Added handling of files of `input` element. There is new `ChangeData::Files` variant
|
||||
of `onchange` handler.
|
||||
|
||||
- Added `ReaderService` to read data from `File` instances.
|
||||
|
||||
- New example `file_upload` that prints sizes of uploaded files.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
## 0.5 - Released 2019-02-01
|
||||
|
||||
8
examples/file_upload/Cargo.toml
Normal file
8
examples/file_upload/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "file_upload"
|
||||
version = "0.1.0"
|
||||
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
yew = { path = "../.." }
|
||||
71
examples/file_upload/src/lib.rs
Normal file
71
examples/file_upload/src/lib.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use yew::{html, ChangeData, Component, ComponentLink, Html, Renderable, ShouldRender};
|
||||
use yew::services::reader::{File, FileData, ReaderService, ReaderTask};
|
||||
|
||||
pub struct Model {
|
||||
link: ComponentLink<Model>,
|
||||
reader: ReaderService,
|
||||
tasks: Vec<ReaderTask>,
|
||||
files: Vec<FileData>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Loaded(FileData),
|
||||
Files(Vec<File>),
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
Model {
|
||||
reader: ReaderService::new(),
|
||||
link,
|
||||
tasks: vec![],
|
||||
files: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Loaded(file) => {
|
||||
self.files.push(file);
|
||||
}
|
||||
Msg::Files(files) => {
|
||||
for file in files.into_iter() {
|
||||
let callback = self.link.send_back(Msg::Loaded);
|
||||
let task = self.reader.read_file(file, callback);
|
||||
self.tasks.push(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable<Model> for Model {
|
||||
fn view(&self) -> Html<Self> {
|
||||
html! {
|
||||
<div>
|
||||
<input type="file", multiple=true, onchange=|value| {
|
||||
let mut result = Vec::new();
|
||||
if let ChangeData::Files(files) = value {
|
||||
result.extend(files);
|
||||
}
|
||||
Msg::Files(result)
|
||||
},/>
|
||||
<ul>
|
||||
{ for self.files.iter().map(|f| self.view_file(f)) }
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn view_file(&self, file: &FileData) -> Html<Self> {
|
||||
html! {
|
||||
<li>{ format!("file: {}, size: {}", file.name, file.content.len()) }</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
3
examples/file_upload/src/main.rs
Normal file
3
examples/file_upload/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
yew::start_app::<file_upload::Model>();
|
||||
}
|
||||
18
src/html.rs
18
src/html.rs
@ -5,7 +5,7 @@
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use stdweb::web::{Element, EventListenerHandle, INode, Node};
|
||||
use stdweb::web::{Element, EventListenerHandle, FileList, INode, Node};
|
||||
use stdweb::web::html_element::SelectElement;
|
||||
use virtual_dom::{Listener, VDiff, VNode};
|
||||
use callback::Callback;
|
||||
@ -361,12 +361,24 @@ impl_action! {
|
||||
InputData { value }
|
||||
}
|
||||
onchange(event: ChangeEvent) -> ChangeData => |this: &Element, _| {
|
||||
use stdweb::web::{FileList, IElement};
|
||||
use stdweb::web::html_element::{InputElement, TextAreaElement, SelectElement};
|
||||
use stdweb::unstable::TryInto;
|
||||
match this.node_name().as_ref() {
|
||||
"INPUT" => {
|
||||
let input: InputElement = this.clone().try_into().unwrap();
|
||||
ChangeData::Value(input.raw_value())
|
||||
let is_file = input.get_attribute("type").map(|value| {
|
||||
value.eq_ignore_ascii_case("file")
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if is_file {
|
||||
let files: FileList = js!( return @{input}.files; )
|
||||
.try_into()
|
||||
.unwrap();
|
||||
ChangeData::Files(files)
|
||||
} else {
|
||||
ChangeData::Value(input.raw_value())
|
||||
}
|
||||
}
|
||||
"TEXTAREA" => {
|
||||
let tae: TextAreaElement = this.clone().try_into().unwrap();
|
||||
@ -407,6 +419,8 @@ pub enum ChangeData {
|
||||
/// to collect your required data such as: `value`, `selected_index`, `selected_indices` or
|
||||
/// `selected_values`. You can also iterate throught `selected_options` yourself.
|
||||
Select(SelectElement),
|
||||
/// Files
|
||||
Files(FileList),
|
||||
}
|
||||
|
||||
/// A bridging type for checking `href` attribute value.
|
||||
|
||||
@ -7,6 +7,7 @@ pub mod console;
|
||||
pub mod dialog;
|
||||
pub mod fetch;
|
||||
pub mod interval;
|
||||
pub mod reader;
|
||||
pub mod render;
|
||||
pub mod storage;
|
||||
pub mod timeout;
|
||||
@ -16,6 +17,7 @@ pub use self::console::ConsoleService;
|
||||
pub use self::dialog::DialogService;
|
||||
pub use self::fetch::FetchService;
|
||||
pub use self::interval::IntervalService;
|
||||
pub use self::reader::ReaderService;
|
||||
pub use self::render::RenderService;
|
||||
pub use self::storage::StorageService;
|
||||
pub use self::timeout::TimeoutService;
|
||||
|
||||
86
src/services/reader.rs
Normal file
86
src/services/reader.rs
Normal file
@ -0,0 +1,86 @@
|
||||
//! Service to load files using `FileReader`.
|
||||
|
||||
pub use stdweb::web::File;
|
||||
use stdweb::web::{
|
||||
IEventTarget,
|
||||
FileReader,
|
||||
FileReaderReadyState,
|
||||
FileReaderResult,
|
||||
TypedArray,
|
||||
};
|
||||
use stdweb::web::event::{
|
||||
LoadEndEvent,
|
||||
};
|
||||
use callback::Callback;
|
||||
use super::Task;
|
||||
|
||||
/// Struct that represents data of file.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileData {
|
||||
/// Name of loaded file.
|
||||
pub name: String,
|
||||
/// Content of loaded file.
|
||||
pub content: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A reader service attached to a user context.
|
||||
#[derive(Default)]
|
||||
pub struct ReaderService {}
|
||||
|
||||
impl ReaderService {
|
||||
/// Creates a new service instance connected to `App` by provided `sender`.
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
/// Reads all bytes from files and returns them with a callback.
|
||||
pub fn read_file(&mut self, file: File, callback: Callback<FileData>) -> ReaderTask {
|
||||
let file_reader = FileReader::new();
|
||||
let reader = file_reader.clone();
|
||||
let name = file.name();
|
||||
file_reader.add_event_listener(move |event: LoadEndEvent| {
|
||||
match reader.result() {
|
||||
Some(FileReaderResult::String(_)) => {
|
||||
unreachable!();
|
||||
}
|
||||
Some(FileReaderResult::ArrayBuffer(buffer)) => {
|
||||
let array: TypedArray<u8> = buffer.into();
|
||||
let data = FileData {
|
||||
name: name.clone(),
|
||||
content: array.to_vec(),
|
||||
};
|
||||
callback.emit(data);
|
||||
}
|
||||
None => { }
|
||||
}
|
||||
});
|
||||
file_reader.read_as_array_buffer(&file).unwrap();
|
||||
ReaderTask {
|
||||
file_reader,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to control reading.
|
||||
#[must_use]
|
||||
pub struct ReaderTask {
|
||||
file_reader: FileReader,
|
||||
}
|
||||
|
||||
impl Task for ReaderTask {
|
||||
fn is_active(&self) -> bool {
|
||||
self.file_reader.ready_state() == FileReaderReadyState::Loading
|
||||
}
|
||||
|
||||
fn cancel(&mut self) {
|
||||
self.file_reader.abort();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ReaderTask {
|
||||
fn drop(&mut self) {
|
||||
if self.is_active() {
|
||||
self.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user