Closed zengsai closed 6 years ago
#![feature(proc_macro_hygiene, decl_macro)]
use futures::{future, Future, Poll};
use js_sys::Promise;
use ruukh::prelude::*;
use serde_derive::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::future_to_promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};
#[component]
struct MyApp {
#[state]
message: String,
}
impl Render for MyApp {
fn render(&self) -> Markup<Self> {
html! {
<h1>"Todo Apps"</h1>
<p>"message:" {&self.message}</p>
}
}
}
/// The lifecycle of a stateful component.
///
/// When you do not require these lifecycle hooks, you may implement them with
/// an auto derive `#[derive(Lifecycle)]` on the component struct.
impl Lifecycle for MyApp {
/// Invoked when the component is mounted onto the DOM tree.
fn mounted(&self) {
self.set_state(|state| state.message = "hello world".into());
self.fetch_data();
}
/// Invoked when the component is removed from the DOM tree.
fn destroyed(&self) {}
}
// #[wasm_bindgen]
// pub struct FetchTask(Closure<FnMut()>);
#[wasm_bindgen]
extern "C" {
fn setTimeout(closure: &Closure<FnMut()>, time: u32);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
impl MyApp {
fn fetch_data(&self) -> Promise {
let setter = self.state_setter();
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);
let request = Request::new_with_str_and_init(
"https://api.github.com/repos/rustwasm/wasm-bindgen/branches/master",
&opts,
)
.unwrap();
request
.headers()
.set("Accept", "application/vnd.github.v3+json")
.unwrap();
let window = web_sys::window().unwrap();
let request_promise = window.fetch_with_request(&request);
let closure: Closure<dyn FnMut(JsValue)> = Closure::wrap(Box::new(move |b| {
handler(b);
}));
let promise = request_promise.then(&closure);
closure.forget();
promise
// let future = JsFuture::from(request_promise)
// .and_then(|resp_value| {
// // `resp_value` is a `Response` object.
// assert!(resp_value.is_instance_of::<Response>());
// let resp: Response = resp_value.dyn_into().unwrap();
// resp.json()
// })
// .and_then(|json_value: Promise| {
// // Convert this other `Promise` into a rust `Future`.
// JsFuture::from(json_value)
// })
// .and_then(|json| {
// // state.set_state(|state| state.message = string);
// // Use serde to parse the JSON into a struct.
// let branch_info: Branch = json.into_serde().unwrap();
// log(&format!("{:?}", branch_info));
// // Send the `Branch` struct back to JS as an `Object`.
// future::ok(JsValue::NULL)
// });
// // Convert this Rust `Future` back into a JS `Promise`.
// future_to_promise(future)
}
}
pub fn handler(b: JsValue) {
let resp: Response = b.dyn_into().unwrap();
log(&resp.status_text());
log("......");
}
#[wasm_bindgen]
pub fn run() {
App::<MyApp>::new().mount("app");
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Branch {
pub name: String,
pub commit: Commit,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Commit {
pub sha: String,
pub commit: CommitDetails,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CommitDetails {
pub author: Signature,
pub committer: Signature,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Signature {
pub name: String,
pub email: String,
}
I've tried this way, it works, handle
get called.
but still no way to update MyApp.messge using the result json from promise.
#![feature(proc_macro_hygiene, decl_macro)]
use futures::{future, Future};
use js_sys::Promise;
use ruukh::prelude::*;
use serde_derive::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::future_to_promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};
#[component]
struct MyApp {
#[state]
message: String,
#[state]
seconds: i32,
}
impl Render for MyApp {
fn render(&self) -> Markup<Self> {
html! {
<h1>"Todo Apps"</h1>
<p>"message:" {&self.message}</p>
}
}
}
/// The lifecycle of a stateful component.
///
/// When you do not require these lifecycle hooks, you may implement them with
/// an auto derive `#[derive(Lifecycle)]` on the component struct.
impl Lifecycle for MyApp {
fn mounted(&self) {
self.fetch_data();
}
/// Invoked when the component is removed from the DOM tree.
fn destroyed(&self) {}
}
// #[wasm_bindgen]
// pub struct FetchTask(Closure<FnMut()>);
#[wasm_bindgen]
extern "C" {
fn setTimeout(closure: &Closure<FnMut()>, time: u32);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
impl MyApp {
fn fetch_data(&self) -> Promise {
let setter = self.state_setter();
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);
let request = Request::new_with_str_and_init(
"https://api.github.com/repos/rustwasm/wasm-bindgen/branches/master",
&opts,
)
.unwrap();
request
.headers()
.set("Accept", "application/vnd.github.v3+json")
.unwrap();
let window = web_sys::window().unwrap();
let request_promise = window.fetch_with_request(&request);
let future = JsFuture::from(request_promise)
.and_then(|resp_value| {
// `resp_value` is a `Response` object.
assert!(resp_value.is_instance_of::<Response>());
let resp: Response = resp_value.dyn_into().unwrap();
resp.json()
})
.and_then(|json_value: Promise| {
// Convert this other `Promise` into a rust `Future`.
JsFuture::from(json_value)
})
.and_then(|json| {
// Use serde to parse the JSON into a struct.
let branch_info: Branch = json.into_serde().unwrap();
let s = setter;
s.set_state(|state| state.message = format!("{:?}", branch_info));
log(&format!("{:?}", branch_info));
// Send the `Branch` struct back to JS as an `Object`.
future::ok(JsValue::NULL)
});
// Convert this Rust `Future` back into a JS `Promise`.
future_to_promise(future)
}
}
#[wasm_bindgen]
pub fn run() {
App::<MyApp>::new().mount("app");
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Branch {
pub name: String,
pub commit: Commit,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Commit {
pub sha: String,
pub commit: CommitDetails,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CommitDetails {
pub author: Signature,
pub committer: Signature,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Signature {
pub name: String,
pub email: String,
}
Finally, It works!
I'd like to make it a fetch api for Ruukh.
@zengsai Yeah, you need to get a state_setter
before you can mutate the state within a closure.
I'd like to make it a fetch api for Ruukh.
Sorry, I did not understand what you mean. It seems you are successful in using a fetch api within Ruukh.
I mean I already write this fetch api as a service mod in my project. like FetchService in Yew
project, my code like this:
/// impl Lifecycle for Deities {
/// fn created(&self) {
/// self.set_state(|state| {
/// state.input = "Hello".into();
/// })
/// }
/// fn mounted(&self) {
/// let url = "http://0.0.0.0:3000/login";
///
/// let params = LoginParam {
/// shop_id: 123,
/// password: "hello".into(),
/// };
///
/// let state_for_data = self.state_setter();
///
/// let _ = post(url, params, move |data| {
/// state_for_data.set_state(|state| {
/// LoginInfo::from_resp(&data)
/// .map(|info| state.message = info.token)
/// .unwrap_or_else(|error| {
/// state.error = error.message();
/// });
/// })
/// })
/// .map_err(|e| {
/// self.set_state(|state| state.error = e.message());
/// });
/// }
/// }
pub fn post<H>(url: &str, params: impl Params, handler: H) -> Result<Promise, Error>
where
H: FnMut(String) + 'static,
{
let mut opts = RequestInit::new();
opts.method("POST").mode(RequestMode::Cors);
params
.fill_body(&mut opts)
.context(ErrorKind::Initailization)?;
let request = Request::new_with_str_and_init(url, &opts)
.map_err(|_| Error::from(ErrorKind::Initailization))?;
request
.headers()
.set("Content-Type", "application/json")
.map_err(|_| Error::from(ErrorKind::Initailization))?;
let window = web_sys::window().ok_or(Error::from(ErrorKind::Initailization))?;;
fetch(&window, &request, handler)
}
pub trait Params: Serialize {
fn fill_body<'b>(&self, opts: &'b mut RequestInit) -> serde_json::Result<&'b mut RequestInit> {
let s = serde_json::to_string(self)?;
opts.body(Some(&JsValue::from_str(&s)));
Ok(opts)
}
}
impl<T> Params for T where T: Serialize {}
fn fetch<H>(window: &Window, request: &Request, mut handler: H) -> Result<Promise, Error>
where
H: FnMut(String) + 'static,
{
use std::any::Any;
let request_promise = window.fetch_with_request(&request);
let future = JsFuture::from(request_promise)
.and_then(|resp_value| {
// `resp_value` is a `Response` object.
assert!(resp_value.is_instance_of::<Response>());
let resp: Response = resp_value.dyn_into().unwrap();
if resp.ok() {
resp.text()
} else {
Ok(Promise::reject(&JsValue::from(resp.status_text())))
}
})
.and_then(|js_value: Promise| {
// Convert this other `Promise` into a rust `Future`.
JsFuture::from(js_value)
})
.or_else(move |error| {
// AbortError The request was aborted (using AbortController.abort()).
// TypeError Since Firefox 43, fetch() will throw a TypeError if the URL has credentials, such as http://user:password@example.com.
// CustomError String type from this model.
let message = error.as_string().unwrap_or("网络通信错误".into());
log(&format!(
"FetchError: {:?} {}",
error.get_type_id(),
message
));
let error = format!(r#"{{"error": {{ "message": "{}"}}}}"#, message);
future::ok(JsValue::from_str(error.as_str()))
})
.and_then(move |text| {
let resp_text = text
.as_string()
.unwrap_or(r#"{{"error": {{ "message": "返回数据不是文本"}}}}"#.into());
log(&format!("FetchSuccess: {}", resp_text));
handler(resp_text);
future::ok(JsValue::null())
});
// Convert this Rust `Future` back into a JS `Promise`.
Ok(crate::future_to_promise(future))
}
But it not ready to push it, right now it only handle json
response, I need more time to make it a better and more generic service.
And I don't known if this kind of thing (it belong to data layer) is proper for this project, but we MUST have it in real project.
Are you trying to say that you would like to have a Service
-like thing within Ruukh? If that is the question then I think it would be better in a third-party library as it is pretty much framework agnostic.
If that was not your question, please write it in simple words what you are trying to achieve with Ruukh in a new issue (probably because it is a different issue).
Also, this issue has already been answered by you, so closing it.
#[derive(Debug, Serialize, Deserialize)] pub struct Signature { pub name: String, pub email: String, }
Finally, It works!
Yes, I mean "I would like to have a Service-like thing within Ruukh", a fetching Api for real project to communicate with backend server. @csharad
I can read English will, but not good at writing English, so ..., pardon me!
@zengsai That's fine :+1: .
But it still stands that a Service API is to be provided in a separate crate as the scope of this library is only framework specific things.
I've wrote a demo using ruukh, but don't known how to fetch server data(json). any idear?