Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
324 changes: 324 additions & 0 deletions demo/app/src/app/form_example/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
use vertigo::{Computed, Value, bind, component, css, dom, transaction};

type ErrorLine = (Vec<String>, String);

#[derive(Clone)]
struct ValidatorError {
message: Option<String>,
errors: Vec<ErrorLine>,
}

impl ValidatorError {
pub fn from_message(message: String) -> ValidatorError {
ValidatorError {
errors: Vec::new(), //vec![(vec![getter_name], message.clone())],
message: Some(message),
}
}
}

trait FormNode<T: Clone + PartialEq + 'static> {

Check failure on line 20 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Stable Demo/Examples Clippy Output

trait `FormNode` is never used

error: trait `FormNode` is never used --> demo/app/src/app/form_example/mod.rs:20:7 | 20 | trait FormNode<T: Clone + PartialEq + 'static> { | ^^^^^^^^ | = note: `-D dead-code` implied by `-D warnings` = help: to override `-D warnings` add `#[expect(dead_code)]` or `#[allow(dead_code)]`

Check failure on line 20 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Nightly Demo/Examples Clippy Output

trait `FormNode` is never used

error: trait `FormNode` is never used --> demo/app/src/app/form_example/mod.rs:20:7 | 20 | trait FormNode<T: Clone + PartialEq + 'static> { | ^^^^^^^^ | = note: `-D dead-code` implied by `-D warnings` = help: to override `-D warnings` add `#[expect(dead_code)]` or `#[allow(dead_code)]`
fn result(&self, getter_name: impl Into<String>) -> Computed<Result<T, ValidatorError>>;
}

struct ErrorBuilder {
errors: Vec<(Vec<String>, String)>,
}

impl ErrorBuilder {
pub fn new() -> ErrorBuilder {
ErrorBuilder { errors: Vec::new() }
}

pub fn add<T>(mut self, getter_name: String, result: Result<T, ValidatorError>) -> Self {
if let Err(err) = result {
for (mut path, message) in err.errors {
path.insert(0, getter_name.clone());
self.errors.push((path, message));
}

if let Some(message) = err.message {
self.errors.push((vec![getter_name], message));
}
}

self
}

pub fn set_error(mut self, getter_name: String, message: String) -> Self {

Check failure on line 48 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Stable Demo/Examples Clippy Output

method `set_error` is never used

error: method `set_error` is never used --> demo/app/src/app/form_example/mod.rs:48:12 | 28 | impl ErrorBuilder { | ----------------- method in this implementation ... 48 | pub fn set_error(mut self, getter_name: String, message: String) -> Self { | ^^^^^^^^^

Check failure on line 48 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Nightly Demo/Examples Clippy Output

method `set_error` is never used

error: method `set_error` is never used --> demo/app/src/app/form_example/mod.rs:48:12 | 28 | impl ErrorBuilder { | ----------------- method in this implementation ... 48 | pub fn set_error(mut self, getter_name: String, message: String) -> Self { | ^^^^^^^^^
self.errors.push((vec![getter_name], message));
self
}

pub fn export(self) -> ValidatorError {
ValidatorError {
message: None,
errors: self.errors,
}
}
}

// type Erro/

//........................................................................................
//........................................................................................
//........................................................................................

#[derive(Clone)]
struct FormValue<T: Clone + PartialEq + 'static> {

Check failure on line 68 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Stable Demo/Examples Clippy Output

struct `FormValue` is never constructed

error: struct `FormValue` is never constructed --> demo/app/src/app/form_example/mod.rs:68:8 | 68 | struct FormValue<T: Clone + PartialEq + 'static> { | ^^^^^^^^^

Check failure on line 68 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Nightly Demo/Examples Clippy Output

struct `FormValue` is never constructed

error: struct `FormValue` is never constructed --> demo/app/src/app/form_example/mod.rs:68:8 | 68 | struct FormValue<T: Clone + PartialEq + 'static> { | ^^^^^^^^^
text: Value<String>,
value: Value<T>,
// jedną stronę -->
// w drugą stronę -->
}

impl<T: Clone + PartialEq + 'static> FormNode<T> for FormValue<T> {
fn result(&self, _getter_name: impl Into<String>) -> Computed<Result<T, ValidatorError>> {
todo!()
}
}

//........................................................................................

#[derive(Clone)]
struct FormDate {

Check failure on line 84 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Stable Demo/Examples Clippy Output

struct `FormDate` is never constructed

error: struct `FormDate` is never constructed --> demo/app/src/app/form_example/mod.rs:84:8 | 84 | struct FormDate { | ^^^^^^^^

Check failure on line 84 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Nightly Demo/Examples Clippy Output

struct `FormDate` is never constructed

error: struct `FormDate` is never constructed --> demo/app/src/app/form_example/mod.rs:84:8 | 84 | struct FormDate { | ^^^^^^^^
day: FormValue<u8>,
mounth: FormValue<u8>,
year: FormValue<u16>,
// error: Value<Option<String>>,
}

#[derive(Clone, PartialEq)]
struct FormDate1 {

Check failure on line 92 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Stable Demo/Examples Clippy Output

struct `FormDate1` is never constructed

error: struct `FormDate1` is never constructed --> demo/app/src/app/form_example/mod.rs:92:8 | 92 | struct FormDate1 { | ^^^^^^^^^

Check failure on line 92 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Nightly Demo/Examples Clippy Output

struct `FormDate1` is never constructed

error: struct `FormDate1` is never constructed --> demo/app/src/app/form_example/mod.rs:92:8 | 92 | struct FormDate1 { | ^^^^^^^^^
day: u8,
mounth: u8,
year: u16,
}

impl FormNode<FormDate1> for FormDate {
fn result(
&self,
getter_name: impl Into<String>,
) -> Computed<Result<FormDate1, ValidatorError>> {
let ggg = self.clone();
let getter_name = getter_name.into();

Computed::from(move |context| {
let day = ggg.day.result("day").get(context);
let mounth = ggg.mounth.result("mounth").get(context);
let year = ggg.year.result("year").get(context);

if let (Ok(day), Ok(mounth), Ok(year)) = (day.clone(), mounth.clone(), year.clone()) {
if mounth == 2 && day > 29 {
return Err(ValidatorError::from_message(
// getter_name.clone(),
"Luty nie moze mieć więcej niz 29 dni".into(),
));
}

return Ok(FormDate1 { day, mounth, year });
}

let errors = ErrorBuilder::new()
.add(getter_name.clone(), day)
.add(getter_name.clone(), mounth)
.add(getter_name.clone(), year)
.export();
Err(errors)
})
}
}

//........................................................................................

#[derive(Clone)]
struct FormModel {

Check failure on line 135 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Stable Demo/Examples Clippy Output

struct `FormModel` is never constructed

error: struct `FormModel` is never constructed --> demo/app/src/app/form_example/mod.rs:135:8 | 135 | struct FormModel { | ^^^^^^^^^

Check failure on line 135 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Nightly Demo/Examples Clippy Output

struct `FormModel` is never constructed

error: struct `FormModel` is never constructed --> demo/app/src/app/form_example/mod.rs:135:8 | 135 | struct FormModel { | ^^^^^^^^^
name: FormValue<String>,
birth: FormDate,
start_schools: FormDate,
}

#[derive(Clone, PartialEq)]
struct FormModel1 {

Check failure on line 142 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Stable Demo/Examples Clippy Output

struct `FormModel1` is never constructed

error: struct `FormModel1` is never constructed --> demo/app/src/app/form_example/mod.rs:142:8 | 142 | struct FormModel1 { | ^^^^^^^^^^

Check failure on line 142 in demo/app/src/app/form_example/mod.rs

View workflow job for this annotation

GitHub Actions / Nightly Demo/Examples Clippy Output

struct `FormModel1` is never constructed

error: struct `FormModel1` is never constructed --> demo/app/src/app/form_example/mod.rs:142:8 | 142 | struct FormModel1 { | ^^^^^^^^^^
name: String,
birth: FormDate1,
start_schools: FormDate1,
}

impl FormNode<FormModel1> for FormModel {
fn result(
&self,
_getter_name: impl Into<String>,
) -> Computed<Result<FormModel1, ValidatorError>> {
todo!()
}
}

//........................................................................................

/*
w input mogą występować jakieś stany "przejściowe" które nie są dozwolone, ale są
konieczne zeby przejść z jednego dozwolonego stanu do innego


zestawy róznych walidatorów jak ograć
np, datę jak zrealizować ... ?

*/

#[component]
pub fn FormExample() {
let name = Value::new("ddadas".to_string());
let email = Value::new("".to_string());

let on_submit = bind!(name, email, |_| {
transaction(|context| {
let name_val = name.get(context);
let email_val = email.get(context);
log::info!("Form submitted: name={}, email={}", name_val, email_val);
});
});

let wrapper_css = css! {"
border: 1px solid #ccc;
padding: 20px;
margin: 20px;
border-radius: 8px;
max-width: 400px;
background-color: #f9f9f9;
display: flex;
flex-direction: column;
gap: 15px;
"};

let label_css = css! {"
display: flex;
flex-direction: column;
font-weight: bold;
gap: 5px;
"};

let input_css = css! {"
padding: 8px;
border: 1px solid #999;
border-radius: 4px;
font-weight: normal;
"};

let button_css = css! {"
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
:hover {
background-color: #0056b3;
}
"};

dom! {
<div css={wrapper_css}>
<h2>"Form Example"</h2>
<label css={label_css.clone()}>
"Name:"
<input
css={input_css.clone()}
value={&name}
on_input={bind!(name, |val| name.set(val))}
placeholder="Enter your name"
/>
</label>

<label css={label_css}>
"Email:"
<input
css={input_css}
value={&email}
on_input={bind!(email, |val| email.set(val))}
placeholder="Enter your email"
/>
</label>

<button css={button_css} on_click={on_submit}>
"Submit"
</button>

<div>
"Current name: " { name }
</div>
<div>
"Current email: " { email }
</div>
</div>
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_simple_date_errors() {
let err_day = ValidatorError::from_message("invalid day".into());
let err_month = ValidatorError::from_message("invalid month".into());

// Simulate FormDate failing
let date_errors = ErrorBuilder::new()
.add("day".into(), Err::<(), _>(err_day))
.add("month".into(), Err::<(), _>(err_month))
.export();

assert_eq!(date_errors.errors.len(), 2);
assert_eq!(
date_errors.errors[0],
(vec!["day".to_string()], "invalid day".to_string())
);
assert_eq!(
date_errors.errors[1],
(vec!["month".to_string()], "invalid month".to_string())
);
}

#[test]
fn test_error_propagation() {
let err_day = ValidatorError::from_message("invalid day".into());
let err_month = ValidatorError::from_message("invalid month".into());

// Simulate FormDate failing
let date_errors = ErrorBuilder::new()
.add("day".into(), Err::<(), _>(err_day))
.add("month".into(), Err::<(), _>(err_month))
.export();

// Simulate FormModel failing because of birth (FormDate)
let model_errors = ErrorBuilder::new()
.add("birth".into(), Err::<(), _>(date_errors))
.export();

for (path, msg) in &model_errors.errors {
println!("Path: {:?}, Msg: {}", path, msg);
}

// We expect:
// ["birth", "day"] from err_day (nested in date_errors)
// ["birth", "month"] from err_month (nested in date_errors)

assert_eq!(model_errors.errors.len(), 2);
assert_eq!(
model_errors.errors[0],
(
vec!["birth".to_string(), "day".to_string()],
"invalid day".to_string()
)
);
assert_eq!(
model_errors.errors[1],
(
vec!["birth".to_string(), "month".to_string()],
"invalid month".to_string()
)
);
}
}
1 change: 1 addition & 0 deletions demo/app/src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod chat;
mod counters;
mod dropfiles;
mod form_example;
mod game_of_life;
mod github_explorer;
mod input;
Expand Down
9 changes: 6 additions & 3 deletions demo/app/src/app/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ use vertigo::{Computed, Css, DomNode, KeyDownEvent, css, dom, get_driver, includ
use crate::app::{self, counters::state_counters, state::state_route};

use super::{
chat::Chat, counters::CountersDemo, dropfiles::DropFiles, game_of_life::GameOfLife,
github_explorer::GitHubExplorer, input::MyInput, js_api_access::JsApiAccess, list::ListDemo,
route::Route, styling::Styling, sudoku::Sudoku, todo::Todo,
chat::Chat, counters::CountersDemo, dropfiles::DropFiles, form_example::FormExample,
game_of_life::GameOfLife, github_explorer::GitHubExplorer, input::MyInput,
js_api_access::JsApiAccess, list::ListDemo, route::Route, styling::Styling, sudoku::Sudoku,
todo::Todo,
};

fn css_menu_item(active: bool) -> Css {
Expand Down Expand Up @@ -73,6 +74,7 @@ fn render_header() -> DomNode {
{ render_menu_item(Route::DropFile) }
{ render_menu_item(Route::JsApiAccess) }
{ render_menu_item(Route::List) }
{ render_menu_item(Route::Form) }
</div>
</div>
}
Expand Down Expand Up @@ -126,6 +128,7 @@ pub fn render(state: &app::State) -> DomNode {
Route::DropFile => dom! { <DropFiles /> },
Route::JsApiAccess => dom! { <JsApiAccess /> },
Route::List => dom! { <ListDemo /> },
Route::Form => dom! { <FormExample /> },
Route::NotFound => {
get_driver().set_status(404);
dom! { <div>"Page Not Found"</div> }
Expand Down
4 changes: 4 additions & 0 deletions demo/app/src/app/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum Route {
DropFile,
JsApiAccess,
List,
Form,
NotFound,
}

Expand All @@ -33,6 +34,7 @@ impl Route {
"/drop-file" => Self::DropFile,
"/js-api-access" => Self::JsApiAccess,
"/list" => Self::List,
"/form" => Self::Form,
_ => Self::NotFound,
}
}
Expand All @@ -50,6 +52,7 @@ impl Route {
Self::DropFile => "Drop File",
Self::JsApiAccess => "JS Api Access",
Self::List => "List",
Self::Form => "Form",
Self::NotFound => "Not Found",
}
}
Expand All @@ -76,6 +79,7 @@ impl Display for Route {
Self::DropFile => "/drop-file",
Self::JsApiAccess => "/js-api-access",
Self::List => "/list",
Self::Form => "/form",
Self::NotFound => "/not-found",
};
f.write_str(&get_driver().route_to_public(str))
Expand Down
Loading