Создание веб-приложения SPA на Rust
Я хотел создать SPA (одностраничное приложение), использующее только файлы ржавчины, нет. JavaScript
или же HTML
файлы.
Я понял это, заметив, что WASM
код должен вызываться из JS
с использованием fetch
так что хотя может использовать XHR
или же Fetch
может использоваться для вызова необходимой функциональности из Rust
а пользовательский интерфейс форм/страниц можно назвать html
ответ тоже.
У меня было 2 проблемы, а именно:
- Необходимость отправки данных формы в виде json, поэтому я создал для этого глобальную функцию, чтобы избежать многократного кодирования одних и тех же строк.
- Загрузка первой страницы, так как до этого ничего нельзя сделать
window.loaded
так что даже первая страница была загружена при получении ответа XHR
Итак, я закодировал ниже для:
- Создание глобальной функции
toJSONString(form)
, - Вызов
'/first'
Пользовательский интерфейс и отображать его
#[get("/")]
fn index() -> content::Html<&'static str> { content::Html(r#" <script> (function (root, factory) { if ( typeof define === 'function' && define.amd ) { define([], factory(root)); } else if ( typeof exports === 'object' ) { module.exports = factory(root); } else { root.oryxPlugin = factory(root); } })(typeof global !== "undefined" ? global : this.window || this.global, function (root) {
; var oryxPlugin = {}; oryxPlugin.toJSONString = function(form){ var obj = {}; var elements = form.querySelectorAll( "input, select, textarea" ); for( var i = 0; i < elements.length; ++i ) { var element = elements[i]; var name = element.name; var value = element.value; if( name ) { obj[name] = value; } } return JSON.stringify( obj ); }; return oryxPlugin; }); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { var s = document.createElement('script'); s.type="text/javascript"; if (this.readyState == 4 && this.status == 200) { s.appendChild(document.createTextNode(this.responseText)); document.body.appendChild(s); } }; xhr.open("GET", '/first'); xhr.send(); </script> "#)
}
И закодировал ниже как '/first'
загрузка пользовательского интерфейса страницы, в которой я разделил страницу на 2 части:
- заголовок, который будет использоваться для меню и так далее
- контекст, быть
empty
а такжеrefilled
с каждым новым содержимым страницы
fn first() -> (content::Html<&'static str>) { content::Html(r#" var hdr = document.createElement("div"); hdr.id = 'header' hdr.innerHTML= ` <h1>Welcome to my app</h1><br> <button id = 'btn'>load second screen</button> `; hdr.querySelector(' var context = document.querySelector('#context') var xhr = new XMLHttpRequest(); xhr.open("GET", '/second'); xhr.onreadystatechange = function() { var s = document.createElement('script'); s.type="text/javascript"; if (this.readyState == 4 && this.status == 200) { s.appendChild(document.createTextNode(this.responseText)); document.body.appendChild(s); } }; xhr.send(); } document.body.appendChild(hdr); var context = document.createElement("div"); context.id = 'context' var form = document.createElement("form"); var button = document.createElement("button"); form.innerHTML =` <input type="text" name="fname" /> <input type="text" name="lname" /> ` button.onclick = function(){ var dataContainer = oryxPlugin.toJSONString(form); console.log(dataContainer); var xhr = new XMLHttpRequest(); xhr.open("POST", '/call', true); xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var obj = JSON.parse(this.responseText); console.log('Returned string is: ' + obj.fname + ', ' + obj.lname); } }; xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xhr.send(dataContainer); }; button.innerHTML ='Click HERE' context.appendChild(<h5>This is the first screen</h5><br>`) context.appendChild(form) context.appendChild(button) document.body.appendChild(context); "#)
}
Каждая страница после first
страница, может быть закодирована аналогично приведенному ниже, где содержимое context
элемент постоянно обновляется:
#[get("/second")]
fn second() -> (content::Html<&'static str>) { content::Html(r#" var div = document.createElement("div"); div.innerHTML= ` <h5>This is the second screen</h5><br> `; while (context.hasChildNodes()) { context.removeChild(context.lastChild); } context.appendChild(div); "#)
}
Обрабатывать json
получено/отправлено, ниже код для выполнения обоих шагов:
struct Name { fname: String, lname: String,
} fn call(name: Json<Name>) -> Json<Name> { let user = &name; println!("Form field is: {:?} ", user); dbg!(user); println!("Fist name: {0}, Family name: {1}", user.fname, user.lname); let x = Name{ fname : name.fname.to_owned(), lname : name.lname.to_owned() }; Json(x)
}
Это было сделано с помощью rocket.rs
Итак main.rs
заголовок должен быть:
use rocket_contrib::json::Json; use rocket::response::content;
И маршруты должны быть определены как:
fn main() { rocket::ignite() .mount("/", routes![index, call, first, second]) .launch();
}
Не забыть Cargo.toml
является:
[package]
name = "workshop"
version = "0.1.0"
authors = ["Hasan Yousef"]
edition = "2018"
[dependencies]
rocket = "0.4.0"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
[dependencies.rocket_contrib]
version = "0.4.0"
default-features = false
features = ["json"]
Nightly
требуется ржавчина rocket.rs
приложения до сих пор.