skubalj / build_html

Library for basic server-side rendering written in pure rust https://crates.io/crates/build_html
MIT License
29 stars 5 forks source link

Insert or remove elements after `HtmlPage` has been constructed? #12

Open 766F6964 opened 1 year ago

766F6964 commented 1 year ago

Summary:

I was wondering, if there is a way to insert (or remove) new elements at a specific position in the HtmlPage after its initial construction. To me, this functionality would be really useful. I want to generate a basic "site-template" that my application would use by default. However, depending on how my application is configured, I might want to add additional features to that default "site-template", or remove some.

If I didn't overlook anything, that doesn't seem to be possible yet. I am not entirely sure on the API design for such functionality, but maybe something like this could work:

// Create a new HtmlPage
let html: doc = HtmlPage::new()
    .with_title("My Page")
    .with_header(1, "Main Content:");
// Query for elements to allow for updating them
let title = doc.query_selector("title");

// Remove elements from the doc
doc.remove(title);

// Insert new elements
let body = doc.query_selector("body");
body.add_container(Container::new(ContainerType::Article)
        .with_attributes([("id", "article1")])
        .with_header_attr(2, "Hello, World", [("id", "article-head"), ("class", "header")])
        .with_paragraph("This is a simple HTML demo")
));

Obviously this is just pseudo-code, but it would be nice if there is some way to modify the DOM after the HtmlPage has been created. This requires that HtmlPage stores the DOM, and not just the string somehow.

skubalj commented 1 year ago

Thanks for opening a ticket! You are correct: this is not supported by the current implementation in v2 because everything is stored as strings.

In early implementations, we stored everything as structs in Vecs and only converted them to strings at the end, which would make supporting this feature easy. However, this was dropped because it required using Vec<Box<dyn Html>> which was a much more complicated construction (and less memory efficient) than just storing everything as a String.

This may change when I find the time to design a v3 of this library, since all of the various Html types may get unified into one struct. (I've been experimenting on-and-off with a few different designs when I have time, so there are no guarantees on what v3 will look like)

In any regard, I'd suggest using string templates to achieve this. It's not a perfect solution, but it should work for simple cases:

let page_template = HtmlPage::new()
    .with_header(1, "Main Content:")
    .with_raw("{optional_content}")
    .to_html_string();

let optional = Container::new(ContainerType::Article)
    .with_attributes([("id", "article1")])
    .with_paragraph("example text")
    .to_html_string();

let page = if some_condition {
   format!(page_template, optional_content = optional)
} else {
    format!(page_template, optional_content = "")
};

In fact, when this library was originally designed, I was keeping my HTML in a separate file so it could be linted and formatted nicely. build_html was only used to create elements that had to be dynamic, which were then injected into the HTML.