Giter Club home page Giter Club logo

rust-dominator's Introduction

crates.io docs.rs

Zero-cost ultra-high-performance declarative DOM library using FRP signals for Rust!

Overview

Dominator is one of the fastest DOM frameworks in the world (it is just as fast as Inferno).

It does not use VDOM, instead it uses raw DOM nodes for maximum performance. It is close to the metal and has almost no overhead: everything is inlined to raw DOM operations.

It scales incredibly well even with very large applications, because updates are always O(1) time, no matter how big or deeply nested your application is.

It has a convenient high level declarative API which works similar to React components, but is designed for Rust and FRP signals.

It is generally feature complete, though more convenience methods might be added over time.

It is quite stable: breaking changes are very rare, and are handled with the normal semver system.

I have successfully used Dominator on multiple large applications, and it performed excellently.

Running the examples

Just do yarn and then yarn start (it will take a while to compile the dependencies, please be patient)

Community

We have a Discord server. Feel free to ask any Dominator questions there.

rust-dominator's People

Contributors

alexisfontaine avatar billy-sheppard avatar implode-nz avatar jomerdev avatar kinrany avatar martin-kolarik avatar mdm23 avatar mendyberger avatar pauan avatar qm3ster avatar tqwewe avatar tristancacqueray avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rust-dominator's Issues

Examples don't work

$ rustc --version
rustc 1.34.0-nightly (097c04cf4 2019-02-24)
$ cargo --version
cargo 1.34.0-nightly (5c6aa46e6 2019-02-22)
$ git clone https://github.com/Pauan/rust-dominator.git
Cloning into 'rust-dominator'...
remote: Enumerating objects: 40, done.
remote: Counting objects: 100% (40/40), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 804 (delta 13), reused 29 (delta 10), pack-reused 764
Receiving objects: 100% (804/804), 421.21 KiB | 1.06 MiB/s, done.
Resolving deltas: 100% (435/435), done.
$ cd rust-dominator/examples/counter/
$ cargo web start --release
Compiling proc-macro2 v0.4.24
Compiling unicode-xid v0.1.0
Compiling either v1.5.0
Compiling semver-parser v0.7.0
Compiling libc v0.2.46
Compiling ryu v0.2.7
Compiling rand_core v0.3.0
Compiling serde v1.0.84
Compiling iovec v0.1.2
Compiling pin-utils v0.1.0-alpha.4
Compiling slab v0.4.1
Compiling stdweb-internal-runtime v0.1.3
Compiling itoa v0.4.3
Compiling lazy_static v1.2.0
Compiling sha1 v0.6.0
Compiling base-x v0.2.4
Compiling discard v1.0.4
Compiling futures-core-preview v0.3.0-alpha.11
Compiling rand_core v0.2.2
Compiling semver v0.9.0
error[E0432]: unresolved import core::task::LocalWaker
--> /home/kirill/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-core-preview-0.3.0-alpha.11/src/future/future_obj.rs:6:12
|
6 | task::{LocalWaker, Poll},
| ^^^^^^^^^^ no LocalWaker in task
error[E0432]: unresolved imports core::task::LocalWaker, core::task::UnsafeWake
--> /home/kirill/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-core-preview-0.3.0-alpha.11/src/task/mod.rs:8:35
|
8 | pub use core::task::{Poll, Waker, LocalWaker, UnsafeWake};
| ^^^^^^^^^^ ^^^^^^^^^^ no UnsafeWake in task
| |
| no LocalWaker in task

error[E0432]: unresolved imports std::task::Wake, std::task::local_waker, std::task::local_waker_from_nonlocal
--> /home/kirill/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-core-preview-0.3.0-alpha.11/src/task/mod.rs:10:21
|
10 | pub use std::task::{Wake, local_waker, local_waker_from_nonlocal};
| ^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ no local_waker_from_nonlocal in task
| | |
| | no local_waker in task
| no Wake in task. Did you mean to use Waker?

Compiling rand v0.5.5
Compiling num_cpus v1.9.0
error: aborting due to 3 previous errors
For more information about this error, try rustc --explain E0432.
error: Could not compile futures-core-preview.
warning: build failed, waiting for other jobs to finish...
error: build failed
error: build failed

Idiomatically implementing controlled components

See yewstack/yew#233 (comment) for context.

Essentially, the desired behavior is: if an edit for a field (e.g. an input or text area) is 'rejected' (i.e. not applied to the model), the render should not update. In react this is known as 'controlled components'.

In dominator you can test this by applying the patch I've pasted at the bottom and then editing the 'new item' input element on the todomvc example. Because the internal state isn't changing, arguably neither should the render. Unfortunately, it does (due to default behaviour of the element).

React solves this by restoring state after a change event (described on the linked issue). Note that react also provides uncontrolled components where the value field is not set.

It is relatively easy to work around this in dominator (as opposed to diffing frameworks, where it's a little trickier) - just make sure to always do .set (i.e. not set_neq, which the current todomvc does) for any elements with relevant input events (I believe textarea, input, select as those are the ones react implements restoreControlledState for). This mimics the react behaviour of always resetting the controlled state of the element on an input event.

However, this seems subideal since you have to remember to do this for every element. I propose it should be straightforward to decide whether a component is controlled or uncontrolled - with react you decide by whether the value field is assigned. I also have a personal preference for controlled components but that's more subjective.

It is a valid response to say that dominator is a lower level library that shouldn't think about the semantics of different elements, but this does seem like a footgun when you end up wanting to control field inputs.

diff --git a/examples/todomvc/src/main.rs b/examples/todomvc/src/main.rs
index 3919949..3a5d84b 100644
--- a/examples/todomvc/src/main.rs
+++ b/examples/todomvc/src/main.rs
@@ -161,7 +161,7 @@ fn main() {
                             .property_signal("value", state.new_todo_title.signal_cloned())
 
                             .event(clone!(state => move |event: InputEvent| {
-                                state.new_todo_title.set_neq(get_value(&event));
+                                //state.new_todo_title.set_neq(get_value(&event));
                             }))
 
                             .event(clone!(state => move |event: KeyDownEvent| {

Change dependencies to minor version

Should Dominator not depend only on minor version of crates instead of patch version?

e.g.

wasm-bindgen = "^0.2"

instead of:

wasm-bindgen = "0.2.48"

?

That way application code won't need to duplicate the dependency (unless I misunderstand Rust's package management system...)

Render Dom-tree to string (SSR)

I was wondering how one could render a Dom-tree to an HTML string for server-side-rendering (SSR).

Conceptually I see three options:

  1. Render the application in a headless browser with wasm-bindgen-test. This should be quite easy to set up, but is slow and brittle. (example code)
  2. Adding an intermediary runtime layer in rust-dominator, which wraps dom nodes and operations on dom nodes. So Dom would not store a web_sys::Node but some internal struct, which could either contain a web_sys-node or some internal node representation.
  3. Allow to swap out web_sys at compile-time with a virtual dom library that has a compatible API to web_sys, and provides all the functionality needed by rust-dominator.

What do you think about this topic in general? Are there other ways?

Fix examples in Edge

According to the docs Edge requires that some polyfill for TextDecoder be installed.

I can confirm that it currently fails...

Or should Edge just not be supported? I suppose eventually everyone on old Edge will move on to Chromium-Edge...

`const_generics` feature has been replaced

The const_generics feature was removed and replaced with a number of other features. As a result, dominator can no longer compile with the nightly feature activated. The appropriate replacement is adt_const_params.

Cross-platform Dominator programs

As cross-platform GUI solutions in Rust are being sought, how could that benefit Dominator in taking it beyond the web with minimal, if any, additional development?

Event Queue

What's the recommendation for storing events in a queue to be processed later?

Box<dyn StaticEvent> is not allowed since StaticEvent can't be made into a Trait Object

Question: Tauri integration

Is there any non-trivial example that showcases how to share code b/w the backend and frontend when combining Dominator and Tauri?

Evaluate and Implement HTML Macros

I want to evaluate these HTML macro systems and any others. @Pauan and others, let me know if you have thoughts on a good html macro system for rust to enable JSX style in Dominator, thanks.

typed-html is a wonderful library. Unfortunately, it focused its power in strictness of the HTML spec itself, and doesn't allow arbitrary compositions of custom elements.

render takes a different approach. For now, HTML is not typed at all. It can get any key and get any string value. The main focus is custom components, so you can create a composable and declarative template with no runtime errors.

Offset_x method for MouseEvent

I am tring to create a simple paint example with Zoon(rust wasm frontend). And in my example, i want to get canvas coordinate. I tried screen_x(which it was wrong), mouse_x, x methods but all of them was not fit my use case. And i found an example with pure rust wasm example here. In this example, it uses offset_x and offset_y for drawing a line with mouse moves but i could not find that method on events::MouseDown

Spring animations

I added Spring animations to dominator. It is based on they the current react native implementation. For very basic use case it works, see example but it is not well integrated yet with dominator animation, because your current animation code is only targeting timing animations such as easing (defined by a duration). Spring animations are defined by other parameters such as stiffness and damping.

So I have been thinking to generalize the data structures and as a first step replace in your code duration: f64 with an enum which can contain all the parameter of the chosen type of animation:

enum AnimationEnum {
    Timing(f64),  // duration
    Spring(SpringConfig),
   // Decay(f64),  // initial velocity (not implemented yet)
}

pub struct SpringConfig {
    stiffness: f64,
    damping: f64,
    mass: f64,
    initial_velocity: f64,
    overshoot_clamping: bool,
    rest_displacement_threshold: f64,
    rest_speed_threshold: f64,
}

What do think ? Is this something you would be interested in to accept a PR ?

Btw. one observation I made when working with dominator:
If start a new rust project and then just add dominator dependency, it fails to build. Dominator is depending on signals 0.3.0. But only rust-signals latest version 0.3.2 is supposed to work with Rust nightly (I guess). Unfortunately, when I just delete the cargo lock files (dominator and/or dominator examples) and update to latest dependencies, then rebuild fails.

So for a new project with dominator dependency I just copy a cargo lockfile from a dominator example project and all works fine. But I guess that is not the right way to handle it!

I am using rustc 1.33.0-nightly (8e2063d02 2019-01-07)

Feat: dynamic_class_signal

As discussed on Discord, sometimes it's needed to add/remove a class with a dynamic name

@Pauan suggested a fn dynamic_class_signal<S>(self, signal: S) -> Self where S: Signal<Item = Option<String>>

which makes something like this much more ergonomic:

.with_node!(element => {
    .future({
        let mut old = None;

        signal.for_each(move |value| {
            if let Some(old) = old.as_deref() {
                element.class_list().remove_1(old).unwrap();
            }

            let name = ...;
            element.class_list().add_1(name).unwrap();
            old = Some(name);
            ready(())
        })
    })
})

wasm-bindgen does not have `enable-intern` features. Examples fail to run

This looks great, but I ran into an issue when I tried to run the examples.

rust-dominator|master โ‡’ cargo +nightly build
    Updating crates.io index
error: failed to select a version for `wasm-bindgen`.
    ... required by package `dominator v0.5.0 (/Users/marco/code/rust-dominator)`
versions that meet the requirements `^0.2.45` are: 0.2.48, 0.2.47, 0.2.46, 0.2.45

the package `dominator` depends on `wasm-bindgen`, with features: `enable-interning` but `wasm-bindgen` does not have these features.


failed to select a version for `wasm-bindgen` which could resolve this conflict

Probably because you just merged the enable-interning features in wasm-bindgen, and that hasn't been pushed to crates.io yet (?).

How are you running these?
Thanks!

Multi-Consumer Signals

While I was playing around with creating a store struct for state I noticed that only one signal reciever gets the update (I hadn't really grokked this before). E.g.

let (sender_text, receiver_text) = signal::unsync::mutable(String::from("Hello  "));

html!("div", {
    children(&mut [
        text("First: "),
        text(receiver_text.clone().dynamic()),
        text(", Second: "),
        text(receiver_text.clone().dynamic()),
        html!("button", {
            children(&mut [text("Click Me!")]);
            event(move |_: ClickEvent| { sender_text.set(String::from("World")); });
        })
    ]);
})

The rendered Dom populates the text after First: with "Hello", but then when the button is clicked it's the second text that recieves "World". Ouch ๐Ÿ˜จ

I guess that either:

  • This is an argument that receivers should not impl Clone.
  • I'm doing bad stuff that users shouldn't do.
  • Signals should be able to broadcast to multiple receivers.

I have a concept on how I would build that last one, would it be interesting if I made a PR with a signal::mpmc module to discuss?

Feat: .attrs!

@Pauan proposed the following:

.attrs!{ foo="bar", qux="corge" }

which would essentially be the same as

.attr("foo", "bar")
.attr("qux", "corge")

This simple macro would actually go a very long way to make dominator code more palatable to newcomers especially imho.

Some questions

While doing research for adding spring animations to a seed-based app, I discovered this project. I am totally blown away by the signal-based concept and all the advanced and highly performant stuff packed into this framework. Especially the animations example! Here some questions which piled up, while digging into rust-dominator and rust-signals:

If I understand properly, rust-signals is conceptually like Erlang message passing. Or Actor frameworks like Rust actix, but more lightweight and specifically optimized for your primary use case rust-dominator. Please correct me if I am wrong.

I read the reusable component issue, where you mentioned queues. If I understand properly, queues are a way to make the whole thing look like an Elm (or seed) approach where all state updates are done in an update function, so the view contains less state update logic. @Pauan Are you still planning to add this to rust-dominator? Are there disadvantages for such a Queue approach like elimination of the rust-signal performance gain over Elm-like approaches ? Could push-to-queue and direct-signal-to-model (as we have it now) both be used together ? I imagine for things things like animations, which have their own internal state, one does not want to propagate things up to the the message queue.

One more specific question about dom.StyleSheetBuilder. Where is that getting used ? In the examples, style changes are directly applied to dom HTML elements (if I understood properly). But the code from the StyleSheetBuilder suggests to me that this is for collecting styles from the element definitions in the view and building a stylesheet out of it, for injection into the HTML page.

What is the roadmap for rust-dominator ? It is based on stdweb, but since wasm-bindgen became usable, it seems to me that all new stuff in wasm space is based on wasm-bindgen. Is there any technical reason preventing rust-dominator from being based on wasm-bindgen ?

Other Events

Currently rust-dominator has a great event mechanism but only few usable events.

The make_event macro is not exported. I tried manually expanding the macro for an event, but I'm neither getting an error message nor registering events, when I set up my events: .global_event(|_event: LoadEnd| { log!("LoadEndEvent registered") }).global_event(|_event: Load| { log!("LoadEvent registered") })

The Resize event, that you defined works: .global_event(|_event: Resize| { log!("ResizeEvent registered") })

Here's how I tried to expand them manually:

pub struct Load {
    event: web_sys::ProgressEvent
}

impl StaticEvent for Load {
    const EVENT_TYPE: &'static str = "load";

    #[inline]
    fn unchecked_from_event(event: web_sys::Event) -> Self {
        Self {
            event: event.unchecked_into(),
        }
    }
}

impl Load {
    #[inline]
    pub fn prevent_default(&self) { self.event.prevent_default(); }

    #[inline]
    pub fn target(&self) -> Option<EventTarget> { self.event.target() }

    #[inline]
    pub fn dyn_target<A>(&self) -> Option<A> where A: JsCast {
        self.target()?.dyn_into().ok()
    }
}

pub struct LoadEnd {
    event: web_sys::ProgressEvent
}

impl StaticEvent for LoadEnd {
    const EVENT_TYPE: &'static str = "loadend";

    #[inline]
    fn unchecked_from_event(event: web_sys::Event) -> Self {
        Self {
            event: event.unchecked_into(),
        }
    }
}

impl LoadEnd {
    #[inline]
    pub fn prevent_default(&self) { self.event.prevent_default(); }

    #[inline]
    pub fn target(&self) -> Option<EventTarget> { self.event.target() }

    #[inline]
    pub fn dyn_target<A>(&self) -> Option<A> where A: JsCast {
        self.target()?.dyn_into().ok()
    }
}

Astral plane characters in user input can get mangled on Windows

On Windows, character input was traditionally done via UTF-16 code units. The result of operating this way is that any code point that requires a surrogate pair in UTF-16 will come as two user input events instead of one. Thereโ€™s a newer technique that lets you take an entire code point at once for applications that support it, but itโ€™s a mixed bunch whether IMEs that are sending the input in the first place will operate in that way. I have two ways of entering emoji, for example: Windows 10โ€™s now-built-in emoji picker (Win+.), and my compose key, using WinCompose. The emoji picker is capable of sending both at once, while WinCompose sends the surrogates individually. (Iโ€™m not certain about other platforms and their IME techniques; I think theyโ€™re probably all safe from this gotcha.)

The result is this: you cannot trust the value of an <input> to be a legal UTF-16 string at any given point; it should (should) eventually be a legal Unicode string, but in the mean time it is just like any DOMString, merely a sequence of legal UTF-16 code units.

This is the effect of this on the todomvc example: when I type ๐Ÿ˜• into the #new-todo input via WinCompose, it is actually input as two two input events, one with the high surrogate 0xD83D, and then one with the low surrogate 0xDE15, which together make U+1F615. (With the emoji picker it comes through in one event, so the bug does not occur.)

The first one triggers an input event on the <input> element. The code fetches the value, finds it to be "\ud83d" (in JavaScript terms), then tries to turn it into a UTF-8 string for Rust, and encountering an unmatched surrogate replaces it with the replacement character, ๏ฟฝ. Then, because the binding is two-way, it writes ๏ฟฝ back to the text input.

Then the second logical keystroke is processed, and the low surrogate appended to the ๏ฟฝ, and then through Rust again, and so "\ufffd\ude15" becomes "\ufffd\ufffd".

End result: ๐Ÿ˜• became ๏ฟฝ๏ฟฝ because of the combination of two-way bindings and the use of UTF-8 instead of WTF-8.

This particular case can be resolved by killing off the altogether unnecessary and inefficient two-way binding of the value (just read and reset the value at submission time, you have a handle to the DOM node), and leaving the browser to sort it out, but itโ€™s indicative of a broader class of bug that will generally affect few people (not many people use a compose key on Windows), but could be catastrophic for e.g. Chinese users, depending on the IME theyโ€™re using.

I think this is the first time Iโ€™ve ever come across a thing on the web that didnโ€™t cope with transient unmatched surrogatesโ€”itโ€™s not something thatโ€™s ever likely to trip you up in JavaScript, but itโ€™s a problem for wasm stuff.

generate web components

Sortof the inverse of #23

Would be great if dominator could be used to create web components. On one very rough foot, I think the only thing missing really is css encapsulation and rendering as shadow dom?

Haven't looked into it too much... just an idea :)

Reusable components

Maybe it's too early to start talking this; I have been wondering about it a bit so thought I'd ask:

Do you have any feeling for how you would want to componentise dominator (if you would want to componentise ๐Ÿค”)? I guess Surplus.js is prior art on how something like that could work.

At the moment I can see functions like

fn number_in_div<N: Signal<i32>, OnClick: Fn()> (number: N, callback: OnClick) -> Dom {
   // ... use html! to make a div displaying a number which changes with N
}

being an opportunity for re-use, and maybe that's fine. I'm just used to the React / Vue way of thinking where things are a bit more prescribed. E.g. some kind of trait or other way to express "this is a component" might be cool:

struct NumberDiv<N: Signal, OnClick: Fn()> {
   number: N,
   callback: Fn()
}

impl Component for NumberDiv { /* No idea what would be here, if anything */ }

// Maybe could fit in html! like so:
html!(NumberDiv { number: N, callback: ... }, {
  class(&some_class, true); // apply a class to the Dom of this specific NumberDiv, is that desirable?
});

Thanks for hearing out my rambling ๐Ÿ˜„

Signal of HTML elements

I'm currently using

.children_signal_vec(signal_of_elements.map(|x| vec![x]).to_signal_vec())

Is there a more efficient way to show a whole tag that comes out of a signal?

Improving for new and/or average users.

I am still not sure if I use this library or mine. But I think it worth raising this issue. How do you think about these?

  1. Statically checking if a child (directly) is allowed in an element, an attribute is available for an element.
  2. Add the Component as discuss in #12, and have an example demonstrating it.
  3. Less mysterious macro, especially the clone! (at least document it).
  4. Able to build directly with wasm-bindgen-cli or wasm-pack?

Personally, the first thing I do is looking at the examples. And my impression is: "Oh, what the hell is all this?" - totally new to signals; able to figure out about html!, but what does clone! do? when to use it? Looking for the document - no explanation for it. Look at the definition code? Uh oh, I am not good at it. I guess if users not desperately need this, they will move along.

(1) requires much more work, but give less obvious impact, but benefit in long term. Users may get frustrated if they accidentally assign an invalid attribute to an element. Because we are in Rust, we can help them avoid such situation. Especially for users who don't like to use the macro, IDEs can help showing available methods for an element.

Feat: with_element method on Dom

Problem

I have a function that receives a Signal<Item = Option<Dom>> and wants to render it as a child, yet it needs to sprinkle in some additional attributes first (for example a slot attribute)

Since there's currently no way to do that, it means creating a wrapper, and then that can introduce awkward CSS constraints that need workarounds... for example:

.child(html!("div", {
    .style("width", "100%")
    .style("height", "100%")
    .attribute("slot", "sidebar")
    .child_signal(sidebar_signal())
}))

Solution

If Dom had a with_element() which accepts a FnOnce(&web_sys::Element), then it would be possible to completely remove the nesting:

.child_signal(sidebar_signal()
  .map(|dom| {
    if let Some(dom) = dom {
      dom.with_element(|el| {
        el.attribute("slot", "sidebar")
      })
    }
  })
)

Although probably outside the scope of Dominator itself, this would then allow for easy extensions to add children to slots.

Add `show_picker()` functionality to `focused_signal()` Upon wasm-bindgen/issues/3036 being implemented.

Focused signal works great for text inputs but for picker style inputs it doesn't work.

Unsure exactly how to implement it though, also heavily dependant on how/if/when the wasm-bindgen guys get the following issue working.

rustwasm/wasm-bindgen#3036

pub(crate) fn show_picker(elem: &HtmlElement) {
    elem.show_picker().unwrap_throw();
}
fn set_focused_signal<B>(&mut self, value: B)
        where B: Signal<Item = bool> + 'static {

        let element = self.element.as_ref().clone();

        // This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
        self.callbacks.after_insert(move |callbacks| {
            // TODO verify that this is correct under all circumstances
            callbacks.after_remove(for_each(value, move |value| {
                // TODO avoid updating if the focused state hasn't changed ?
                if value {
                    bindings::focus(&element);
                }
                else if value { // && [ "date", "datetime-local", "month", "time", "week", "color", "file"].contains(input_type) ??
                    bindings::show_picker(&element); // COULD ADD HERE?
                } else {
                    bindings::blur(&element);
                    // NOT SURE HOW TO HANDLE BLUR-ING
                }
            }));
        });
    }

Should unsync::Sender impl Clone?

I just had a play around with this library tonight, I'm really enjoying it!

I came across one small pain point: I think it would be helpful if unsync::Sender also implemented Clone (like reciever). Though I'm guessing you might have omitted this on purpose?

BTW, let me know if there's anything I can do to help out. Otherwise I'll continue to play with the library and post back here with any thoughts ๐Ÿ˜ธ

Macros to make working with existing elements and web components easier

Consider the following... there's these CustomElements that are pre-registered and created via something like lit-element:

  • sticker-book
  • sticker-menu
  • sticker-image
  • sticker-frame

Some of them are setup to have meaningful properties that should be set from the outside (for example, sticker-frame might play an animation and let its step be driven from an external clock)

dominator wants to mount sticker-book to the top-level body. So far so good. But the problem is that the other elements are actually inside of sticker-book's shadow dom. It may look like this:

<div>
  <sticker-menu id="menu" activeSticker="2" />
  <ul id="list">
    <li><sticker-image id="1" /></li>
    <li><sticker-image id="2" active="true" /></li>
  </ul>
</div>

And lets say sticker-image is setup to react on its active property change such that it will render either:

<sticker-frame step={step}>
  <img src="..." />
</sticker-frame>
  

or

null

So dominator should be able to:

  1. Append/Remove sticker-image items in the list (at id="list")
  2. Set properties of these nested elements
  3. Set properties on those nested elements' nested elements - but after mounting

In other words, through dominator, with the above setup it should be possible to easily drive the final step property on sticker-frame. However, if the top level active sticker is changed, then it must switch to set the step property on the new sticker-frame

@Pauan noted that it is currently possible, but may require some ugly workarounds, and that macros will make this much more elegant. Also that since it fundamentally boils down to working with pre-existing nodes, it will help with server-side rendering as well.

Add routing::replace_url

Would it be acceptable to add a replace_url helper that uses history.replace instead of history.push, so that the current page is removed from the history?

single string multiple class names

Can this be supported?

e.g. using TailwindCSS, this:

.class("min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8")

instead of this:

.class("min-h-screen")
.class("flex")
.class("items-center")
.class("justify-center")
.class("bg-gray-50")
.class("py-12")
.class("px-4")
.class("sm:px-6")
.class("lg:px-8")

How to define CSS pseudo classes with ClassBuilder

I am trying to implement a Design System and CSS framework partially based on TailwindCSS with the dominator class! (and stylesheet! if necessary) macros.

Main motivation is to have typed class names and only those CSS classes which are actually used, will end up in the compiled binary (and eventually in the CSSRuleList).

However i got stuck at the following issue: I see no way how I could define pseudo classes with the current ClassBuilder and its class! macro. I would like to do something like:

lazy_static! {
    static ref FOO_CLASS: String = class! {
        .style("background-color", "green")
        .style_hover("background-color", "yellow")
    };
    static ref FOO_CLASS_HOVER: String = class! {
        .base(&*FOO_CLASS)  // not implemented yet. Does it make sense ???
        .pseudo("hover")  // not implemented yet. 
        .style_hover("background-color", "yellow") 
    };
}

That should generate this:

<style>
.__class_0__ {
    background-color: green;
}
.__class_0__:hover {
    background-color: yellow;
}
</style>

Is is currently possible to do something like that ?

Does the code above to implement pseudo classes make sense ? One thing I am not sure about: For the pseudo classes we need the base class name, but when the base class name gets first time accessed (inside the macro for the pseudo class), then maybe the compiler can not optimize it away anymore, in case the base class never gets used in the actual app code.

I am aware that I could build pseudo classes with StylesheetBuilder and its macro, but then I won't have typed class names anymore, which was my main motivation to start with all this.

How do I get random access Signals from SignalVecs?

I have a graph structure, described by edges and nodes, and a graph layout -- a Vec of node-positions. Say the graph structure won't change, but laying out the graph is an iterative process that updates the node positions 1000 to 10000 times depending on the size and complexity of the graph.

Using Dominator, I thought I'd needed to replace the Vec with a MutableVec to be able to create the SVG-Elements once, and update the their transform attribute by using a signal.
I must be doing something wrong though, because for

.attribute_signal("transform", translations.signal_vec().map(|t|format!("translate({},{})", t.x(), t.y())))

I'm getting the error

the trait bound `futures_signals::signal_vec::Map<futures_signals::signal_vec::mutable_vec::MutableSignalVec<Translation>, [closure@src\lib.rs:225:55: 225:99]>: futures_signals::signal::signal::Signal` is not satisfied

despite Map implementing SignalVec.

I have no idea how to draw the edges. In a static context I'd iterate through the edges and read the positions of the start- and end-nodes to draw each edge, like

let a_center = translations[edge.a_end()];
let b_center = translations[edge.b_end()];
new_svg("line")
                    .class("edge")
                    .attribute("node-1", &edge.a_end().to_string())
                    .attribute("x1", &a_center.x().to_string())
                    .attribute("y1", &a_center.y().to_string())
                    .attribute("node-2", &edge.b_end().to_string())
                    .attribute("x2", &b_center.x().to_string())
                    .attribute("y2", &b_center.y().to_string())
                    .into_dom()

How can I get a the signal-equivalent of translations[edge.a_end()], when translations is a MutableSignalVec (where edge.a_end() is not a signal)?

How to use child_signal

Hi,

I'm trying to use child_signal() but struggle to create a Signal<Item = Option> as required by the trait bound. I assume, I've to use signal_ref() as Dom does neither implement Copy nor Clone, so signal() and signal_cloned do not work. Correct?

This is what I have put together so far. I guess, I'm missing something obvious or misunderstand the approach completely. A example code showing how this is meant to be used would be great.

let dom = Mutable::new(Some(Dom::empty()));
let signal = dom.signal_ref(|d| d );

let header: DomBuilder<web_sys::HtmlElement> = DomBuilder::new_html("header");
header.child_signal(signal);

This is the compiler error:

error[E0271]: type mismatch resolving `for<'r> <[closure@webapp/src/lib.rs:35:33: 35:38] as FnOnce<(&'r Option<Dom>,)>>::Output == Option<Dom>`
  --> webapp/src/lib.rs:38:12
   |
38 |     header.child_signal(signal);
   |            ^^^^^^^^^^^^ expected `&Option<Dom>`, found enum `Option`
   |
   = note: expected reference `&Option<Dom>`
                   found enum `Option<Dom>`
   = note: required because of the requirements on the impl of `Signal` for `MutableSignalRef<Option<Dom>, [closure@webapp/src/lib.rs:35:33: 35:38]>`

Any help appreciated. Thanks.

feature request: shadow root stylesheets

this was discussed on Discord and @Pauan suggested something like the following syntax:

static STYLE1: Lazy<Stylesheet> = Lazy::new(|| stylesheet!("div.bar", {
    .style(...)
}));

html!("div", {
    .shadow_root!(ShadowRootMode::Open, {
        .stylesheet(&*STYLE1)
        .children(&mut [ ... ])
    })
})

The Stylesheet here should somehow use Constructable Stylesheet Objects - such that if the above html! were called 100 times to add instances of this component to a page, they should all share the same static stylesheet.

Even though the stylesheet is static, its inner contents can be changed and so the above will support .style_signal()

It's likely that these stylesheets, scoped to a shadow root, will often define many rules at once - so it would be more efficient and convenient to have a wrapper that collects them all.

This wrapper could be called stylesheets!, or perhaps, stylesheet! becomes the wrapper and each rule uses a rule! macro. If going with the latter, the above could look like:

static STYLE1: Lazy<Stylesheet> = Lazy::new(|| stylesheet!(&[
    rule!("div.bar", {
      .style(...)
    }),
    rule!("ul > li + li", {
      .style_signal(...)
    })
]));

html!("div", {
    .shadow_root!(ShadowRootMode::Open, {
        .stylesheet(&*STYLE1)
        .children(&mut [ ... ])
    })
})

Better readme, and Rust discussion

Hi,

I'm interested in FRP and Rust so this looks up my alley. But, the readme isn't giving me much. What kind of FRP? Do you have continuous-time? Higher order? Cycles? Purity?

Continuing
krausest/js-framework-benchmark#589 (comment)

Yeah, I use TypeScript at work. It's... better than JS, but not very good. Too many quirks, unsound type system, lack of useful features (typeclasses, nice ADTs, etc.), poor syntax (inherited from JS).

I used to use PureScript, then I switched to Rust because PureScript is indeed very slow (understatement), with no intention of improving.

You don't address Reason here. Have you tried it out? Good type system, pretty good performance. Compiler is much faster than Rust's. Good middle ground.

Also, all the stuff about doing performance-critical parts in Rust: I agree. : )

About the benchmark

Hmm... sorry for opening this issue here. It's not about Dominator, just bring up by Dominator.

I saw Dominator's benchmark on Rust+Wasm news.

wasm-bindgen slower in some benchmarks

There are some benchmarks where Dominator v0.5 slower than v0.4.4:

... v0.5 v0.4.4 v0.5 slowdown
partial update 544 418 >20%
select row 82.4 76.9 >5%
swap 108 90.6 ~20%
remove row 56.9 51.8 ~10%

What do you think about these? Are there problems with wasm-bindgen?

Creating 10,000 vs 1,000 rows:

... vanillajs inferno wasm bindgen preact dominator react stdweb yew
1,000 rows (1) 110 130 200 155 201 171 228 314
10,000 rows (2) 1177 1373 2024 1864 2630 1981 2378 3237
ratio = (2)/(1) ~10.5 ~10.5 ~10 ~12 ~13 ~11 ~10.5 ~10.3

The ratio of Dominator is not good, but the ratio of Yew is perfectly normal?

Getting rid of inlined JS

Hello,

I'm looking at getting rid of the inlined JS because I need to have the minimum file possible for a project.

Here is the relevant part of dominator's code:

#[wasm_bindgen(inline_js = "
    export function set_property(obj, name, value) { obj[name] = value; }

    export function add_event(elem, name, capture, passive, f) {
        elem.addEventListener(name, f, {
            capture,
            passive,
            once: false,
        });
    }

    export function add_event_once(elem, name, f) {
        elem.addEventListener(name, f, {
            capture: true,
            passive: true,
            once: true,
        });
    }

    export function remove_event(elem, name, capture, f) {
        elem.removeEventListener(name, f, capture);
    }
")]
extern "C" {
    // TODO move this into wasm-bindgen or gloo or something
    // TODO maybe use Object for obj ?
    pub(crate) fn set_property(obj: &JsValue, name: &str, value: &JsValue);

    // TODO replace with gloo-events
    pub(crate) fn add_event(elem: &EventTarget, name: &str, capture: bool, passive: bool, f: &Function);
    pub(crate) fn add_event_once(elem: &EventTarget, name: &str, f: &Function);
    pub(crate) fn remove_event(elem: &EventTarget, name: &str, capture: bool, f: &Function);
}

This is what I came up with to get rid of set_property for now:

use wasm_bindgen::prelude::*;

#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(message: &str);
    #[wasm_bindgen(js_namespace = Object, js_name = defineProperty)]
    fn define_property(obj: &JsValue, prop: &str, descriptor: &JsValue);
    #[wasm_bindgen(js_namespace = Object, js_name = fromEntries)]
    fn from_entries(iterable: &JsValue) -> JsValue;
    #[wasm_bindgen(js_namespace = Object, js_name = assign)]
    fn assign(target: &JsValue, source: &JsValue);
    #[wasm_bindgen(js_namespace = Array, js_name = of)]
    fn array_of(of: &JsValue) -> JsValue;
    #[wasm_bindgen(js_namespace = Array, js_name = of)]
    fn array_of_2(of_1: &JsValue, of_2: &JsValue) -> JsValue;
    #[wasm_bindgen(js_namespace = Array, js_name = of)]
    fn array_prop_key_value(key: &str, value: &JsValue) -> JsValue;
}

#[wasm_bindgen(start)]
pub fn run_app() -> Result<(), JsValue> {
    #[cfg(feature = "console_error_panic_hook")]
    console_error_panic_hook::set_once();

    // Solution 1: using Object.defineProperty and Object.fromEntries
    let descriptor = js_sys::Array::new();
    descriptor.push(&js_sys::Array::from_iter([JsValue::from("value"), JsValue::from(42_i32)]));
    descriptor.push(&js_sys::Array::from_iter([JsValue::from("writable"), JsValue::from(true)]));
    descriptor.push(&js_sys::Array::from_iter([JsValue::from("configurable"), JsValue::from(true)]));
    descriptor.push(&js_sys::Array::from_iter([JsValue::from("enumerable"), JsValue::from(true)]));
    let descriptor = from_entries(&descriptor);
    let obj = js_sys::Object::new();
    define_property(&obj, "foo", &descriptor);
    log(&format!("it works: {:?}", obj));

    // Solution 2: using Object.fromEntries and js_sys::Array::from_iter
    let obj = js_sys::Object::new();
    assign(&obj, &from_entries(&js_sys::Array::from_iter([&js_sys::Array::from_iter([JsValue::from("bar"), JsValue::from(42_i32)])])));
    log(&format!("it works: {:?}", obj));

    // Solution 3: using Object.fromEntries and Array.of and js_sys::Array::from_iter
    let obj = js_sys::Object::new();
    assign(&obj, &from_entries(&array_of(&js_sys::Array::from_iter([JsValue::from("baz"), JsValue::from(42_i32)]))));
    log(&format!("it works: {:?}", obj));

    // Solution 4: using Object.fromEntries and Array.of
    let obj = js_sys::Object::new();
    assign(&obj, &from_entries(&array_of(&array_of_2(&JsValue::from("foobar"), &JsValue::from(42_i32)))));
    log(&format!("it works: {:?}", obj));

    // Solution 5: using Object.fromEntries and Array.of and a special Array.of that takes &str in argument
    let obj = js_sys::Object::new();
    assign(&obj, &from_entries(&array_of(&array_prop_key_value(wasm_bindgen::intern("foobaz"), &JsValue::from(42_i32)))));
    log(&format!("it works: {:?}", obj));

    Ok(())
}

Do you think this could be a good way to go or do you have another idea in mind?

Solution 5 looks the more efficient but I have not done benchmarks yet.

import and encapsulate CSS

This has come up in a couple other issues, like #35, but I think it deserves its kindof its own issue too...

Is there a way to import .css files?

How about importing them so that they are encapsulated within a particular component/tree? (shadowDOM or not)?

How about doing that, but also being able to import common files (like "theme-dark.css", "base.css", etc.) - without having it add bloat at each additional import?

Routing

Sorry I have so many questions! (Can you tell I'm excited to see where dominator goes ๐Ÿ˜„)

I was playing around with the concept of routing using a signal containing an enum. I was hoping that I could map that signal to a Signal<Item=Vec<Dom>>, which would essentially swap out the page contents on navigation. I put in some top-level app state too to make things hard for myself.

The idea was that this might pave the way for building myself a little Router struct or trait.

Sample of code for a simple todo app with a login route is below; I hit a couple of stumbling blocks and wondered how you would change what I've attempted to work around them:

  1. signal_vec::unsync::Reciever recieves self by value for Map. It's also not Clone. Thus connecting the signal_vec into Dom is one-shot only. I agree this is correct, but it gets in the way of what I want to do here where it needs to potentially be bound and dropped multiple times as the user navigates ๐Ÿ˜ข
  2. Dynamic<Signal<Item=Vec<Dom>> used to impl Children, but I that impl had to be killed as it conflicted with the signal_vec impl. Possibly not much can be done about that until specialisation comes to Rust?

enum Route {
  Login,
  Todos
};

fn todolist_app() -> Dom {

    let (sender_route, receiver_route) = signal::unsync::mutable(Route::Login);
    
    let (sender_todos, receiver_todos) = signal_vec::unsync::mutable::<String>();
    let sender_todos = Rc::new(RefCell::new(sender_todos));
    let receiver_todos = Rc::new(receiver_todos);

    html! {
        "div", {
            children(
              receiver_route.map(move |route| match route {
                    Route::Login => vec![
                      // this is just my stopgap view-only component with a callback while we figure out better
                      login_page(clone!(
                          {router, sender_todos}
                          move || {                             // callback if login is successful
                              router.navigate(Route::Todos);
                              sender_todos.borrow_mut().push("First Todo");
                          }
                      )) 
                    ],
    
                    Route::Todos => vec! [
                        html!("h1", { children(&mut [text("Notebook")]); }),
                        html!("div", {
                            children(receiver_todos.map(|todo| html!("p", { text(todo); })).dynamic());
                                     // (problem 1)
                        })  
                    ]
                })
            ]);
        }
    }
}

Set make_event to pub

Would allow external code to define CustomEvents more easily

The detail property would just come through as a JsValue and it's up to the user to deserialize

Currently, only StaticEvent types are supported.

Conditional rendering

I'm wondering what is a good way to render dom nodes conditionally, based on a signal.

For example to render different sub pages, based on the current "route" (document.location). In such a case only the current sub page should be added the document, and when the route changes, it should be replaced.

I can use a SignalVec, with each entry representing a different state, and then render only the current state with children_signal_vec(), but it feels like a workaround.

How about adding a function that renders a dom node based on a signal, and adds it as a child to a parent node (here's a draft: #18)?
Then it could be used something like this:

html("div")
    .child_signal(state.path.signal_cloned()
        .map({
            let state = Rc::clone(self);
            move |path| {
                match path.as_str() {
                    "/foo" => {
                        html("div").text("foo page!").into_dom()
                    }
                    "/bar" => {
                        html("div").text("bar page!").into_dom()
                    }
                    _ => {
                        html("div").text("error page!").into_dom()
                    }
                }
            }
        })
    )
    .into_dom()

Or maybe there are other, better ways to solve this?

Scene Graph

So this might be a bit early since I haven't actually tried working with Dominator yet, and I'm also not sure exactly how I'd want to build this out for practical usage, and it's not dom related... but...

On one foot, I'm wondering if I could have a tree of transforms where nodes listen to signals and upon change it causes specific properties of those nodes to cascade down.

So for example, changing a Mutable<Position> would cause that element's LocalTransform to change - and any LocalTransform change would cause that element and their children to update their WorldTransform's.

Along with the cascading changes, another gotcha is that some of them should be done in batches and a separate step. For example, perhaps the change from Position to LocalTransform is immediate, but updating all the WorldTransforms for dirty elements is once per tick.

It seems like Dominator is very related to this problem too since it's a tree and maybe does some batching too...?

Unable to compile todomvc

I've been able to successfully build and run the counter and the animation examples, but with todomv I'm getting the following errors

error[E0277]: the trait bound `futures_signals::signal::mutable::Mutable<std::string::String>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Serialize` is not satisfied
  --> src/todo.rs:17:5
   |
17 |     title: Mutable<String>,
   |     ^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Serialize` is not implemented for `futures_signals::signal::mutable::Mutable<std::string::String>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::ser::SerializeStruct::serialize_field`

error[E0277]: the trait bound `futures_signals::signal::mutable::Mutable<bool>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Serialize` is not satisfied
  --> src/todo.rs:18:5
   |
18 |     pub completed: Mutable<bool>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Serialize` is not implemented for `futures_signals::signal::mutable::Mutable<bool>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::ser::SerializeStruct::serialize_field`

error[E0277]: the trait bound `futures_signals::signal::mutable::Mutable<std::string::String>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not satisfied
  --> src/todo.rs:17:5
   |
17 |     title: Mutable<String>,
   |     ^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not implemented for `futures_signals::signal::mutable::Mutable<std::string::String>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::de::SeqAccess::next_element`

error[E0277]: the trait bound `futures_signals::signal::mutable::Mutable<bool>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not satisfied
  --> src/todo.rs:18:5
   |
18 |     pub completed: Mutable<bool>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not implemented for `futures_signals::signal::mutable::Mutable<bool>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::de::SeqAccess::next_element`

error[E0277]: the trait bound `futures_signals::signal::mutable::Mutable<std::string::String>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not satisfied
  --> src/todo.rs:17:5
   |
17 |     title: Mutable<String>,
   |     ^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not implemented for `futures_signals::signal::mutable::Mutable<std::string::String>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::de::MapAccess::next_value`

error[E0277]: the trait bound `futures_signals::signal::mutable::Mutable<bool>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not satisfied
  --> src/todo.rs:18:5
   |
18 |     pub completed: Mutable<bool>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not implemented for `futures_signals::signal::mutable::Mutable<bool>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::de::MapAccess::next_value`

error[E0277]: the trait bound `futures_signals::signal_vec::mutable_vec::MutableVec<std::rc::Rc<todo::Todo>>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Serialize` is not satisfied
  --> src/app.rs:22:5
   |
22 |     todo_list: MutableVec<Rc<Todo>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Serialize` is not implemented for `futures_signals::signal_vec::mutable_vec::MutableVec<std::rc::Rc<todo::Todo>>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::ser::SerializeStruct::serialize_field`

error[E0277]: the trait bound `futures_signals::signal_vec::mutable_vec::MutableVec<std::rc::Rc<todo::Todo>>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not satisfied
  --> src/app.rs:22:5
   |
22 |     todo_list: MutableVec<Rc<Todo>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not implemented for `futures_signals::signal_vec::mutable_vec::MutableVec<std::rc::Rc<todo::Todo>>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::de::SeqAccess::next_element`

error[E0277]: the trait bound `futures_signals::signal_vec::mutable_vec::MutableVec<std::rc::Rc<todo::Todo>>: todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not satisfied
  --> src/app.rs:22:5
   |
22 |     todo_list: MutableVec<Rc<Todo>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::Deserialize<'_>` is not implemented for `futures_signals::signal_vec::mutable_vec::MutableVec<std::rc::Rc<todo::Todo>>`
   |
   = note: required by `todo::_IMPL_DESERIALIZE_FOR_Todo::_serde::de::MapAccess::next_value`

Update examples to separate view and state update!

I do not have a look into dominator, just have a quick look at example/stdweb/counter. Follow up what I've said about my concern in a discussion in rustwasm/gloo, I open this issue instead of saying this there because it's about dominator only.

At least, I think dominator should provide a way to move code that modifies the state out of the renderer. If it is already available, I think the examples should be updated to demonstrate that.

Isomorphic web app?

Hello, this project looks absolutely awesome!

I was wondering if someone could use dominator to build and isomorphic single page application using this library. From my limited knowledge I'm thinking SSR would needed to do this but I am unsure.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.