Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide some means to deduplicate the base template #20

Open
ssokolow opened this issue May 6, 2022 · 6 comments
Open

Provide some means to deduplicate the base template #20

ssokolow opened this issue May 6, 2022 · 6 comments

Comments

@ssokolow
Copy link

ssokolow commented May 6, 2022

I currently have to manually include something like this in every top-level template I write and it'd be really nice if I could deduplicate it.

        @markup::doctype()
        html[lang="en", class=dark_mode.then(|| "dark_mode")] {
            @Header { title: &format!("Error {status_code}"), robots_meta, build_timestamp }

            body {
                #container {
                    // REST OF THE TEMPLATE HERE
                }
                @JsBundle { build_timestamp }
            }
        }

I tries playing around with the where clauses and the Rendertrait and making a pair ofFooandFooInner` templates, where the inner template is effectively what an AJAX response would return, but gave up as the need to manually undo the reference-taking for the arguments caused by the outer template calling the inner one made the inner template very cluttered and ugly.

In a more HTML-styled template language, this'd typically be solved either by "template inheritance", which looks like this in Django/Jinja/Twig-style templates...

base.tmpl:

<!DOCTYPE html>
<html lang="en">
    ...
    <body>
        <div id="container">
            {% block content %}ERROR: No Content{% endblock %}
        </div>
    </body>
</html>

specific.tmpl:

{% extends "base.tmpl" %}

{% block content %}
page body goes here
{% endblock %}

...or by abusing the template language's dynamic scoping and runtime evaluation to render "specific.tmpl" by rendering "base.tmpl" and passing in a string argument which is used as the path to include!, relying on how various template languages default to exposing the the template arguments as global variables to what you include!.

@utkarshkukreti
Copy link
Owner

Hi,

This is the pattern I use in my own websites:

markup::define! {
    Layout<Content: markup::Render>(content: Content, dark_mode: bool) {
        @markup::doctype()
        html[lang="en", class=dark_mode.then(|| "dark_mode")] {
            body {
                #container {
                    @content
                }
            }
        }
    }
}

// Use

@Layout {
    content: markup::new! {
        div {}
    },
    dark_mode: true,
}

If there are more than a couple of fields that need to be passed to the Layout, e.g. <title>, I create a struct with all the settings and pass that in.

Does this help?

@ssokolow
Copy link
Author

ssokolow commented May 6, 2022

I hadn't tried using markup::new! and that does make it typecheck.

I can't say whether it works yet since I only just finished transcribing the templates over and haven't yet tried switching the actix-web routes over to calling the markup.rs versions.

EDIT: (Upon discovering that the docs for markup::define! were empty, there was no syntax reference in the current README, and finding 962fe14, I more or less concluded this was one of those "the rustdocs are useless" macro crates and habitually exclusively used that syntax reference in the now-deleted version of the README for documentation.)

EDIT: ...and I needn't have explained that thoroughly. The rustdocs for markup::new are empty too, so the rustdocs wouldn't have helped me understand why it exists anyway.

A strange situation I find myself in when I'll probably recommend Sailfish over markdown.rs, not on performance or security considerations, but because it would reflect terribly on me to have to say "Here. The docs are in a deleted version of the README from an old commit."

@utkarshkukreti
Copy link
Owner

Sorry about the state of the docs. I'm thinking of just cleaning up the deleted parts of the README and putting it back.

Also to be clear, markup::new! is not strictly required here. You can also do:

markup::define! {
    Layout<Content: markup::Render>(content: Content, dark_mode: bool) {
        @markup::doctype()
        html[lang="en", class=dark_mode.then(|| "dark_mode")] {
            head {
                title { @title }
            }
            body {
                #container {
                    @content
                }
            }
        }
    }

    Index(message: &'static str) {
        h1 { @message }
    }
}

// Use

@Layout {
    content: Index { message: "hello" },
    dark_mode: true,
}

@ssokolow
Copy link
Author

ssokolow commented May 6, 2022

That looks like what I tried, and wound up throwing away after getting errors like this unless I made my templates horrendously ugly:

error[E0277]: can't compare `&&SortMode` with `SortMode`
   --> src/templates.rs:120:27
    |
120 |             @if sort_mode == SortMode::AtoZ {
    |                           ^^ no implementation for `&&SortMode == SortMode`
    |
    = help: the trait `PartialEq<SortMode>` is not implemented for `&&SortMode`

EDIT: By horrendously ugly, I mean turning pretty much every variable access from a simple @foo to something more like { *foo }.

@ZaneHannanAU
Copy link

Alternative is matching, but personally I'd be using a direct iterator here:

markup::define! {
  List<T: markup::Render>(items: impl IntoIterator<Item = T>) {
    @for item in items {
      @item
    }
  }
}

Mostly since IntoIterator is more general here, and is returned with a DoubleEndedIterator + Iterator, among others.

Though I personally put sorting differently, as a list of groups which get sorted among themselves... but that's another talk.

@ssokolow
Copy link
Author

ssokolow commented May 6, 2022

That particular example is part of an ugly rapid-protoype that I recently implemented, and will probably be turning into a match once I finish porting the templates, but other examples included turning ThingInner { foo, bar, baz } into something along the lines of ThingInner { foo: { *foo }, bar: { *bar ), baz: { *baz } } being the least ugly way to make things typecheck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants