# `application!` and `component!` Similar to other frameworks, `Simi` core thing is a virtual DOM. Everything that may change in the future will be tracked in the virtual DOM. On every update, the previously rendered values are check against the current value, if it has been changed, both virtual and real DOM will be updated. Everything else that will never change, `Simi` does not track them, instead, `Simi` render them directly to real DOM when the app started. `application!` and `component!` are responsible for building and updating DOMs. Internally, these two macros have the same implementation, working in two different modes (application mode or component mode). They generate code for building/update DOMs from a simi-style HTML template. We have a closer look at these two macros in this section. ## Syntax Supported HTML element tags are `keyword`s in the content of `application!` and `component!`. It means that you are not allowed to use a local variable name `div`, `span`... to store some value to be use inside the macros. ```rust let local_value application! { // HTML tags hr // `
` br // `
` div (...) // With attribute block span {...} // With content block p (...) {...} // With both attribute block and content block // Anything that is not a supported HTML element tag will be considered a value-expression. // Its value will be converted to a string and render as a Text node. local_value self.some_value } ``` ### Important: `simi` only works with a set of supported html tags. If `simi` found out that an identifier is not an html tag, it will automatically treat it as an expression. If you believe there are valid html tags that must be supported by `simi` please open an issue (or better: open a pull/merge request). ## Text and child content ```rust application! { p { "This is the first paragraph." } "Some text at the root level" p { "This paragraph contains" code { "some special text" } "in its content. It also contains" self.get_some_string() // represent as a text node self.field.some_funtion().count() // also represent as a text node ". Every expression output must have implemented trait ToString" } } ``` ## Attributes Attributes must be provided in the form of `name=value` pair. Rules for `name`: * An event name: `onclick`, `oninput`... * An attribute name: `type`, `checked`, `id`, `class`... * A hyphen-separated-series of identifiers such as `data-my-custom-attribute`. * A special simi-attribute: a `str` literal like `"class1"`, this is only use as a class name, see more below - in the *Conditional classes* section. Rules for `value`: * A literal: `"class-name"`, `"element-id"`... * A bool literal: `true`, `false` * A Rust expression: `self.get_name()` * A `Message` variant: `Msg::SendMessage`, `Msg::SetSomething(some_value)`, `Msg::ButtonClick(?)` (for events only) ```rust application! { p (id="first-para" class="class1 class2") { "..." } input (type="checkbox" checked=self.pull_request.is_approved()) ``` ## Conditional classes A `str` literal that is used in place of `attribute name` will be treated as a `class`. Its value will be the condition to add/remove itself to/from the real element. ```rust application! { div ( // These classes will never change class="class1 class2" // These classes will be on/off depend on the result value (true/false) of the expression "blue-text"=self.is_something_true() "border"=self.is_something_false() // Remember: it (attribute name) must be a str literal "this-must-be-a-str-literal"=other_bool_expression() ) { "IMPORTANT: expressions must evaluate to `bool`" } } ``` ## Events Messages that are sent back to the `Application::update` method: ```rust application! { // This will receive the event argument sended by JS in the place of `?`. button (onclick=Msg::Clicked(?)) { "Click me" } // This will ignore the event argument. button (onclick=Msg::SendMessage) { "Send" } // This will also ignore the event argument. button (onclick=Msg::SetValue(value)) { "Send" } } ``` ## `if ... else ...` and `match` `application!` and `component!` support `if else` and `match`: ```rust application! { // if else if some_expr { div {...} p {...} } else if another_expr { section {...} } else { span {...} } // match match some_value { value1 => { p {...} } value2 => { div {...} } } } ``` When rendering a new `if else`/`match` arm (different from the current rendered-arm in DOMs), all nodes (both virtual and real DOM) of the previous rendered-arm will be removed from their DOM, then the new arm will be created. So, you should minimize things in `if else`/`match` construct. A special case is `if` or `if else if`... (without the final `else`). Simi will create comment node as the content of the omitted `else`. If you want everything in your control, you must create the phantom-element yourself. ## Non-keyed `for` loop `application!` and `component!` supports `for` loop. Unlike `if else` or `match`, `for` only supports a single item at the root of its body: ```rust application! { for value in some_list.iter() { li { "use the value of " value } } } ``` ## Keyed `for` loop Simi does not support keyed `for` loop yet. ## Tell `Simi` not to update things with `#` `application!` and `component!` try to avoid building a new virtual DOM on update. It will try to update current virtual DOM, then - if it is required, updates the real DOM. When updating, `Simi` always ignore `literal`s. `Simi` can also ignore updating an attribute, a content or a whole sub DOM if you want. ```rust application! { p { "The initial value of count: " #self.count } // ^ // Prefix the value with `#`, Simi will only create it with the value when the app start. It will not be updated when the value change // Similarly, for an attribute: `alt` will not be updated img (src=self.image_url alt=#self.alt_string) div { p { ... } #div { // This `div` and its whole sub will be ignore when updating p { ... } span { ... } } } } ``` ## View only Simi does not like to have complex code in its macro. If you need complex code, do it before calling the macros. ```rust // Complex code here, outside of `application!` or ``component!` let check_on = match something { value1 => true, value2 if some_expr => true, _ => false, }; application! { input (type="checkbox" checked=check_on) // ^^^^^^^^ Use the result value here } ``` Or may be better, create a dedicated method for it. ```rust application! { input (type="checkbox" checked=self.is_check_on()) // ^^^^^^^^^^^^^^^^^^ Use a dedicated method } ```