use web_sys::Element;
use super::{alias::*, App};
use crate::{dom_types::View, orders::OrdersContainer, routing, util};
pub type InitFn<Ms, Mdl, ElC, GMs> =
Box<dyn FnOnce(routing::Url, &mut OrdersContainer<Ms, Mdl, ElC, GMs>) -> Init<Mdl>>;
pub trait MountPoint {
fn element(self) -> Element;
}
impl MountPoint for &str {
fn element(self) -> Element {
util::document().get_element_by_id(self).unwrap_or_else(|| {
panic!(
"Can't find element with id={:?} - app cannot be mounted!\n\
(Id defaults to \"app\", or can be set with the .mount() method)",
self
)
})
}
}
impl MountPoint for Element {
fn element(self) -> Element {
self
}
}
impl MountPoint for web_sys::HtmlElement {
fn element(self) -> Element {
self.into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UrlHandling {
PassToRoutes,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MountType {
Takeover,
Append,
}
pub struct Init<Mdl> {
pub model: Mdl,
pub url_handling: UrlHandling,
pub mount_type: MountType,
}
impl<Mdl> Init<Mdl> {
pub const fn new(model: Mdl) -> Self {
Self {
model,
url_handling: UrlHandling::PassToRoutes,
mount_type: MountType::Append,
}
}
pub const fn new_with_url_handling(model: Mdl, url_handling: UrlHandling) -> Self {
Self {
model,
url_handling,
mount_type: MountType::Append,
}
}
}
impl<Mdl: Default> Default for Init<Mdl> {
fn default() -> Self {
Self {
model: Mdl::default(),
url_handling: UrlHandling::PassToRoutes,
mount_type: MountType::Append,
}
}
}
pub struct Builder<Ms: 'static, Mdl: 'static, ElC: View<Ms>, GMs> {
init: InitFn<Ms, Mdl, ElC, GMs>,
update: UpdateFn<Ms, Mdl, ElC, GMs>,
sink: Option<SinkFn<Ms, Mdl, ElC, GMs>>,
view: ViewFn<Mdl, ElC>,
mount_point: Option<Element>,
routes: Option<RoutesFn<Ms>>,
window_events: Option<WindowEvents<Ms, Mdl>>,
}
impl<Ms, Mdl, ElC: View<Ms> + 'static, GMs: 'static> Builder<Ms, Mdl, ElC, GMs> {
pub(super) fn new(
init: InitFn<Ms, Mdl, ElC, GMs>,
update: UpdateFn<Ms, Mdl, ElC, GMs>,
view: ViewFn<Mdl, ElC>,
) -> Self {
Self {
init,
update,
sink: None,
view,
mount_point: None,
routes: None,
window_events: None,
}
}
pub fn mount(mut self, mount_point: impl MountPoint) -> Self {
let _ = util::document().query_selector("html");
self.mount_point = Some(mount_point.element());
self
}
pub fn routes(mut self, routes: RoutesFn<Ms>) -> Self {
self.routes = Some(routes);
self
}
pub fn window_events(mut self, evts: WindowEvents<Ms, Mdl>) -> Self {
self.window_events = Some(evts);
self
}
pub fn sink(mut self, sink: SinkFn<Ms, Mdl, ElC, GMs>) -> Self {
self.sink = Some(sink);
self
}
#[deprecated(since = "0.4.2", note = "Please use `.build_and_start` instead")]
pub fn finish(mut self) -> App<Ms, Mdl, ElC, GMs> {
if self.mount_point.is_none() {
self = self.mount("app")
}
let app = App::new(
self.update,
self.sink,
self.view,
self.mount_point.unwrap(),
self.routes,
self.window_events,
);
let mut initial_orders = OrdersContainer::new(app.clone());
let mut init = (self.init)(routing::initial_url(), &mut initial_orders);
match init.url_handling {
UrlHandling::PassToRoutes => {
let url = routing::initial_url();
if let Some(r) = self.routes {
if let Some(u) = r(url) {
(self.update)(u, &mut init.model, &mut initial_orders);
}
}
}
UrlHandling::None => (),
};
app.cfg.initial_orders.replace(Some(initial_orders));
app.cfg.mount_type.replace(Some(init.mount_type));
app.data.model.replace(Some(init.model));
app
}
pub fn build_and_start(self) -> App<Ms, Mdl, ElC, GMs> {
let app = self.finish();
app.run()
}
}