├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── index.html ├── screenshot.gif └── src ├── input.rs ├── item.rs ├── main.rs ├── modal.rs └── model.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "anyhow" 5 | version = "1.0.25" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14" 8 | 9 | [[package]] 10 | name = "anymap" 11 | version = "0.12.1" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" 14 | 15 | [[package]] 16 | name = "autocfg" 17 | version = "0.1.7" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 20 | 21 | [[package]] 22 | name = "bincode" 23 | version = "1.2.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" 26 | dependencies = [ 27 | "byteorder", 28 | "serde", 29 | ] 30 | 31 | [[package]] 32 | name = "boolinator" 33 | version = "2.4.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" 36 | 37 | [[package]] 38 | name = "bumpalo" 39 | version = "3.2.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" 42 | 43 | [[package]] 44 | name = "byteorder" 45 | version = "1.3.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 48 | 49 | [[package]] 50 | name = "bytes" 51 | version = "0.5.4" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" 54 | 55 | [[package]] 56 | name = "cfg-if" 57 | version = "0.1.10" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 60 | 61 | [[package]] 62 | name = "cfg-if" 63 | version = "1.0.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 66 | 67 | [[package]] 68 | name = "cfg-match" 69 | version = "0.2.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "8100e46ff92eb85bf6dc2930c73f2a4f7176393c84a9446b3d501e1b354e7b34" 72 | 73 | [[package]] 74 | name = "console_error_panic_hook" 75 | version = "0.1.6" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" 78 | dependencies = [ 79 | "cfg-if 0.1.10", 80 | "wasm-bindgen", 81 | ] 82 | 83 | [[package]] 84 | name = "fnv" 85 | version = "1.0.6" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 88 | 89 | [[package]] 90 | name = "futures" 91 | version = "0.3.8" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" 94 | dependencies = [ 95 | "futures-channel", 96 | "futures-core", 97 | "futures-executor", 98 | "futures-io", 99 | "futures-sink", 100 | "futures-task", 101 | "futures-util", 102 | ] 103 | 104 | [[package]] 105 | name = "futures-channel" 106 | version = "0.3.8" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" 109 | dependencies = [ 110 | "futures-core", 111 | "futures-sink", 112 | ] 113 | 114 | [[package]] 115 | name = "futures-core" 116 | version = "0.3.8" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" 119 | 120 | [[package]] 121 | name = "futures-executor" 122 | version = "0.3.8" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" 125 | dependencies = [ 126 | "futures-core", 127 | "futures-task", 128 | "futures-util", 129 | ] 130 | 131 | [[package]] 132 | name = "futures-io" 133 | version = "0.3.8" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" 136 | 137 | [[package]] 138 | name = "futures-macro" 139 | version = "0.3.8" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" 142 | dependencies = [ 143 | "proc-macro-hack", 144 | "proc-macro2", 145 | "quote", 146 | "syn", 147 | ] 148 | 149 | [[package]] 150 | name = "futures-sink" 151 | version = "0.3.8" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" 154 | 155 | [[package]] 156 | name = "futures-task" 157 | version = "0.3.8" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" 160 | dependencies = [ 161 | "once_cell", 162 | ] 163 | 164 | [[package]] 165 | name = "futures-util" 166 | version = "0.3.8" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" 169 | dependencies = [ 170 | "futures-channel", 171 | "futures-core", 172 | "futures-io", 173 | "futures-macro", 174 | "futures-sink", 175 | "futures-task", 176 | "memchr", 177 | "pin-project", 178 | "pin-utils", 179 | "proc-macro-hack", 180 | "proc-macro-nested", 181 | "slab", 182 | ] 183 | 184 | [[package]] 185 | name = "gloo" 186 | version = "0.2.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "68ce6f2dfa9f57f15b848efa2aade5e1850dc72986b87a2b0752d44ca08f4967" 189 | dependencies = [ 190 | "gloo-console-timer", 191 | "gloo-events", 192 | "gloo-file", 193 | "gloo-timers", 194 | ] 195 | 196 | [[package]] 197 | name = "gloo-console-timer" 198 | version = "0.1.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "b48675544b29ac03402c6dffc31a912f716e38d19f7e74b78b7e900ec3c941ea" 201 | dependencies = [ 202 | "web-sys", 203 | ] 204 | 205 | [[package]] 206 | name = "gloo-events" 207 | version = "0.1.1" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "088514ec8ef284891c762c88a66b639b3a730134714692ee31829765c5bc814f" 210 | dependencies = [ 211 | "wasm-bindgen", 212 | "web-sys", 213 | ] 214 | 215 | [[package]] 216 | name = "gloo-file" 217 | version = "0.1.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "8f9fecfe46b5dc3cc46f58e98ba580cc714f2c93860796d002eb3527a465ef49" 220 | dependencies = [ 221 | "gloo-events", 222 | "js-sys", 223 | "wasm-bindgen", 224 | "web-sys", 225 | ] 226 | 227 | [[package]] 228 | name = "gloo-timers" 229 | version = "0.2.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" 232 | dependencies = [ 233 | "js-sys", 234 | "wasm-bindgen", 235 | "web-sys", 236 | ] 237 | 238 | [[package]] 239 | name = "http" 240 | version = "0.2.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" 243 | dependencies = [ 244 | "bytes", 245 | "fnv", 246 | "itoa", 247 | ] 248 | 249 | [[package]] 250 | name = "indexmap" 251 | version = "1.3.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" 254 | dependencies = [ 255 | "autocfg", 256 | ] 257 | 258 | [[package]] 259 | name = "itoa" 260 | version = "0.4.4" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 263 | 264 | [[package]] 265 | name = "js-sys" 266 | version = "0.3.46" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" 269 | dependencies = [ 270 | "wasm-bindgen", 271 | ] 272 | 273 | [[package]] 274 | name = "lazy_static" 275 | version = "1.4.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 278 | 279 | [[package]] 280 | name = "log" 281 | version = "0.4.8" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 284 | dependencies = [ 285 | "cfg-if 0.1.10", 286 | ] 287 | 288 | [[package]] 289 | name = "memchr" 290 | version = "2.2.1" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 293 | 294 | [[package]] 295 | name = "once_cell" 296 | version = "1.5.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" 299 | 300 | [[package]] 301 | name = "pin-project" 302 | version = "1.0.3" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "5a83804639aad6ba65345661744708855f9fbcb71176ea8d28d05aeb11d975e7" 305 | dependencies = [ 306 | "pin-project-internal", 307 | ] 308 | 309 | [[package]] 310 | name = "pin-project-internal" 311 | version = "1.0.3" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "b7bcc46b8f73443d15bc1c5fecbb315718491fa9187fa483f0e359323cde8b3a" 314 | dependencies = [ 315 | "proc-macro2", 316 | "quote", 317 | "syn", 318 | ] 319 | 320 | [[package]] 321 | name = "pin-utils" 322 | version = "0.1.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 325 | 326 | [[package]] 327 | name = "proc-macro-hack" 328 | version = "0.5.19" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 331 | 332 | [[package]] 333 | name = "proc-macro-nested" 334 | version = "0.1.3" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" 337 | 338 | [[package]] 339 | name = "proc-macro2" 340 | version = "1.0.24" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 343 | dependencies = [ 344 | "unicode-xid", 345 | ] 346 | 347 | [[package]] 348 | name = "quote" 349 | version = "1.0.2" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 352 | dependencies = [ 353 | "proc-macro2", 354 | ] 355 | 356 | [[package]] 357 | name = "rust-crud" 358 | version = "0.1.0" 359 | dependencies = [ 360 | "serde", 361 | "serde_json", 362 | "yew", 363 | ] 364 | 365 | [[package]] 366 | name = "ryu" 367 | version = "1.0.2" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 370 | 371 | [[package]] 372 | name = "serde" 373 | version = "1.0.118" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" 376 | dependencies = [ 377 | "serde_derive", 378 | ] 379 | 380 | [[package]] 381 | name = "serde_derive" 382 | version = "1.0.118" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" 385 | dependencies = [ 386 | "proc-macro2", 387 | "quote", 388 | "syn", 389 | ] 390 | 391 | [[package]] 392 | name = "serde_json" 393 | version = "1.0.61" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" 396 | dependencies = [ 397 | "itoa", 398 | "ryu", 399 | "serde", 400 | ] 401 | 402 | [[package]] 403 | name = "slab" 404 | version = "0.4.2" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 407 | 408 | [[package]] 409 | name = "syn" 410 | version = "1.0.58" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" 413 | dependencies = [ 414 | "proc-macro2", 415 | "quote", 416 | "unicode-xid", 417 | ] 418 | 419 | [[package]] 420 | name = "thiserror" 421 | version = "1.0.23" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" 424 | dependencies = [ 425 | "thiserror-impl", 426 | ] 427 | 428 | [[package]] 429 | name = "thiserror-impl" 430 | version = "1.0.23" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" 433 | dependencies = [ 434 | "proc-macro2", 435 | "quote", 436 | "syn", 437 | ] 438 | 439 | [[package]] 440 | name = "unicode-xid" 441 | version = "0.2.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 444 | 445 | [[package]] 446 | name = "wasm-bindgen" 447 | version = "0.2.69" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" 450 | dependencies = [ 451 | "cfg-if 1.0.0", 452 | "wasm-bindgen-macro", 453 | ] 454 | 455 | [[package]] 456 | name = "wasm-bindgen-backend" 457 | version = "0.2.69" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" 460 | dependencies = [ 461 | "bumpalo", 462 | "lazy_static", 463 | "log", 464 | "proc-macro2", 465 | "quote", 466 | "syn", 467 | "wasm-bindgen-shared", 468 | ] 469 | 470 | [[package]] 471 | name = "wasm-bindgen-futures" 472 | version = "0.4.5" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "1458706aa1b8fe6898d19433c9f110d93a05d1f22ae6adf55810409a94df34b4" 475 | dependencies = [ 476 | "cfg-if 0.1.10", 477 | "js-sys", 478 | "wasm-bindgen", 479 | "web-sys", 480 | ] 481 | 482 | [[package]] 483 | name = "wasm-bindgen-macro" 484 | version = "0.2.69" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" 487 | dependencies = [ 488 | "quote", 489 | "wasm-bindgen-macro-support", 490 | ] 491 | 492 | [[package]] 493 | name = "wasm-bindgen-macro-support" 494 | version = "0.2.69" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" 497 | dependencies = [ 498 | "proc-macro2", 499 | "quote", 500 | "syn", 501 | "wasm-bindgen-backend", 502 | "wasm-bindgen-shared", 503 | ] 504 | 505 | [[package]] 506 | name = "wasm-bindgen-shared" 507 | version = "0.2.69" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" 510 | 511 | [[package]] 512 | name = "web-sys" 513 | version = "0.3.46" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" 516 | dependencies = [ 517 | "js-sys", 518 | "wasm-bindgen", 519 | ] 520 | 521 | [[package]] 522 | name = "yew" 523 | version = "0.17.4" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "2d8703eb5b883e816cd74c65e2f6dd4144eeedb77c1b3e0284e8f3f593b80ab1" 526 | dependencies = [ 527 | "anyhow", 528 | "anymap", 529 | "bincode", 530 | "cfg-if 0.1.10", 531 | "cfg-match", 532 | "console_error_panic_hook", 533 | "futures", 534 | "gloo", 535 | "http", 536 | "indexmap", 537 | "js-sys", 538 | "log", 539 | "proc-macro-hack", 540 | "proc-macro-nested", 541 | "ryu", 542 | "serde", 543 | "serde_json", 544 | "slab", 545 | "thiserror", 546 | "wasm-bindgen", 547 | "wasm-bindgen-futures", 548 | "web-sys", 549 | "yew-macro", 550 | ] 551 | 552 | [[package]] 553 | name = "yew-macro" 554 | version = "0.17.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "61a9a452e63b6222b28b426dafbc6b207192e0127cdb93324cc7407b8c7e1768" 557 | dependencies = [ 558 | "boolinator", 559 | "lazy_static", 560 | "proc-macro-hack", 561 | "proc-macro2", 562 | "quote", 563 | "syn", 564 | ] 565 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-crud" 3 | version = "0.1.0" 4 | authors = ["joselo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | yew = "0.17.4" 11 | serde = "1.0.118" 12 | serde_json = "1.0.61" 13 | 14 | [lib] 15 | name = "yew_dev_viewer" 16 | path = "src/model.rs" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust CRUD 2 | 3 | ![Screenshot](screenshot.gif) 4 | 5 | Basic CRUD (Create, Read, Update, Delete) [Rust] application with [Yew Framework] that uses WebAssembly :heart_eyes:. 6 | I'm still learning rust, so the app has some minor issues, but it general it works to show you an approach of Yew and Rust. 7 | 8 | [Rust]: https://www.rust-lang.org 9 | [Yew Framework]: https://github.com/yewstack/yew 10 | 11 | ## Current Features 12 | 13 | * Create, update and delete items 14 | * Local Storage 15 | * Validations 16 | * Modal Window 17 | 18 | ### Future Features 19 | 20 | * Confirm dialog 21 | * Filters 22 | * Pagination 23 | 24 | ## Getting Started 25 | 26 | You need to [install Rust] and [Trunk] then: 27 | 28 | ```bash 29 | $ git clone https://github.com/joselo/rust-crud 30 | $ cd rust-crud 31 | $ trunk serve 32 | ``` 33 | 34 | Then you can access the web server at `http://127.0.0.1:8080`. 35 | 36 | [install Rust]: https://www.rust-lang.org/tools/install 37 | [Trunk]: https://github.com/thedodd/trunk 38 | 39 | ## Authors 40 | 41 | * **Jose Carrion** - *Initial work* - [Joselo](https://github.com/joselo) 42 | 43 | ## License 44 | 45 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 46 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rust Crud Example 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joselo/rust-crud/72b1e0636f7f315bbbed0efa9953659f0acb8d1b/screenshot.gif -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use yew::{Callback, ComponentLink, Component, ShouldRender, Html, html, InputData, Properties}; 2 | 3 | #[derive(Properties, Clone)] 4 | pub struct TextInputProps { 5 | pub value: String, 6 | pub oninput: Callback, 7 | } 8 | 9 | pub struct TextInput { 10 | value: String, 11 | link: ComponentLink, 12 | oninput: Callback, 13 | } 14 | 15 | pub enum TextInputMsg { 16 | Changed(String), 17 | } 18 | 19 | impl Component for TextInput { 20 | type Message = TextInputMsg; 21 | type Properties = TextInputProps; 22 | 23 | fn create(props: Self::Properties, link: ComponentLink) -> Self { 24 | TextInput { 25 | value: props.value, 26 | oninput: props.oninput, 27 | link, 28 | } 29 | } 30 | 31 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 32 | match msg { 33 | TextInputMsg::Changed(value) => { 34 | self.oninput.emit(value); 35 | } 36 | } 37 | false 38 | } 39 | 40 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 41 | self.value = props.value; 42 | self.oninput = props.oninput; 43 | true 44 | } 45 | 46 | fn view(&self) -> Html { 47 | html! { 48 | 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/item.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Default, Clone, PartialEq, Serialize, Deserialize)] 5 | pub struct Item { 6 | pub id: usize, 7 | pub name: String, 8 | pub price: f32 9 | } 10 | 11 | #[derive(Default, PartialEq)] 12 | pub struct ItemFormData { 13 | pub name: String, 14 | pub price: String 15 | } 16 | 17 | #[derive(Debug, PartialEq)] 18 | pub struct ValidatedItem { 19 | name: String, 20 | price: String 21 | } 22 | 23 | #[derive(Debug, PartialEq)] 24 | pub enum ItemValidationErr { 25 | InvalidName, 26 | InvalidPrice 27 | } 28 | 29 | impl ItemFormData { 30 | pub fn validate(form_data: &ItemFormData) -> Result> { 31 | let mut errors = vec![]; 32 | 33 | let name = ItemFormData::validate_name(String::from(&form_data.name)) 34 | .unwrap_or_else(|e| { 35 | errors.push(e); 36 | String::from("") 37 | }); 38 | 39 | let price = ItemFormData::validate_price(String::from(&form_data.price)) 40 | .unwrap_or_else(|e| { 41 | errors.push(e); 42 | String::from("") 43 | }); 44 | 45 | if !errors.is_empty() { 46 | return Err(errors); 47 | } 48 | 49 | Ok( ValidatedItem { name, price } ) 50 | } 51 | 52 | fn validate_name(name: String) -> Result { 53 | if name.len() > 1 { 54 | Ok(name) 55 | } else { 56 | Err(ItemValidationErr::InvalidName) 57 | } 58 | } 59 | 60 | fn validate_price(price: String) -> Result { 61 | if price.parse::().is_ok() { 62 | Ok(price) 63 | } else { 64 | Err(ItemValidationErr::InvalidPrice) 65 | } 66 | } 67 | } 68 | 69 | impl From<(String, String)> for ItemFormData { 70 | fn from(fd: (String, String)) -> Self { 71 | let name = fd.0; 72 | let price = fd.1; 73 | 74 | Self { 75 | name, 76 | price, 77 | ..Default::default() 78 | } 79 | } 80 | } 81 | 82 | impl Item { 83 | pub fn generate_id() -> usize { 84 | static COUNTER:AtomicUsize = AtomicUsize::new(1); 85 | COUNTER.fetch_add(1, Ordering::Relaxed) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //mod document; 2 | use yew_dev_viewer::Model; 3 | 4 | fn main() { 5 | yew::start_app::(); 6 | } 7 | -------------------------------------------------------------------------------- /src/modal.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Component, ComponentLink, Html, ShouldRender, Callback, Properties}; 2 | 3 | use crate::item::Item; 4 | use crate::item::ItemFormData; 5 | use crate::item::ItemValidationErr; 6 | use crate::input::TextInput; 7 | 8 | use yew::services::{ 9 | ConsoleService 10 | }; 11 | 12 | #[derive(Properties, Clone)] 13 | pub struct ModalProperties { 14 | pub item: Item, 15 | pub visible: bool, 16 | pub on_close: Callback, 17 | pub on_save: Callback 18 | } 19 | 20 | pub struct Modal { 21 | pub item: Item, 22 | pub name: String, 23 | pub price: String, 24 | pub visible: bool, 25 | pub on_close: Callback, 26 | pub on_save: Callback, 27 | error: Option>, 28 | link: ComponentLink 29 | } 30 | 31 | pub enum ModalMsg { 32 | HideModal, 33 | SetName(String), 34 | SetPrice(String), 35 | Save 36 | } 37 | 38 | impl Component for Modal { 39 | type Message = ModalMsg; 40 | type Properties = ModalProperties; 41 | 42 | fn create(prop: Self::Properties, link: ComponentLink) -> Self { 43 | Self { 44 | item: prop.item, 45 | name: "".to_string(), 46 | price: "".to_string(), 47 | visible: prop.visible, 48 | on_close: prop.on_close, 49 | on_save: prop.on_save, 50 | error: None, 51 | link 52 | } 53 | } 54 | 55 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 56 | match msg { 57 | ModalMsg::HideModal => { 58 | self.visible = false; 59 | self.on_close.emit(true); 60 | 61 | true 62 | } 63 | 64 | ModalMsg::SetName(name) => { 65 | self.name = name; 66 | 67 | true 68 | } 69 | 70 | ModalMsg::SetPrice(price) => { 71 | self.price = price; 72 | 73 | true 74 | } 75 | 76 | ModalMsg::Save => { 77 | let form_data: ItemFormData = (self.name.clone(), self.price.clone()).into(); 78 | let valid = ItemFormData::validate(&form_data); 79 | 80 | match valid { 81 | Ok(_v) => { 82 | self.visible = false; 83 | self.on_save.emit(Item { 84 | id: self.item.id, 85 | name: form_data.name, 86 | price: form_data.price.parse().unwrap(), 87 | ..Default::default() 88 | }); 89 | 90 | //self.error = None; 91 | ConsoleService::info("Saved"); 92 | }, 93 | Err(e) => { 94 | self.error = Some(e) 95 | } 96 | } 97 | 98 | true 99 | } 100 | } 101 | } 102 | 103 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 104 | self.name = props.item.name.clone(); 105 | self.price = props.item.price.to_string(); 106 | self.item = props.item; 107 | self.visible = props.visible; 108 | self.error = None; 109 | 110 | true 111 | } 112 | 113 | fn view(&self) -> Html { 114 | let visible = if self.visible { "is-active" } else { "" }; 115 | 116 | let error = |e: &ItemValidationErr| { 117 | match e { 118 | ItemValidationErr::InvalidName => html! { 119 |
120 | {"Name is required"} 121 |
122 | }, 123 | ItemValidationErr::InvalidPrice => html! { 124 |
125 | {"Invalid Price"} 126 |
127 | } 128 | } 129 | }; 130 | 131 | let errors = match self.error.as_ref() { 132 | None => { 133 | html! {} 134 | } 135 | 136 | Some(errors) => { 137 | html! { 138 |
139 | {for errors.iter().map(error)} 140 |
141 | } 142 | } 143 | }; 144 | 145 | let title = if self.item.name.is_empty() { 146 | "New Item" 147 | } else { 148 | "Update Item" 149 | }; 150 | 151 | html! { 152 |
153 | 154 | 189 |
190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /src/model.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "512"] 2 | 3 | use yew::{html, Component, ComponentLink, Html, ShouldRender}; 4 | use yew::format::Json; 5 | use yew::services::storage::{Area, StorageService}; 6 | 7 | mod item; 8 | mod modal; 9 | mod input; 10 | 11 | use crate::item::Item; 12 | use crate::modal::Modal; 13 | 14 | const KEY: &'static str = "yew.rust.crud.database"; 15 | 16 | pub struct Model { 17 | storage: StorageService, 18 | state: List, 19 | link: ComponentLink 20 | } 21 | 22 | pub struct List { 23 | items: Vec, 24 | modal_visible: bool, 25 | current_item: Option 26 | } 27 | 28 | pub enum Msg { 29 | New, 30 | HiddedModal, 31 | Saved(Item), 32 | Edit(usize), 33 | Remove(usize), 34 | Store 35 | } 36 | 37 | impl Component for Model { 38 | type Message = Msg; 39 | type Properties = (); 40 | 41 | fn create(_: Self::Properties, link: ComponentLink) -> Self { 42 | let storage = StorageService::new(Area::Local).expect("Storage Error"); 43 | 44 | let items = { 45 | if let Json(Ok(items)) = storage.restore(KEY) { 46 | items 47 | } else { 48 | Vec::new() 49 | } 50 | }; 51 | 52 | let state = List { 53 | items, 54 | modal_visible: false, 55 | current_item: None 56 | }; 57 | 58 | Model { storage, state, link } 59 | } 60 | 61 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 62 | 63 | match msg { 64 | Msg::New => { 65 | self.state.modal_visible = true; 66 | self.state.current_item = None; 67 | 68 | true 69 | } 70 | 71 | Msg::HiddedModal => { 72 | self.state.modal_visible = false; 73 | true 74 | } 75 | 76 | Msg::Saved(item) => { 77 | if item.id == 0 { 78 | let mut item = item; 79 | item.id = Item::generate_id(); 80 | self.state.items.push(item); 81 | } else { 82 | let index = self.state.items.iter().position(|i| i.id == item.id).unwrap(); 83 | self.state.items[index] = item; 84 | } 85 | 86 | self.update(Msg::HiddedModal); 87 | self.update(Msg::Store); 88 | 89 | true 90 | } 91 | 92 | Msg::Edit(idx) => { 93 | let item = self.state.items[idx].clone(); 94 | self.state.current_item = Some(item); 95 | self.state.modal_visible = true; 96 | 97 | true 98 | } 99 | 100 | Msg::Remove(idx) => { 101 | self.state.items.remove(idx); 102 | self.update(Msg::Store); 103 | 104 | true 105 | } 106 | 107 | Msg::Store => { 108 | self.storage.store(KEY, Json(&self.state.items)); 109 | false 110 | } 111 | } 112 | } 113 | 114 | fn change(&mut self, _props: Self::Properties) -> bool { 115 | false 116 | } 117 | 118 | fn view(&self) -> Html { 119 | let modal = match self.state.current_item.as_ref() { 120 | None => { 121 | html! { 122 | 123 | } 124 | } 125 | 126 | Some(item) => { 127 | html! { 128 | 129 | } 130 | } 131 | }; 132 | 133 | html! { 134 | <> 135 | {modal} 136 |
137 |
138 |
139 |

140 | {{ "Items" }} 141 |

142 |

143 | {{"List of items"}} 144 |

145 |
146 |
147 |
148 |
149 |
150 | {{self.view_table()}} 151 |
152 |
153 | 154 | } 155 | } 156 | 157 | } 158 | 159 | impl Model { 160 | fn view_table(&self) -> Html { 161 | html! { 162 | <> 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | {for self.state.items.iter().enumerate().map(|idx_itm| self.view_item(idx_itm))} 174 | 175 |
{"Id"}{"Name"}{"Price"}
176 | 177 |
178 | 179 |
180 | 181 | } 182 | } 183 | 184 | fn view_item(&self, (idx, item): (usize, &Item)) -> Html { 185 | html! { 186 | 187 | {&item.id} 188 | {&item.name} 189 | {&item.price} 190 | 191 | 192 | 193 | } 194 | } 195 | } 196 | 197 | --------------------------------------------------------------------------------