seed-rs / seed Goto Github PK
View Code? Open in Web Editor NEWA Rust framework for creating web apps
License: MIT License
A Rust framework for creating web apps
License: MIT License
Hi,
I'm watching seed several month. I like it and hope it become the wide-using wasm webapp dev framework.
everything is fine, except the following:
I'd like to create a frontend component library, like [ng-bootstrap](https://ng-bootstrap.github.io/#/components/alert/examples)
, using seed. It's a difficult task!
Is it possible that seed provide REACT like component, which have global level MODEL and component level MODEL at the same time?
Is it possible to provide a mechanism that make component can communicate with each other?
Hello, I've tried to put TodoMVC outside of examples into my project, but Orders
are not found, also on 0.3.2 docs I cannot find them as well. 0.3.3 docs are not built due to some usual nightly tooling issues. Could you tell me, what Orders is and how to use TodoMVC outsise of seed repo?
It would be cool to introduce a CHANGELOG.md
:)
Please update this code:
So that id accepts anything that implements Into<String>
.
That way if users want to pass in an id that is dynamic, it is reasonably possible to do so while using the builder interface.
I want to share one thought of mine about dynamic routing: The most mind-blowing simple but fully functional routing solution (based on window.history
) I have seen so far is one written in ReasonML:
Description
Code
Implementation is so much simpler than any of those react router implementations written in Javascript!
The secret sauce is just to split the URL (by /
) into a list
(ReasonML tail-recursive version of an array, or what in Rust is called Vec
) of path segments, than the user of the api can recursively pattern match on that list to do his thing.
I am new to Rust, but it seems to me Rust pattern matching should also allow to do something like that.
Return type:
fn update(msg: Msg, model: &mut Model) -> impl Updater<Msg> {
...
}
Before:
fn update(msg: Msg, model: &mut Model) -> Update<Msg> {
match msg {
Msg::Increment => {
model.count += 1;
Render.into()
}
Msg::Decrement => {
model.count -= 1;
Render.into()
}
Msg::Log(something) => {
log!(something);
Skip.into()
}
}
}
After:
fn update(msg: Msg, model: &mut Model) -> impl Updater<Msg> {
match msg {
Msg::Increment => model.count += 1,
Msg::Decrement => model.count -= 1,
Msg::Log(something) => {
log!(something);
Skip
}
}
}
Render
.into()
Skip
and ForceRender
in source codeNo breaking changes.
Implement trait Updater
for enum ShouldRender
, struct Update
and unit ()
. Unit will be de facto alias for Render
.
I'll try to implement it after we agree on a scope of changes. Thanks for opinions and counter-arguments.
It seems to happen when another test panics. I added a panic to one of my tests in the merge-attrs
branch, and got the following:
---- seed::dom_types::tests::merge_different_attrs output ----
error output:
panicked at 'oops', src/dom_types.rs:1431:9
...
---- seed::vdom::tests::update_promises output ----
error output:
panicked at 'cannot modify the panic hook from a panicking thread', src/libstd/panicking.rs:99:9
JS exception that was thrown:
RuntimeError: unreachable executed
std::panicking::rust_panic_with_hook::hfee0088857287f5e@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1392]:0x11d303
std::panicking::begin_panic::hbb52db289a771dc8@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[6266]:0x1a4d04
std::panicking::set_hook::hb4aa901e5df5948e@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[2968]:0x163abe
<seed::vdom::App<Ms, Mdl>>::run::h6974a0b857137a67@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[224]:0x77b1a
seed::vdom::tests::update_promises::hf39e5b7d86fcc21d@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[667]:0xd8458
core::ops::function::FnOnce::call_once::h138edd608e53fa1e@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[6103]:0x1a306
<futures::future::lazy::Lazy<F, R>>::get::hbdd74f92fdede09a@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[368]:0xa55f
<futures::future::lazy::Lazy<F, R> as futures::future::Future>::poll::h679e659dd2c16847@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantia
<wasm_bindgen_test::__rt::TestFuture<F> as futures::future::Future>::poll::{{closure}}::{{closure}}::hf6964e778384fd02@http://127.0.0.1:35831/wasm-bindgen-test_bg
wasm_bindgen::convert::closures::invoke0_mut::hb742e1fd7f1ed33a@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1741]:0
cbarg0@http://127.0.0.1:35831/wasm-bindgen-test:248:20
window.__wbg_test_invoke@http://127.0.0.1:35831/:37:38
__wbg_wbgtestinvoke_7cb78623c45caf3c@http://127.0.0.1:35831/wasm-bindgen-test:261:13
wasm_bindgen_test::__rt::__wbg_test_invoke::hc29c5496854d23d3@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1030]:0x1
<wasm_bindgen_test::__rt::TestFuture<F> as futures::future::Future>::poll::{{closure}}::hba1e17724b720a6e@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > Web
<scoped_tls::ScopedKey<T>>::set::h2c0631d12c00fdc5@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1682]:0x12ecf6
<wasm_bindgen_test::__rt::TestFuture<F> as futures::future::Future>::poll::h027bed2b453d6636@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.inst
<alloc::boxed::Box<F> as futures::future::Future>::poll::h5cc15dea138dc8c3@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-funct
<wasm_bindgen_test::__rt::ExecuteTests as futures::future::Future>::poll::hdca1c41a428258b9@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.insta
<futures::future::map::Map<A, F> as futures::future::Future>::poll::hd38ea1b75a6600da@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate
<futures::future::map_err::MapErr<A, F> as futures::future::Future>::poll::h9156cda28ef36195@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.inst
<alloc::boxed::Box<F> as futures::future::Future>::poll::he7d6878934154090@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-funct
<futures::task_impl::Spawn<T>>::poll_future_notify::{{closure}}::hfb739fefd52fbee2@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wa
<futures::task_impl::Spawn<T>>::enter::{{closure}}::h101e3f9bd12f17cd@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[4
futures::task_impl::std::set::hf47c0b1e18fe8ee8@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1701]:0x12fe5d
<futures::task_impl::Spawn<T>>::enter::h851ac3033f34fc28@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1141]:0x10a1de
<futures::task_impl::Spawn<T>>::poll_fn_notify::hae4a04219bf076cf@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[2198]
<futures::task_impl::Spawn<T>>::poll_future_notify::h33e4757647d66c71@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[4
wasm_bindgen_futures::_future_to_promise::Package::poll::h67e6d3dfb2d4e5ea@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-funct
wasm_bindgen_futures::_future_to_promise::{{closure}}::h4171d10b7b9053ca@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-functio
wasm_bindgen::convert::closures::invoke2_mut::h87ce774e1313d19e@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1052]:0
cbarg0@http://127.0.0.1:35831/wasm-bindgen-test:1097:20
__wbg_new_73c7b9beced23b50@http://127.0.0.1:35831/wasm-bindgen-test:1110:34
js_sys::Promise::new::h5ba4d9a47d564f1d@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1666]:0x12de9b
wasm_bindgen_futures::_future_to_promise::h8220e7422916ffdc@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[1278]:0x115
wasm_bindgen_futures::future_to_promise::hb18f1446cf2ecb50@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[2196]:0x1483
wasm_bindgen_test::__rt::Context::run::h1de5750c65d2f37d@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[215]:0x72f53
wasmbindgentestcontext_run@http://127.0.0.1:35831/wasm-bindgen-test_bg line 8 > WebAssembly.instantiate:wasm-function[910]:0xf4567
run@http://127.0.0.1:35831/wasm-bindgen-test:1355:27
main@http://127.0.0.1:35831/run.js:35:19
failures:
seed::dom_types::tests::merge_different_attrs
seed::vdom::tests::update_promises
test result: FAILED. 15 passed; 2 failed; 0 ignored
console.log div contained:
panicked at 'oops', src/dom_types.rs:1431:9
panicked at 'cannot modify the panic hook from a panicking thread', src/libstd/panicking.rs:99:9
error: some tests failed
error: test failed, to rerun pass '--lib'
What do y'all think?
Eg instead of this:
input![ attrs!{"value" => model.what_we_count}, input_ev("input", Msg::ChangeWWC) ]
This:
input![ attrs!{At::Value => model.what_we_count}, input_ev(Ev::Input, Msg::ChangeWWC) ]
We can (and do in the case of tags and events) validate attrs/tags/events etc internally using Enums, but this might provide better real-time assessment, eg in IDEs. What do y'all think?
edit: Added this as an optional, eg the attrs! macro and event funcs accept either; leaning towards making it the default in guide/examples. Haven't done Style yet
Consider (using seed v0.2.6):
#[macro_use]
extern crate seed;
fn main () {
assert!(true);
}
cargo expand
displays:
fn main() {
if !true {
{
::std::rt::begin_panic("assertion failed: true",
&("iml-action-dropdown/src/lib.rs",
{
let mut el =
El::empty_svg(seed::dom_types::Tag::Line);
el
}, 5u32))
}
};
}
I've tracked this down to the line!
macro.
If we do not import the line! macro, the assert expands as expected.
I've been running into an issue where I have a few views which have a somewhat similar layout.
When the view is changed, the child elements which are similar are not updated. It would seem that the diffing algorithm is not taking into account certain attributes during the diff. The exact issue is as follows:
At::Value
s are different.I tried using different IDs on the parent elements which are being changed, in hopes that the diffing algorithm would see this as a fundamentally different element and invalidate all of its children, but I know there are lots of nuances here.
I have the code in a public repo, let me know if you need repro steps.
I'll make a pull request shortly with a test showing what I mean
Hi,
I like the direction the project is going with regards to the view templates, it looking like the Elm view DSL, rather than JSX, as well as the Elm inspired app model. As a matter of fact, I've been making such a DSL myself, but this implementation seems to be much more complete.
One idea that I considered interesting is in addition to working with DOM, is to wrap WebRender(https://github.com/servo/webrender) to make native widgets, sort of like this project (https://github.com/cztomsik/node-webrender), but the output would be entirely native binaries(and given WebRender is moving to use the native libraries as much a possible, the binaries should be very small as well).
I'm very much planning to do that, that being said, it might become a side project that goes nowhere.
So, wanted to ask if this project only plans to target the web via DOM.
Thanks
And the message in the Option::except call doesn't show up.
1) New possible .mount(..)
calls:
// A - HTMLElement argument
seed::App::build(Model::default(), update, view).mount(seed::body())
// B - &str argument
...mount("app") // mount to element with `id` 'app'
// C - Element argument
....mount(seed::body().querySelector("section").unwrap().unwrap())
// D - Default mount point
seed::App::build(Model::default(), update, view) // not called => same as B
2)
Remove function .mount_el(..)
mount(&str)
, mount_el(Element)
, ..):
mount_el(seed::body().into())
if we want to pass HTMLElement
as argument or implement another function like mount_html_el
Removing function .mount_el(..)
:
deprecated
and remove it in the futurepub fn mount(mut self, mount_point: impl MountPoint) -> Self {
...
}
I'll implement it after we agree on a scope of changes. Thanks for opinions and counter-arguments.
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 seed you can test this by commenting out this line from the todomvc example and then trying to edit an item. Because the internal state isn't changing, arguably neither should the render. Unfortunately, it does.
React solves this by restoring state after a change event (described on the linked issue).
In the example of widow listeners, if I set the watching
field of the model to True
by default, the listeners regarding the mouse event seem disable. It is also said at the end of the documentation page concerning windows listeners.
Do you have any clue to fix this issue? Does it come from the WASM generation or anything else?
The build.sh
files compile in debug mode, as do the readme instructions. cargo build --release
greatly improves performance, but this also requires modifying the wasm-bindgen
line, and also users might not be aware of the difference this makes.
Using an enum for attributes is a nice idea but does not work as long you're not able to user custom ones.
E.g. here is a log of my example app that has some SVG in it:
Can't find this attribute: transform
Can't find this attribute: x1
Can't find this attribute: y1
Can't find this attribute: x2
Can't find this attribute: y2
Can't find this attribute: x1
Can't find this attribute: y1
Can't find this attribute: x2
Can't find this attribute: y2
Can't find this attribute: fill
Can't find this attribute: stroke-width
Can't find this attribute: stroke
Can't find this attribute: d
And I don't want to create custom attributes in another way than usual ones.
Components have lifecycle hooks but do not provide a means to update the state of the app. This makes it difficult to cleanup any component actions. For example, consider a component that when mouseover occurs, will fetch data every ten seconds. When I leave the page and the component unmounts, I can catch the unmount event, but I have no way to send a message that would allow me to stop fetching.
I was able to handle this before the update to 0.3 by using state.update
in the view function; but state is no longer passed in as an argument. At the moment, we are slowly integrating seed into an existing app, so we are not at the point where we are using routes (I think I could hook into there if I was). I think the easiest thing (from my point of view) would be to provide a way to send messages in lifecycle callbacks, similar to how events can send messages in their callback function. Is this something that can be done?
Regards,
Will
If I understand properly, each time the update
function changes the model, a render
gets triggered.
This is a problem with my recent experiment to add animations based on a requestAnimationFrame
loop to control spring-like animations. Whether the animations should run or not, depends on user interactions, e.g. if a button click triggers to open a modal then the animation fades or slides the modal in. I want to store the animation metadata (which changes during the lifetime of an animation) in a hashmap in the model. But updating that metadata triggers a 'render'. I don't want that, because the animations work directly on the DOM elements.
Here how some other frameworks handle conditional model update:
update
returns a new model, like in seed, but if a field in the model is defined as mutable, then an update of only that field won't trigger a render
. Would that work in seed ?Another approach could be to let update
return an Option<Model>
and None
would avoid a rerender.
[Update: The ReasonML approach will not work, Rust does not allow field mutability, I just learned]
Building on the quickstart repo, I tried to display part of the model on the page:
use seed::*;
use seed::prelude::*;
// Model
#[derive(Clone, Debug)]
struct Model {
input: String
}
impl Default for Model {
fn default() -> Self {
Self {
input: "".into() // <--- change this to any other string to make it work
}
}
}
// Update
#[derive(Clone, Debug)]
enum Msg {
ChangeInput(String)
}
use self::Msg::*;
fn update(msg: Msg, model: Model) -> Model {
match msg {
ChangeInput(x) => Model { input: x }
}
}
// View
/// The top-level component we pass to the virtual dom.
fn view(_state: seed::App<Msg, Model>, model: Model) -> El<Msg> {
div![
p![ model.input ],
input![ attrs!{"type" => "text"; "placeholder" => "Enter input text.."}, input_ev("input", Msg::ChangeInput), model.input ]
]
}
#[wasm_bindgen]
pub fn render() {
// The final parameter is an optional routing map.
seed::run(Model::default(), update, view, "main", None, None);
}
The problem seems to be that the paragraph isn't updated if it's empty at first. It works when the initial value isn't empty.
The API currently wraps set_interval
but does not provide a wrapper for set_timeout
. Please consider adding this functionality as well.
Thank you David for your awesome work! I'm very new to Rust and Seed is the only frontend framework I find accessible.
But I discovered a problem. If I put multiple text nodes in an El
, only the last one survives.
An example:
p![ "Today it's ", em!["not"], " gonna rain, this ", em!["is"], " so ", strong![ "good!" ] ]
becomes
<p> so <em>not</em><em>is</em><strong>good!</strong></p>
I suppose it's because the text in the El-struct is Option<String>
. Maybe we need Vec<String>
. But then it's still not clear where which text should go. Maybe it's best to have a special "Text"-Node that can go in the children-Vec.
For now we can work around this e.g. wrapping the text in <span>
tags.
When I have a better understanding of the codebase I try to contribute a fix, but as I said, I'm very new to Rust.
Instead of reinventing the wheel, it might be helpful to use an existing crate like one of the following:
stdweb
)I've got a creative moment, so I tried to design a Seed logo:
Hi,
can I return more elements from root view
?
I think that fn view(model: &Model) -> Vec<El<Msg>>
would correspond more with quote from Seed documentation:
Fragments (<>...</> syntax in React and Yew) are components that represent multiple elements without a parent. They're useful to avoid unecessary divs, which clutter teh DOM, and breaks things like tables and CSS-grid.
Thanks!
I try to run a seed app in a native wrapper, based on tether, for a desktop app. To get the native app interop with the seed webapp, I need somehow to update the seed app state from outside of the app loop. Is that possible ? Below are the relevant parts, and commented out, what I would like to do. Or is there a different way to achieve that kind of interoperability ?
...
#[wasm_bindgen]
pub fn render() {
seed::run(Model::default(), update, view, "main", None);
}
#[wasm_bindgen]
pub fn my_exported_js_bridge(my_paylod: &str) -> () {
// first we need to get the current model somehow
// then:
// seed::eg_update_state(new_model_derived_from_my_paylod) ????
}
I've just encountered a problem with classes and found this TODO in dom_types.rs
:
// TODO:
/*
/// Tests that multiple class attributes are handled correctly
#[wasm_bindgen_test]
pub fn merge_classes() {
let node = el_to_websys(a![
class!["my_class1", "my_class2"],
class!["my_class3"],
attrs![
At::Class => "my_class4 my_class5";
]
]);
let mut expected = HashMap::new();
expected.insert(
"class".to_string(),
"my_class1 my_class2 my_class3 my_class4 my_class5".to_string(),
);
assert_eq!(expected, get_node_attrs(&node));
}
*/
I'll try to fix it.
First, Thanks for this awesome project! This is my favorite Rust/Webassembly project so far.
Adding a ?something to (any?) seed site with routing causes infinite reloads. For example: changing https://seed-rs.org/guide/7 to https://seed-rs.org/guide/7?adwd causes it to reload forever. Tested on Chrome and Safari on MacOS.
This is a problem for me because I posted my site ( https://tiberiusferreira.github.io/gringoshouse ) on facebook and facebook rewrites it as https://tiberiusferreira.github.io/gringoshouse/?fbclid=IwAR0bd0aunkNfmsimtcOvpq0VaaEbbslx_5LvqQaHRpMNbf4q2Ibs8v9may8 so I had to disable routing all together.
A good priciple for SPAs is that navigation should work just like normal web pages. That means that links should be actual clickable links that take the user to a new URL. However, we don't actually want to force a page reload for internal links, but handle them within the app. Typically this is handled by adding a click
handler to the link which redirects without reload, and this is possible with seed
today. However at the moment seed
does not intercept the default handling with event.preventDefault()
, so in fact anchor tags with href = ...
do still force a page reload.
This stackoverflow answer has a nice JS snippet that can be added at page load which will intercept link navigation. Suggestion: seed
should call such a function at page load. The function should also check whether the link is internal or external - if external, it should be followed normally.
First off, this is a pretty cool Rust web framework! ๐
I was playing with the quickstart repo, and tried to add a setInterval
example:
fn view(state: seed::App<Msg, Model>, model: Model) -> El<Msg> {
let callback = Closure::wrap(Box::new(move || {
state.update(Msg::Increment);
}) as Box<dyn Fn()>);
div![
did_mount(move |_| {
let window = web_sys::window().unwrap();
window
.set_interval_with_callback_and_timeout_and_arguments_0(
// Note this method call, which uses `as_ref()` to get a `JsValue`
// from our `Closure` which is then converted to a `&Function`
// using the `JsCast::unchecked_ref` function.
callback.as_ref().unchecked_ref(),
1_000,
)
.unwrap();
}),
button![
simple_ev("click", Msg::Increment),
format!("Hello, World ร {}", model.val)
]
]
}
But I'm getting this error:
I'm pretty much new to WASM, and I'm unsure how to debug this ๐; I'm hoping if there's some guidance to this.
for example:
first question:
I want to change the window title like this:
window.title = "new title";
and how to do it via seed ?
second question:
i created a node: input ![..] and append a node: em! [..]
if user typed illegal letters, the node em![] change its text color to warn user: you are wrong..
how to do it via seed?
thank you very much. u r a nice person!
As far as I can tell, the current seed::storage
module only exposes access to local storage.
Any reason session storage has not been exposed? Probably just a matter of it not having been implemented yet, but I figured I would just go ahead and pop an issue for it.
Happy to help code this up. Interface is pretty damn near identical.
First, this framework looks very promising and the getting-started experience was very smooth thanks to the excellent documentation! I ported an app I started with yew to seed.
Now I would like to perform some DOM actions after a component has been inserted in the DOM, such as focus an input form element. Is this possible ?
To explain this a bit better, I need to be able to trigger a model update event from a closure passed to JS land. Take the websockets example for reference. Currently, it is recommended to pass a clone of the App<_, _>
to the various callbacks so that they can push updates to the system as needed.
However, this is complicated when the websocket goes down and a new one needs to be established. In such a case, we would need to have a reference to the App<_, _>
somewhere in the model so that it can be accessed when a new Websocket needs to be built, which will require new callbacks to be attached to the new Ws.
Seems like we could accomplish this by emitting an event to the model which takes a clone of the app, which would then be stored somewhere on the model for later use. This creates a cycle ... and definitely feels dirty ... but is a path forward. Especially because the cycle doesn't matter due to the fact that the app will most likely be alive for the entire duration of the program.
Thoughts?
I think in most contexts seed is used to create single page applications.
The whole app belongs to one real page but the "virtual" routing is usually done by using Location.hash
.
With a page located at https://example.com/single-page
seed should use URLs like
single-page#home
and single-page#other
instead of single-page/home
and single-page/other
.
Make the framework trigger and update immediately upon load (see #34 (comment))
I'm considering adding a crate that wraps Chrono, but includes functionality it doesn't include on the wasm-target, like getting the current date/time/dt, via js_sys
, and provides a cleaner API. Thoughts?
Caveats:
This is more suited to Gloo, but perhaps here is a better place to start, due to Gloo
's formal update process, and since it's not-yet released. The API wrapper will be opinionated.
While getting the current date/time etc is common, it may not be worth wrapping a non-web crate just for this functionality, and api improvements are out of this project's scope. I'm proposing this anyway because a clean dt api that can get current info is common in web-programming, and this project takes a batteries-included approach: I don't want users to have to hunt around and use binder code.
seed is absolutely one of my favorite rust-wasm projects at the moment.
I really would like to build a real app with it but before investing too much time into it i'd like to know your thoughts about the current state and the future of seed.
Do you see seed more as an experiment or are you planning to move it into a production ready state?
The Fetch module works great but it has a couple of expects where I would hope to have a Result that I can handle to say something went wrong with the request. Today you could do the string and Json deserialization manually and handle the errors but it would be nice if the built in functionality allowed for it.
I'm trying to implement a drag&drop system where the user has a list of div
s and they can reorder them by dragging them around. The div
under the dragged element should be highlighted.
To identify the highlighted element, I'd like to compare the current drag position with each div
's bounding box, retrieved with get_bounding_client_rect
. This would be done inside view
, to decide whether to apply the highlighting styles, and it would use the bounding box of the element rendered by the previous view
call.
Of course, I can't just access the element in my view
function. So I tried using did_mount
. There I can retrieve the bounding box, but I can't save it in my model. I got around that by having an Rc<RefCell<...>>
in the model and then modifying it in did_mount
, but this doesn't seem the right way to do things.
In Elm, I had done this using Browser.Dom.getElement
. I think I called it from my init
function, and I think the Task was executed after view
, and then again later due to some subscription event.
I now realize that I can use document.get_element_by_id
at view time to get the previously rendered element (if any) by ID. This would probably be cleaner than the RefCell, but it seems like it would be nicer to keep the information inside my Model.
How should I solve this?
I am trying to implement some simple login redirection for my web frontend using Seed: if the user navigates to a protected page but is not logged in, redirect to the login page (by pushing a route).
This feels like logic that ought to be in my model's update
call. However, seed::push_route
requires the application App<Msg, Model>
object. Meanwhile, all the code examples seem to be capturing the App
in the view()
method, and using an event to trigger the route-pushing. How would I trigger this event programmatically? All the events (e.g., simple_ev(...)
) seem to be related to user interaction, but I want my application to do this automatically. Some sort of "send event to myself" call would seem in order (e.g., a hypothetical seed::queue_message(...)
).
I have additionally tried calling seed::window().location().assign(...)
, but this seems to lead to redirections loops--the browser refreshes/redirects itself over and over to the same page.
Any ideas on how to inspect the model and programmatically push a route?
My experience during debugging merging classes - I was mainly using:
wasm-pack test --firefox --headless -- --lib merge_classes
console.log
: log!(format!("Something: {:#?}", something))
I think that there are more user-friendly ways or we can create them. Add your ideas into this issue and we can resolve them in the future.
Excellent work! The responsiveness of launching it directly through firefox was good.
the quick start works with one hiccup. no pkg dir. "mkdir pkg" fixes the issue.
I tried the todomvc example. added four items task1, task2, task3, task4, checked them off as completed, then clicked the "X" button at the far right of each item row. task1 deleted when I clicked task1, but task3 was deleted when I clicked task2. When I clicked clear completed, it did not destroy task2 either.
My initial view of this lib is "cool, Elm but in Rust!". However it has also elements of React (componentDidMount etc) which reduce the "purity" of the experience for some convenience, which is obviously a design tradeoff.
Now in Elm, the update
function returns a Model
and a Msg
. The model is immediately rendered by the view
function, the Msg
(if any) is added back into the event loop and is handled the next time round. This means data and effects flow in the same direction all the time, which IMO makes it nice to reason about.
The update
function is seed
is a little different. It only returns a Model
, or rather, an Update<Model>
. Any effects from the message (i.e. creating more messages) are, as far as I can tell, supposed to be handled 'in place', by passing around the App
and running App.update
as and when it is necessary.
I hit a 'race condition' of sorts using this style. I made a login fetch
request, and when the function returns I want to redirect to the Home
route. When fetch
resolves it runs something like
|login_data| app.update(Msg::LoggedIn(login_data, app.clone()))
This callback triggers a new message to be sent through the event loop, OK, fine. Then I handle it in the update
function like
match msg {
LoggedIn(session, app) => {
app.update(Msg::ChangePage(Route::Home));
Model { session: Some(session), ..model }
},
// ....
}
Spot the bug? app.update
runs immediately, redirecting to Home
, then returns to the update
function, which creates a new model with the previous Route
, effectively taking me back to the login page. I could change the new model to point to Route::Home too, but I really want all routing to happen only through Msg::ChangePage
as that handles url-updating as well.
Really what I want is the Elm behavior of just returning a variant Effect(Model, Msg)
from my match statement, and have the framework handle it in order. The I won't have to pass the App
around, also.
Thoughts?
It would be cool to create an example that uses WebSocket :)
To reproduce the bug I created a little example:
https://github.com/flosse/seed/tree/rendering-bug
If you click on a menu item the ordering gets destroyed :(
Is it possible to use Seed to develop applications with Sciter for front-end ?
requestAnimationFrame
ForceRender
(or RenderNow
(?)) into enum ShouldRender
ForceRender
ignores requestAnimationFrame
and forces app renderMain reason is performance, however there are some other reasons, see dev.opera.com/articles/better-performance-with-requestanimationframe/.
Frameworks which already use rAF:
There are no API breaking changes, but there can be some special cases where ForceRender
should be called instead of Render
.
See github.com/elm/virtual-dom/issues/107 (there are broken links, just replace elm-lang
with elm
in their urls)
Can be inspired by:
I'll try to implement it after we agree on a scope of changes. Thanks for opinions and counter-arguments.
We can add hooks/events for animation frame - see Elm-Browser-Animation docs. It will allow us to create precise animations.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.