Skip to content

Commit

Permalink
Merge pull request #73 from DenisKolodin/add-scenes-to-crm
Browse files Browse the repository at this point in the history
Add scenes to crm example
  • Loading branch information
Penny Wing committed Jan 2, 2018
2 parents 502eb94 + f100474 commit f8e0b33
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 75 deletions.
6 changes: 6 additions & 0 deletions examples/crm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## Yew CRM Demo

The main goals of this demo example to show you how to:

* Add multiple screens with Yew (scenes)
* Use storage service
212 changes: 142 additions & 70 deletions examples/crm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,134 +8,206 @@ use yew::format::Json;
use yew::services::dialog::DialogService;
use yew::services::storage::{StorageService, Scope};

const KEY: &'static str = "yew.crm";
const KEY: &'static str = "yew.crm.database";

struct Context {
storage: StorageService,
dialog: DialogService,
}

#[derive(Serialize, Deserialize)]
struct Database {
clients: Vec<Client>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Client {
first_name: String,
last_name: String,
}

impl Client {
fn empty() -> Self {
Client {
first_name: "".into(),
last_name: "".into(),
}
}
}

#[derive(Debug)]
enum Scene {
Initialization,
ClientsList,
NewClientForm(Client),
Settings,
}

struct Model {
clients: Vec<Client>,
first_name_value: String,
last_name_value: String,
database: Database,
scene: Scene,
}

#[derive(Debug)]
enum Msg {
SwitchTo(Scene),
AddNew,
UpdateFirstName(String),
UpdateLastName(String),
Store,
Restore,
Clear,
Nope,
}

fn load_database(context: &mut Context) -> Database {
let Json(database) = context.storage.restore(KEY);
database.unwrap_or_else(|_| Database {
clients: Vec::new(),
})
}

fn update(context: &mut Context, model: &mut Model, msg: Msg) {
match msg {
Msg::AddNew => {
let client = Client {
first_name: model.first_name_value.clone(),
last_name: model.last_name_value.clone(),
};
model.clients.push(client);
model.first_name_value = "".to_string();
model.last_name_value = "".to_string();
}
Msg::UpdateFirstName(val) => {
println!("Input: {}", val);
model.first_name_value = val;
}
Msg::UpdateLastName(val) => {
println!("Input: {}", val);
model.last_name_value = val;
let mut new_scene = None;
match model.scene {
Scene::Initialization => {
match msg {
Msg::SwitchTo(Scene::ClientsList) => {
new_scene = Some(Scene::ClientsList);
}
unexpected => {
panic!("Unexpected message during initialization: {:?}", unexpected);
}
}
}
Msg::Store => {
context.storage.store(KEY, Json(&model.clients));
Scene::ClientsList => {
match msg {
Msg::SwitchTo(Scene::NewClientForm(client)) => {
new_scene = Some(Scene::NewClientForm(client));
}
Msg::SwitchTo(Scene::Settings) => {
new_scene = Some(Scene::Settings);
}
unexpected => {
panic!("Unexpected message when clients list shown: {:?}", unexpected);
}
}
}
Msg::Restore => {
if let Json(Ok(clients)) = context.storage.restore(KEY) {
model.clients = clients;
} else {
context.dialog.alert("Oh no! Storage was corrupted!");
Scene::NewClientForm(ref mut client) => {
match msg {
Msg::UpdateFirstName(val) => {
println!("Input: {}", val);
client.first_name = val;
}
Msg::UpdateLastName(val) => {
println!("Input: {}", val);
client.last_name = val;
}
Msg::AddNew => {
let mut new_client = Client::empty();
::std::mem::swap(client, &mut new_client);
model.database.clients.push(new_client);
context.storage.store(KEY, Json(&model.database));
}
Msg::SwitchTo(Scene::ClientsList) => {
new_scene = Some(Scene::ClientsList);
}
unexpected => {
panic!("Unexpected message diring new client editing: {:?}", unexpected);
}
}
}
Msg::Clear => {
if context.dialog.confirm("Do you really want to clear the data?") {
model.clients.clear();
context.storage.remove(KEY);
Scene::Settings => {
match msg {
Msg::Clear => {
if context.dialog.confirm("Do you really want to clear the data?") {
model.database.clients.clear();
context.storage.remove(KEY);
}
}
Msg::SwitchTo(Scene::ClientsList) => {
new_scene = Some(Scene::ClientsList);
}
unexpected => {
panic!("Unexpected message for settings scene: {:?}", unexpected);
}
}
}
Msg::Nope => {}
}
if let Some(new_scene) = new_scene.take() {
model.scene = new_scene;
}
}

fn view(model: &Model) -> Html<Msg> {
let view_client = |client: &Client| {
html! {
<div class="client",>
<p>{ format!("First Name: {}", client.first_name) }</p>
<p>{ format!("Last Name: {}", client.last_name) }</p>
match model.scene {
Scene::Initialization => html! {
<div>{ "Loading..." }</div>
},
Scene::ClientsList => html! {
<div class="crm",>
<div class="clients",>
{ for model.database.clients.iter().map(view_client) }
</div>
<button onclick=|_| Msg::SwitchTo(Scene::NewClientForm(Client::empty())),>{ "Add New" }</button>
<button onclick=|_| Msg::SwitchTo(Scene::Settings),>{ "Settings" }</button>
</div>
}
};
html! {
<div class="crm",>
<div class="clients",>
{ for model.clients.iter().map(view_client) }
},
Scene::NewClientForm(ref client) => html! {
<div class="crm",>
<div class="names",>
{ view_first_name_input(client) }
{ view_last_name_input(client) }
</div>
<button disabled=client.first_name.is_empty() || client.last_name.is_empty(),
onclick=|_| Msg::AddNew,>{ "Add New" }</button>
<button onclick=|_| Msg::SwitchTo(Scene::ClientsList),>{ "Go Back" }</button>
</div>
<div class="names",>
{ view_first_name_input(&model) }
{ view_last_name_input(&model) }
},
Scene::Settings => html! {
<div>
<button onclick=|_| Msg::Clear,>{ "Clear Database" }</button>
<button onclick=|_| Msg::SwitchTo(Scene::ClientsList),>{ "Go Back" }</button>
</div>
<button onclick=|_| Msg::AddNew,>{ "AddNew" }</button>
<button onclick=|_| Msg::Store,>{ "Store" }</button>
<button onclick=|_| Msg::Restore,>{ "Restore" }</button>
<button onclick=|_| Msg::Clear,>{ "Clear" }</button>
},
}
}

fn view_client(client: &Client) -> Html<Msg> {
html! {
<div class="client",>
<p>{ format!("First Name: {}", client.first_name) }</p>
<p>{ format!("Last Name: {}", client.last_name) }</p>
</div>
}
}

fn view_first_name_input(model: &Model) -> Html<Msg> {
fn view_first_name_input(client: &Client) -> Html<Msg> {
html! {
<input class=("new-client", "firstname"),
placeholder="First name",
value=&model.first_name_value,
value=&client.first_name,
oninput=|e: InputData| Msg::UpdateFirstName(e.value),
onkeypress=|e: KeyData| {
if e.key == "Enter" { Msg::AddNew } else { Msg::Nope }
}, />
/>
}
}

fn view_last_name_input(model: &Model) -> Html<Msg> {
fn view_last_name_input(client: &Client) -> Html<Msg> {
html! {
<input class=("new-client", "lastname"),
placeholder="Last name",
value=&model.last_name_value,
value=&client.last_name,
oninput=|e: InputData| Msg::UpdateLastName(e.value),
onkeypress=|e: KeyData| {
if e.key == "Enter" { Msg::AddNew } else { Msg::Nope }
}, />
/>
}
}

fn main() {
let mut app = App::new();
let context = Context {
let mut context = Context {
storage: StorageService::new(Scope::Local),
dialog: DialogService,
};
let model = Model {
clients: Vec::new(),
first_name_value: "".into(),
last_name_value: "".into(),
};
let database = load_database(&mut context);
let scene = Scene::Initialization;
let model = Model { database, scene };
app.sender().send(Msg::SwitchTo(Scene::ClientsList));
app.run(context, model, update, view);
}
6 changes: 5 additions & 1 deletion src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ pub struct App<MSG> {
impl<MSG: 'static> App<MSG> {
/// Creates a context with connected sender and receiver.
pub fn new() -> Self {
stdweb::initialize();
js! {
// Set dummy loop to process sent messages later
window.yew_loop = function() { }
};
let (tx, rx) = channel();
App {
tx,
Expand All @@ -75,7 +80,6 @@ impl<MSG: 'static> App<MSG> {
U: Fn(&mut CTX, &mut MOD, MSG) + 'static,
V: Fn(&MOD) -> Html<MSG> + 'static,
{
stdweb::initialize();
clear_body();
let body = document().query_selector("body").unwrap();
// No messages at start
Expand Down
11 changes: 9 additions & 2 deletions src/virtual_dom/vnode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,15 @@ impl<MSG> VNode<MSG> {
reference: Some(element),
}) => {
// Copy reference from right to left (as is)
right = Some(vtag);
*reference = Some(element);
if left.tag() == vtag.tag() {
right = Some(vtag);
*reference = Some(element);
} else {
let wrong = element;
let element = document().create_element(left.tag());
parent.replace_child(&element, &wrong);
*reference = Some(element);
}
}
Some(VNode::VText { reference: Some(wrong), .. }) => {
let element = document().create_element(left.tag());
Expand Down
2 changes: 0 additions & 2 deletions src/virtual_dom/vtag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,6 @@ impl<MSG> VTag<MSG> {
/// Renders virtual tag over DOM `Element`, but it also compares this with an opposite `VTag`
/// to compute what to pach in the actual DOM nodes.
pub fn render(&mut self, subject: &Element, mut opposite: Option<Self>, messages: Messages<MSG>) {
// TODO Replace self if tagName differs

let changes = self.soakup_classes(&mut opposite);
for change in changes {
let list = subject.class_list();
Expand Down

0 comments on commit f8e0b33

Please sign in to comment.