mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Split out listener code from html module
This commit is contained in:
parent
3ef8ee5b80
commit
2020944f49
175
src/html/listener.rs
Normal file
175
src/html/listener.rs
Normal file
@ -0,0 +1,175 @@
|
||||
use super::*;
|
||||
use crate::virtual_dom::Listener;
|
||||
use log::debug;
|
||||
use stdweb::web::html_element::SelectElement;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::web::{EventListenerHandle, FileList, INode};
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
|
||||
macro_rules! impl_action {
|
||||
($($action:ident($event:ident : $type:ident) -> $ret:ty => $convert:expr)*) => {$(
|
||||
/// An abstract implementation of a listener.
|
||||
pub mod $action {
|
||||
use stdweb::web::{IEventTarget, Element};
|
||||
use stdweb::web::event::{IEvent, $type};
|
||||
use super::*;
|
||||
|
||||
/// A wrapper for a callback.
|
||||
/// Listener extracted from here when attached.
|
||||
pub struct Wrapper<F>(Option<F>);
|
||||
|
||||
/// And event type which keeps the returned type.
|
||||
pub type Event = $ret;
|
||||
|
||||
impl<F, MSG> From<F> for Wrapper<F>
|
||||
where
|
||||
MSG: 'static,
|
||||
F: Fn($ret) -> MSG + 'static,
|
||||
{
|
||||
fn from(handler: F) -> Self {
|
||||
Wrapper(Some(handler))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, COMP> Listener<COMP> for Wrapper<T>
|
||||
where
|
||||
T: Fn($ret) -> COMP::Message + 'static,
|
||||
COMP: Component + Renderable<COMP>,
|
||||
{
|
||||
fn kind(&self) -> &'static str {
|
||||
stringify!($action)
|
||||
}
|
||||
|
||||
fn attach(&mut self, element: &Element, mut activator: Scope<COMP>)
|
||||
-> EventListenerHandle {
|
||||
let handler = self.0.take().expect("tried to attach listener twice");
|
||||
let this = element.clone();
|
||||
let listener = move |event: $type| {
|
||||
debug!("Event handler: {}", stringify!($type));
|
||||
event.stop_propagation();
|
||||
let handy_event: $ret = $convert(&this, event);
|
||||
let msg = handler(handy_event);
|
||||
activator.send_message(msg);
|
||||
};
|
||||
element.add_event_listener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
// Inspired by: http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events
|
||||
impl_action! {
|
||||
onclick(event: ClickEvent) -> ClickEvent => |_, event| { event }
|
||||
ondoubleclick(event: DoubleClickEvent) -> DoubleClickEvent => |_, event| { event }
|
||||
onkeypress(event: KeyPressEvent) -> KeyPressEvent => |_, event| { event }
|
||||
onkeydown(event: KeyDownEvent) -> KeyDownEvent => |_, event| { event }
|
||||
onkeyup(event: KeyUpEvent) -> KeyUpEvent => |_, event| { event }
|
||||
onmousemove(event: MouseMoveEvent) -> MouseMoveEvent => |_, event| { event }
|
||||
onmousedown(event: MouseDownEvent) -> MouseDownEvent => |_, event| { event }
|
||||
onmouseup(event: MouseUpEvent) -> MouseUpEvent => |_, event| { event }
|
||||
onmouseover(event: MouseOverEvent) -> MouseOverEvent => |_, event| { event }
|
||||
onmouseout(event: MouseOutEvent) -> MouseOutEvent => |_, event| { event }
|
||||
onmouseenter(event: MouseEnterEvent) -> MouseEnterEvent => |_, event| { event }
|
||||
onmouseleave(event: MouseLeaveEvent) -> MouseLeaveEvent => |_, event| { event }
|
||||
onmousewheel(event: MouseWheelEvent) -> MouseWheelEvent => |_, event| { event }
|
||||
ongotpointercapture(event: GotPointerCaptureEvent) -> GotPointerCaptureEvent => |_, event| { event }
|
||||
onlostpointercapture(event: LostPointerCaptureEvent) -> LostPointerCaptureEvent => |_, event| { event }
|
||||
onpointercancel(event: PointerCancelEvent) -> PointerCancelEvent => |_, event| { event }
|
||||
onpointerdown(event: PointerDownEvent) -> PointerDownEvent => |_, event| { event }
|
||||
onpointerenter(event: PointerEnterEvent) -> PointerEnterEvent => |_, event| { event }
|
||||
onpointerleave(event: PointerLeaveEvent) -> PointerLeaveEvent => |_, event| { event }
|
||||
onpointermove(event: PointerMoveEvent) -> PointerMoveEvent => |_, event| { event }
|
||||
onpointerout(event: PointerOutEvent) -> PointerOutEvent => |_, event| { event }
|
||||
onpointerover(event: PointerOverEvent) -> PointerOverEvent => |_, event| { event }
|
||||
onpointerup(event: PointerUpEvent) -> PointerUpEvent => |_, event| { event }
|
||||
onscroll(event: ScrollEvent) -> ScrollEvent => |_, event| { event }
|
||||
onblur(event: BlurEvent) -> BlurEvent => |_, event| { event }
|
||||
onfocus(event: FocusEvent) -> FocusEvent => |_, event| { event }
|
||||
onsubmit(event: SubmitEvent) -> SubmitEvent => |_, event| { event }
|
||||
ondragstart(event: DragStartEvent) -> DragStartEvent => |_, event| { event }
|
||||
ondrag(event: DragEvent) -> DragEvent => |_, event| { event }
|
||||
ondragend(event: DragEndEvent) -> DragEndEvent => |_, event| { event }
|
||||
ondragenter(event: DragEnterEvent) -> DragEnterEvent => |_, event| { event }
|
||||
ondragleave(event: DragLeaveEvent) -> DragLeaveEvent => |_, event| { event }
|
||||
ondragover(event: DragOverEvent) -> DragOverEvent => |_, event| { event }
|
||||
ondragexit(event: DragExitEvent) -> DragExitEvent => |_, event| { event }
|
||||
ondrop(event: DragDropEvent) -> DragDropEvent => |_, event| { event }
|
||||
oncontextmenu(event: ContextMenuEvent) -> ContextMenuEvent => |_, event| { event }
|
||||
oninput(event: InputEvent) -> InputData => |this: &Element, _| {
|
||||
use stdweb::web::html_element::{InputElement, TextAreaElement};
|
||||
use stdweb::unstable::TryInto;
|
||||
// Normally only InputElement or TextAreaElement can have an oninput event listener. In
|
||||
// practice though any element with `contenteditable=true` may generate such events,
|
||||
// therefore here we fall back to just returning the text content of the node.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event.
|
||||
let v1 = this.clone().try_into().map(|input: InputElement| input.raw_value()).ok();
|
||||
let v2 = this.clone().try_into().map(|input: TextAreaElement| input.value()).ok();
|
||||
let v3 = this.text_content();
|
||||
let value = v1.or(v2).or(v3)
|
||||
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
|
||||
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();
|
||||
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();
|
||||
ChangeData::Value(tae.value())
|
||||
}
|
||||
"SELECT" => {
|
||||
let se: SelectElement = this.clone().try_into().unwrap();
|
||||
ChangeData::Select(se)
|
||||
}
|
||||
_ => {
|
||||
panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing data from `oninput` event.
|
||||
#[derive(Debug)]
|
||||
pub struct InputData {
|
||||
/// Inserted characters. Contains value from
|
||||
/// [InputEvent](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data).
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
// There is no '.../Web/API/ChangeEvent/data' (for onchange) similar to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data (for oninput).
|
||||
// ChangeData actually contains the value of the InputElement/TextAreaElement
|
||||
// after `change` event occured or contains the SelectElement (see more at the
|
||||
// variant ChangeData::Select)
|
||||
|
||||
/// A type representing change of value(s) of an element after committed by user
|
||||
/// ([onchange event](https://developer.mozilla.org/en-US/docs/Web/Events/change)).
|
||||
#[derive(Debug)]
|
||||
pub enum ChangeData {
|
||||
/// Value of the element in cases of `<input>`, `<textarea>`
|
||||
Value(String),
|
||||
/// SelectElement in case of `<select>` element. You can use one of methods of SelectElement
|
||||
/// 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),
|
||||
}
|
||||
196
src/html/mod.rs
196
src/html/mod.rs
@ -3,19 +3,22 @@
|
||||
//! Also this module contains declaration of `Component` trait which used
|
||||
//! to create own UI-components.
|
||||
|
||||
mod listener;
|
||||
|
||||
use crate::callback::Callback;
|
||||
use crate::scheduler::{scheduler, Runnable, Shared};
|
||||
use crate::virtual_dom::{Listener, VDiff, VNode};
|
||||
use log::debug;
|
||||
use crate::virtual_dom::{VDiff, VNode};
|
||||
pub use listener::*;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use stdweb::web::html_element::SelectElement;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::web::{Element, EventListenerHandle, FileList, INode, Node};
|
||||
use stdweb::web::{Element, Node};
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
|
||||
/// A type which expected as a result of `view` function implementation.
|
||||
pub type Html<MSG> = VNode<MSG>;
|
||||
|
||||
/// This type indicates that component should be rendered again.
|
||||
pub type ShouldRender = bool;
|
||||
|
||||
@ -42,6 +45,12 @@ pub trait Component: Sized + 'static {
|
||||
fn destroy(&mut self) {} // TODO Replace with `Drop`
|
||||
}
|
||||
|
||||
/// Should be rendered relative to context and component environment.
|
||||
pub trait Renderable<COMP: Component> {
|
||||
/// Called by rendering loop.
|
||||
fn view(&self) -> Html<COMP>;
|
||||
}
|
||||
|
||||
/// Trait for building properties for a component
|
||||
pub trait Properties {
|
||||
/// Builder that will be used to construct properties
|
||||
@ -61,17 +70,12 @@ impl Properties for () {
|
||||
EmptyBuilder
|
||||
}
|
||||
}
|
||||
|
||||
impl EmptyBuilder {
|
||||
/// Build empty properties
|
||||
pub fn build(self) {}
|
||||
}
|
||||
|
||||
/// Should be rendered relative to context and component environment.
|
||||
pub trait Renderable<COMP: Component> {
|
||||
/// Called by rendering loop.
|
||||
fn view(&self) -> Html<COMP>;
|
||||
}
|
||||
|
||||
/// Updates for a `Components` instance. Used by scope sender.
|
||||
pub(crate) enum ComponentUpdate<COMP: Component> {
|
||||
/// Wraps messages for a component.
|
||||
@ -349,176 +353,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A type which expected as a result of `view` function implementation.
|
||||
pub type Html<MSG> = VNode<MSG>;
|
||||
|
||||
macro_rules! impl_action {
|
||||
($($action:ident($event:ident : $type:ident) -> $ret:ty => $convert:expr)*) => {$(
|
||||
/// An abstract implementation of a listener.
|
||||
pub mod $action {
|
||||
use stdweb::web::{IEventTarget, Element};
|
||||
use stdweb::web::event::{IEvent, $type};
|
||||
use super::*;
|
||||
|
||||
/// A wrapper for a callback.
|
||||
/// Listener extracted from here when attached.
|
||||
pub struct Wrapper<F>(Option<F>);
|
||||
|
||||
/// And event type which keeps the returned type.
|
||||
pub type Event = $ret;
|
||||
|
||||
impl<F, MSG> From<F> for Wrapper<F>
|
||||
where
|
||||
MSG: 'static,
|
||||
F: Fn($ret) -> MSG + 'static,
|
||||
{
|
||||
fn from(handler: F) -> Self {
|
||||
Wrapper(Some(handler))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, COMP> Listener<COMP> for Wrapper<T>
|
||||
where
|
||||
T: Fn($ret) -> COMP::Message + 'static,
|
||||
COMP: Component + Renderable<COMP>,
|
||||
{
|
||||
fn kind(&self) -> &'static str {
|
||||
stringify!($action)
|
||||
}
|
||||
|
||||
fn attach(&mut self, element: &Element, mut activator: Scope<COMP>)
|
||||
-> EventListenerHandle {
|
||||
let handler = self.0.take().expect("tried to attach listener twice");
|
||||
let this = element.clone();
|
||||
let listener = move |event: $type| {
|
||||
debug!("Event handler: {}", stringify!($type));
|
||||
event.stop_propagation();
|
||||
let handy_event: $ret = $convert(&this, event);
|
||||
let msg = handler(handy_event);
|
||||
activator.send_message(msg);
|
||||
};
|
||||
element.add_event_listener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
// Inspired by: http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events
|
||||
impl_action! {
|
||||
onclick(event: ClickEvent) -> ClickEvent => |_, event| { event }
|
||||
ondoubleclick(event: DoubleClickEvent) -> DoubleClickEvent => |_, event| { event }
|
||||
onkeypress(event: KeyPressEvent) -> KeyPressEvent => |_, event| { event }
|
||||
onkeydown(event: KeyDownEvent) -> KeyDownEvent => |_, event| { event }
|
||||
onkeyup(event: KeyUpEvent) -> KeyUpEvent => |_, event| { event }
|
||||
onmousemove(event: MouseMoveEvent) -> MouseMoveEvent => |_, event| { event }
|
||||
onmousedown(event: MouseDownEvent) -> MouseDownEvent => |_, event| { event }
|
||||
onmouseup(event: MouseUpEvent) -> MouseUpEvent => |_, event| { event }
|
||||
onmouseover(event: MouseOverEvent) -> MouseOverEvent => |_, event| { event }
|
||||
onmouseout(event: MouseOutEvent) -> MouseOutEvent => |_, event| { event }
|
||||
onmouseenter(event: MouseEnterEvent) -> MouseEnterEvent => |_, event| { event }
|
||||
onmouseleave(event: MouseLeaveEvent) -> MouseLeaveEvent => |_, event| { event }
|
||||
onmousewheel(event: MouseWheelEvent) -> MouseWheelEvent => |_, event| { event }
|
||||
ongotpointercapture(event: GotPointerCaptureEvent) -> GotPointerCaptureEvent => |_, event| { event }
|
||||
onlostpointercapture(event: LostPointerCaptureEvent) -> LostPointerCaptureEvent => |_, event| { event }
|
||||
onpointercancel(event: PointerCancelEvent) -> PointerCancelEvent => |_, event| { event }
|
||||
onpointerdown(event: PointerDownEvent) -> PointerDownEvent => |_, event| { event }
|
||||
onpointerenter(event: PointerEnterEvent) -> PointerEnterEvent => |_, event| { event }
|
||||
onpointerleave(event: PointerLeaveEvent) -> PointerLeaveEvent => |_, event| { event }
|
||||
onpointermove(event: PointerMoveEvent) -> PointerMoveEvent => |_, event| { event }
|
||||
onpointerout(event: PointerOutEvent) -> PointerOutEvent => |_, event| { event }
|
||||
onpointerover(event: PointerOverEvent) -> PointerOverEvent => |_, event| { event }
|
||||
onpointerup(event: PointerUpEvent) -> PointerUpEvent => |_, event| { event }
|
||||
onscroll(event: ScrollEvent) -> ScrollEvent => |_, event| { event }
|
||||
onblur(event: BlurEvent) -> BlurEvent => |_, event| { event }
|
||||
onfocus(event: FocusEvent) -> FocusEvent => |_, event| { event }
|
||||
onsubmit(event: SubmitEvent) -> SubmitEvent => |_, event| { event }
|
||||
ondragstart(event: DragStartEvent) -> DragStartEvent => |_, event| { event }
|
||||
ondrag(event: DragEvent) -> DragEvent => |_, event| { event }
|
||||
ondragend(event: DragEndEvent) -> DragEndEvent => |_, event| { event }
|
||||
ondragenter(event: DragEnterEvent) -> DragEnterEvent => |_, event| { event }
|
||||
ondragleave(event: DragLeaveEvent) -> DragLeaveEvent => |_, event| { event }
|
||||
ondragover(event: DragOverEvent) -> DragOverEvent => |_, event| { event }
|
||||
ondragexit(event: DragExitEvent) -> DragExitEvent => |_, event| { event }
|
||||
ondrop(event: DragDropEvent) -> DragDropEvent => |_, event| { event }
|
||||
oncontextmenu(event: ContextMenuEvent) -> ContextMenuEvent => |_, event| { event }
|
||||
oninput(event: InputEvent) -> InputData => |this: &Element, _| {
|
||||
use stdweb::web::html_element::{InputElement, TextAreaElement};
|
||||
use stdweb::unstable::TryInto;
|
||||
// Normally only InputElement or TextAreaElement can have an oninput event listener. In
|
||||
// practice though any element with `contenteditable=true` may generate such events,
|
||||
// therefore here we fall back to just returning the text content of the node.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event.
|
||||
let v1 = this.clone().try_into().map(|input: InputElement| input.raw_value()).ok();
|
||||
let v2 = this.clone().try_into().map(|input: TextAreaElement| input.value()).ok();
|
||||
let v3 = this.text_content();
|
||||
let value = v1.or(v2).or(v3)
|
||||
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
|
||||
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();
|
||||
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();
|
||||
ChangeData::Value(tae.value())
|
||||
}
|
||||
"SELECT" => {
|
||||
let se: SelectElement = this.clone().try_into().unwrap();
|
||||
ChangeData::Select(se)
|
||||
}
|
||||
_ => {
|
||||
panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing data from `oninput` event.
|
||||
#[derive(Debug)]
|
||||
pub struct InputData {
|
||||
/// Inserted characters. Contains value from
|
||||
/// [InputEvent](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data).
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
// There is no '.../Web/API/ChangeEvent/data' (for onchange) similar to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data (for oninput).
|
||||
// ChangeData actually contains the value of the InputElement/TextAreaElement
|
||||
// after `change` event occured or contains the SelectElement (see more at the
|
||||
// variant ChangeData::Select)
|
||||
|
||||
/// A type representing change of value(s) of an element after committed by user
|
||||
/// ([onchange event](https://developer.mozilla.org/en-US/docs/Web/Events/change)).
|
||||
#[derive(Debug)]
|
||||
pub enum ChangeData {
|
||||
/// Value of the element in cases of `<input>`, `<textarea>`
|
||||
Value(String),
|
||||
/// SelectElement in case of `<select>` element. You can use one of methods of SelectElement
|
||||
/// 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.
|
||||
#[derive(Debug)]
|
||||
pub struct Href {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user