前言
- 在使用https://github.com/yewstack/yew和https://github.com/twbs/bootstrap/开发前端项目的时候,由于我使用yew这个框架写的是单页面应用,在页面数据还没获取到并不会去渲染整个页面,返回的也不是纯html页面,即使用了
popperjs
,根据bootstrap官方在页面最后加入下面的激活代码也是无效的。
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
- 所以需要从rust这边获取到
bootstrap.Popover
这个方法,然后执行创建新的元素才可以显示冒泡组件。
冒泡组件
- 在bootstrap文档中,冒泡组件需要指定
data-bs-toggle
属性,每当页面加载完成会执行上面的javascript代码过滤属性为[data-bs-toggle="popover"]
的元素(父组件),然后调用bootstrap.Popover在body标签下创建以toggle
属性开头的随机字符串为id的冒泡组件,最后在父组件上添加这个随机生成的id进行关联。这样就会父组件附近(指定的placement属性)显示这个提示帮助信息。
<button type="button" class="btn btn-lg btn-danger" data-bs-toggle="popover" data-bs-title="Popover title" data-bs-content="And here's some amazing content. It's very engaging. Right?">Click to toggle popover</button>
- 由于yew没办法直接执行javascript代码去创建冒泡组件,所以鼠标移上去也不会触发事件,就不会显示冒泡组件提示信息。
Yew封装组件
- yew组件分为函数组件和结构组件,结构组件适合封装组件。先给组件起个名字
TooltipPopover
,先按照冒泡组件的option创建Prop属性,并且给属性设置默认值。
#[derive(PartialEq, Clone, Default, Properties)]
pub struct TooltipPopoverProp {
#[prop_or_else(default_toggle)]
pub toggle: AttrValue,
#[prop_or_else(default_trigger)]
pub trigger: AttrValue,
#[prop_or_else(default_placement)]
pub placement: AttrValue,
#[prop_or(false)]
pub html: bool,
#[prop_or_default]
pub content: AttrValue,
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
pub children: Html,
}
fn default_toggle() -> AttrValue {
AttrValue::from("popover")
}
fn default_trigger() -> AttrValue {
AttrValue::from("hover")
}
fn default_placement() -> AttrValue {
AttrValue::from("top")
}
- 然后就是实现给TooltipPopover结构实现组件Component特性trait,
Properties
属性为上面的TooltipPopoverProp,初始化方法,然后就是补充渲染方法。
pub struct TooltipPopover;
impl Component for TooltipPopover {
type Message = ();
type Properties = TooltipPopoverProp;
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
let TooltipPopoverProp { class, content, html, placement, toggle, children, trigger, .. } =
ctx.props();
let on_mouse_enter = {
Callback::from(move |event: MouseEvent| {
event.stop_propagation();
let target = event.target_unchecked_into::<HtmlElement>();
let window = web_sys::window().unwrap();
let bootstrap = js_sys::Reflect
::get(&window, &wasm_bindgen::JsValue::from("bootstrap"))
.expect("Error! Failed to get property bootstrap");
let popover = js_sys::Reflect
::get(&bootstrap, &JsValue::from("Popover"))
.expect("Error! Failed to get property Popover");
let get_or_create_instance = js_sys::Reflect
::get(&popover, &JsValue::from("getOrCreateInstance"))
.expect("Error! Failed to get property getOrCreateInstance");
let popover_bootstrap = get_or_create_instance
.clone()
.unchecked_into::<js_sys::Function>()
.call1(&popover, &target)
.expect("Error! Failed to call function getOrCreateInstance");
let show_popover = js_sys::Reflect
::get(&popover_bootstrap, &JsValue::from("show"))
.expect("Error! Failed to get property show");
let show_callback = Closure::wrap(
Box::new(move |_: MouseEvent| {
show_popover
.clone()
.unchecked_into::<js_sys::Function>()
.call0(&popover_bootstrap)
.expect("Error! Failed to call function show");
}) as Box<dyn FnMut(_)>
);
target
.clone()
.unchecked_into::<Element>()
.add_event_listener_with_callback(
"click",
show_callback.as_ref().unchecked_ref()
)
.expect("Error! Failed to add event listener");
show_callback.forget();
})
};
html! {
<div class={class.clone()}>
<span
tabindex=0
data-bs-trigger={trigger}
onmouseenter={on_mouse_enter}
data-bs-toggle={toggle.clone()}
data-bs-container="body"
data-bs-placement={placement}
data-bs-html={html.to_string()}
data-bs-content={content.clone()}
>
{children.clone()}
</span>
</div>
}
}
}
- 主要是鼠标的触发事件这个函数,根据官方文档的methods方法可以实现Popover的方法调用,要做的就是把下面代码转化为rust代码。
// getOrCreateInstance example
const popover = bootstrap.Popover.getOrCreateInstance('#example') // Returns a Bootstrap popover instance
// setContent example
myPopover.setContent({
'.popover-header': 'another title',
'.popover-body': 'another content'
})
- 当鼠标进入冒泡的父组件后触发下面的函数,这个函数会根据父组件的
data-bs-*
的属性创建冒泡组件,这个冒泡组件会和父组件的aria-describedby
属性下绑定同一个id。
let on_mouse_enter = {
Callback::from(move |event: MouseEvent| {
event.stop_propagation();
let target = event.target_unchecked_into::<HtmlElement>();
let window = web_sys::window().unwrap();
let bootstrap = js_sys::Reflect::get(&window, &wasm_bindgen::JsValue::from("bootstrap"))
.expect("Error! Failed to get property bootstrap");
let popover = js_sys::Reflect::get(&bootstrap, &JsValue::from("Popover"))
.expect("Error! Failed to get property Popover");
let get_or_create_instance =
js_sys::Reflect::get(&popover, &JsValue::from("getOrCreateInstance"))
.expect("Error! Failed to get property getOrCreateInstance");
let popover_bootstrap = get_or_create_instance
.clone()
.unchecked_into::<js_sys::Function>()
.call1(&popover, &target)
.expect("Error! Failed to call function getOrCreateInstance");
let show_popover = js_sys::Reflect::get(&popover_bootstrap, &JsValue::from("show"))
.expect("Error! Failed to get property show");
let show_callback =
Closure::wrap(Box::new(move |_: MouseEvent| {
show_popover
.clone()
.unchecked_into::<js_sys::Function>()
.call0(&popover_bootstrap)
.expect("Error! Failed to call function show");
}) as Box<dyn FnMut(_)>);
target
.clone()
.unchecked_into::<Element>()
.add_event_listener_with_callback("click", show_callback.as_ref().unchecked_ref())
.expect("Error! Failed to add event listener");
show_callback.forget();
})
};
- 这个函数的流程为:通过当前window获取到
bootstrap
这个对象,然后再获取Popover
这个类,获取getOrCreateInstance
,最后和javascript一样调用getOrCreateInstance
函数得到一个实例化的对象。继续从这个实例化对象中获取一个显示函数,也就是文档上面的show函数,然后这个实例化对象注册回调函数(就是当鼠标点击的时候调用show函数),这里使用click点击事件,因为鼠标进入事件已经被用了。
调用冒泡组件
- 导入TooltipPopover组件,使用只需要填属性和给子节点元素就可以了
html! {
<TooltipPopover
class={classes!(["form-help"])}
toggle={"popover"}
placement={"left"}
content={"Running On/With"}>
<i class={classes!( ["ti","ti-stack-2"])}></i>
</TooltipPopover>
...
<TooltipPopover
toggle={"toggle"}
placement={"top"}
content={des}>
<span class={classes!(["badge",class_str])}>
<i class={classes!( ["ti",icon])}></i>{value}
</span>
</TooltipPopover>
};
效果

参考