yew使用bootstrap的Tooltip和Popover

  • 4 分钟阅读
  • 标签: 
  • yew

前言

  • 在使用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>
};

效果

Untitled

参考