go-siris / siris

DEPRECATED: The community driven fork of Iris. The fastest web framework for Golang!
Other
142 stars 16 forks source link

Provide better/faster template examples #30

Closed Allendar closed 7 years ago

Allendar commented 7 years ago

When I started with Iris last year I used the Django templates for quite some time. Later realizing how terribly slow these rendering engines are I moved to https://github.com/valyala/quicktemplate. But https://github.com/shiyanhui/hero also looks amazing.

Instead of providing examples that might be easy to start with it might be better to instantly set people on the right path and offer fast solutions.

On some Django templates I got speeds that just are as terrible as building a PHP website. Since I use QuickTemplate I get 38k+ req/s.

speedwheel commented 7 years ago

@allendar I have been also researching about this since I have been using the default go template and it's horrible slow.

Any examples how to integrate quicktemplate with Siris?

Allendar commented 7 years ago

Here's a raw example of how I'm doing my front-end:

layout.qtpl

{% import "espaldd.com/espal/translations" %}

{% interface
Page {
    Title()
    Menu()
    Content()
    Stylesheets()
    Javascripts()
}
%}

{% code
type BasePage struct {
    IsLoggedIn bool
    LocaleID uint16
    AdminUrl *string
}
%}

{% func PageTemplate(p Page) %}{% stripspace %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>{%= p.Title() %}</title>
        <link rel="stylesheet" href="/c/m.css">
        {%= p.Stylesheets() %}
    </head>
    <body>{% stripspace %}
        {%= p.Menu() %}
        {%= p.Content() %}
        {%= p.Javascripts() %}
    {% endstripspace %}</body>
</html>
{% endstripspace %}{% endfunc %}

{% func (p *BasePage) Title() %}{% endfunc %}
{% func (p *BasePage) Menu() %}{% stripspace %}
<header>
    <a href="/">{%s= translations.Fast(p.LocaleID, "home") %}</a>
    <a href="/catalog">{%s= translations.Fast(p.LocaleID, "catalog") %}</a>
    <a href="/forums">{%s= translations.FastPlural(p.LocaleID, "forum") %}</a>
    {% if !p.IsLoggedIn %}<a href="/login">{%s= translations.Fast(p.LocaleID, "login") %}</a>{% endif %}
    {% if p.IsLoggedIn %}<a href="/logout">{%s= translations.Fast(p.LocaleID, "logout") %}</a>{% endif %}
    {% if !p.IsLoggedIn %}<a href="/forgot-password">{%s= translations.Fast(p.LocaleID, "forgot_password") %}</a>{% endif %}
    {% if p.IsLoggedIn %}<a href="/{%s *p.AdminUrl %}">{%s= translations.Fast(p.LocaleID, "admin") %}</a>{% endif %}
</header>
{% endstripspace %}{% endfunc %}
{% func (p *BasePage) Content() %}{% endfunc %}
{% func (p *BasePage) Stylesheets() %}{% endfunc %}
{% func (p *BasePage) Javascripts() %}{% endfunc %}

login.qtpl

{% import "github.com/go-siris/siris/context" %}
{% import "espaldd.com/espal/validator" %}
{% import "espaldd.com/espal/translations" %}

{% code
type LoginPage struct {
    BasePage
    Context context.Context
    Form map[string]validator.FormFieldType
    FormErrors []string
}
%}

{% func (p *LoginPage) Stylesheets() %}<link rel="stylesheet" href="/c/simple-box.css">{% endfunc%}
{% func (p *LoginPage) Title() %}{%s= translations.Fast(p.LocaleID, "login") %}{% endfunc%}
{% func (p *LoginPage) Content() %}{% stripspace %}
<div class="simpleBox">
    {%= FormErrors(p.FormErrors) %}
    <h1>{%s= translations.Fast(p.LocaleID, "login") %}</h1>
    <form method="post" onsubmit="return s(this)">
        {%s= FormHiddenField(p.Form["_uname"]) %}
        {%s= FormHiddenField(p.Form["_t"]) %}
        {%s= FormEmailField(p.Form["email"]) %}<br>
        {%s= FormPasswordField(p.Form["password"]) %}<br>
        {%s= FormCheckboxField(p.Form["remember_me"]) %}<br><br>
        <input type="submit" value="{%s= translations.Fast(p.LocaleID, "login") %}">
    </form>
    <p><a href="/forgot-password"><small>{%s= translations.Fast(p.LocaleID, "forgot_your_password") %}</small></a></p>
    <p><a href="/register-account"><small>{%s= translations.Fast(p.LocaleID, "no_account_yet") %}</small></a></p>
</div>
{% endstripspace %}{% endfunc%}
{% func (p *LoginPage) Javascripts() %}<script src="/j/form.js"></script>{% endfunc%}

helpers.qtpl

{% import "espaldd.com/espal/validator" %}
{% import "espaldd.com/espal/database" %}

{% func FormErrors(errors []string) %}{% stripspace %}
{% if len(errors) > 0 %}
<ul class="errors">
    {% for _, err := range errors %}
    <li>{%s err %}</li>
    {% endfor %}
</ul>
{% endif %}
{% endstripspace %}{% endfunc %}

{% func FormHiddenField(t validator.FormFieldType) %}{% stripspace %}
<input type="hidden"
    name="{%s t.Name %}"
    placeholder="{%s t.Placeholder %}"
    value="{%s t.Value %}">
{% endstripspace %}{% endfunc %}

{% func FormTextField(t validator.FormFieldType) %}{% stripspace %}
<input type="text"
    name="{%s t.Name %}"
    placeholder="{%s t.Placeholder %}"
    value="{%s t.Value %}"
    {% if !t.Optional %}required{% endif %}>
{% endstripspace %}{% endfunc %}

{% func FormEmailField(t validator.FormFieldType) %}{% stripspace %}
<input type="email"
    name="{%s t.Name %}"
    placeholder="{%s t.Placeholder %}"
    value="{%s t.Value %}"
    {% if !t.Optional %}required{% endif %}>
{% endstripspace %}{% endfunc %}

{% func FormSearchField(t validator.FormFieldType) %}{% stripspace %}
<input type="search"
    name="{%s t.Name %}"
    placeholder="{%s t.Placeholder %}"
    value="{%s t.Value %}"
    {% if !t.Optional %}required{% endif %}>
{% endstripspace %}{% endfunc %}

{% func FormPasswordField(t validator.FormFieldType) %}{% stripspace %}
<input type="password"
    name="{%s t.Name %}"
    placeholder="{%s t.Placeholder %}"
    value="{%s t.Value %}"
    {% if !t.Optional %}required{% endif %}>
{% endstripspace %}{% endfunc %}

{% func FormCheckboxField(t validator.FormFieldType) %}{% stripspace %}
<input type="checkbox"
    name="{%s t.Name %}"
    value="{%s t.Value %}"> {%s t.Placeholder %}
{% endstripspace %}{% endfunc %}

{% func FormDateField(t validator.FormFieldType) %}{% stripspace %}
<input type="date"
    name="{%s t.Name %}"
    placeholder="{%s t.Placeholder %}{%s " " %}({% if !t.ExcludeYear %}YYYY{% endif %}{% if !t.ExcludeYear && !t.ExcludeMonth %}-{% endif %}{% if !t.ExcludeMonth %}MM{% endif %}{% if !t.ExcludeYear && !t.ExcludeMonth && !t.ExcludeDay %}-{% endif %}{% if !t.ExcludeDay %}DD{% endif %})"
    value="{%s t.Value %}"
    {% if !t.Optional %}required{% endif %}>
{% endstripspace %}{% endfunc %}

{% func CreatedBy(u *database.User) %}{% stripspace %}
    {% if nil != u.FirstName && nil != u.Surname %}
        {%s *u.FirstName + " " + *u.Surname %}
    {% elseif nil != u.FirstName %}
        {%s *u.FirstName %}
    {% elseif nil != u.Surname %}
        {%s *u.Surname %}
    {% else %}
        {%s u.Email %}
    {% endif %}
{% endstripspace %}{% endfunc %}

routeLogin.go (validator.NewFormValidator I wrote myself. Kind of works like Symfony's Form handler)

func routeLogin(c context.Context) {
    if userID, _ := c.Session().GetInt("user_id"); userID > 0 {
        c.Redirect("/")
        return
    }

    locale := c.Values().Get("locale").(languages.Language)

    form := validator.NewFormValidator(forms.NewLoginForm(locale.ID), locale)
    form.HandleFromRequest(c)
    if form.IsSubmitted() {
        if form.IsValid() {
            // LOGIC...
        }
    } else {
        if referer := c.Request().Referer(); "" != referer {
            // LOGIC...
        }
    }

    qtpls.WritePageTemplate(c, &qtpls.LoginPage{
        BasePage:   defaultBasePage(c),
        Context:    c,
        Form:       form.GenerateForm(),
        FormErrors: form.Errors(),
    })
}

helpers.go

func defaultBasePage(c context.Context) qtpls.BasePage {
    return qtpls.BasePage{
        IsLoggedIn: (nil != c.Values().Get("user")),
        LocaleID:   c.Values().Get("locale").(languages.Language).ID,
        AdminUrl:   &globals.AdminURL,
    }
}

It's a fast/stripped paste, so forgive me if there are broken parts :)

Dexus commented 7 years ago

I'll add some more template engines in the next weeks. Please stand by.

speedwheel commented 7 years ago

@allendar which is the part that makes it work with siris?

Allendar commented 7 years ago

You make a subpackage (e.a. qtpls), put your .qtpl files there and through qtpls.WritePageTemplate(c, ...) the QuickTemplate parser writes to Siris' context responseWriter. When you run qtc that comes with QuickTemplate it will Gogenerate the .qtpl templates into .go files that deliver a writing interface for super fast buffered output.

Dexus commented 7 years ago

@Allendar are you able to create a example for quicktemplate and create a PullRequest?

Allendar commented 7 years ago

@Dexus I'll submit a PullRequest somewhere in the weekend. Which example level should this be in? Or maybe make a new one called something like best_practice or optimal?

Dexus commented 7 years ago

Use examples/best_practice/views think that would match it.