Let's Go قالب‌های HTML پویا › توابع سفارشی قالب
قبلی · فهرست · بعدی
فصل ۵.۶.

توابع سفارشی قالب

در آخرین بخش این قسمت درباره قالب‌سازی و داده‌های پویا، می‌خواهم توضیح دهم که چگونه توابع سفارشی خود را برای استفاده در قالب‌های Go ایجاد کنید.

برای نشان دادن این موضوع، بیایید یک تابع سفارشی humanDate() ایجاد کنیم که تاریخ‌ها و زمان‌ها را در یک فرمت 'انسانی' زیبا مانند 1 Jan 2024 at 10:47 یا 18 Mar 2024 at 15:04 خروجی می‌دهد، به جای خروجی دادن تاریخ‌ها در فرمت پیش‌فرض YYYY-MM-DD HH:MM:SS +0000 UTC که در حال حاضر استفاده می‌کنیم.

دو مرحله اصلی برای انجام این کار وجود دارد:

  1. باید یک شیء template.FuncMap حاوی تابع سفارشی humanDate() ایجاد کنیم.

  2. باید از متد template.Funcs() برای ثبت این تابع قبل از تجزیه قالب‌ها استفاده کنیم.

بروید و کد زیر را به فایل templates.go خود اضافه کنید:

File: cmd/web/templates.go
package main

import (
    "html/template"
    "path/filepath"
    "time" // New import

    "snippetbox.alexedwards.net/internal/models"
)

...

// Create a humanDate function which returns a nicely formatted string
// representation of a time.Time object.
func humanDate(t time.Time) string {
    return t.Format("02 Jan 2006 at 15:04")
}

// Initialize a template.FuncMap object and store it in a global variable. This is
// essentially a string-keyed map which acts as a lookup between the names of our
// custom template functions and the functions themselves.
var functions = template.FuncMap{
    "humanDate": humanDate,
}

func newTemplateCache() (map[string]*template.Template, error) {
    cache := map[string]*template.Template{}

    pages, err := filepath.Glob("./ui/html/pages/*.tmpl")
    if err != nil {
        return nil, err
    }

    for _, page := range pages {
        name := filepath.Base(page)

        // The template.FuncMap must be registered with the template set before you
        // call the ParseFiles() method. This means we have to use template.New() to
        // create an empty template set, use the Funcs() method to register the
        // template.FuncMap, and then parse the file as normal.
        ts, err := template.New(name).Funcs(functions).ParseFiles("./ui/html/base.tmpl")
        if err != nil {
            return nil, err
        }

        ts, err = ts.ParseGlob("./ui/html/partials/*.tmpl")
        if err != nil {
            return nil, err
        }

        ts, err = ts.ParseFiles(page)
        if err != nil {
            return nil, err
        }

        cache[name] = ts
    }

    return cache, nil
}

قبل از ادامه، باید توضیح دهم: توابع سفارشی قالب (مانند تابع humanDate() ما) می‌توانند به تعداد مورد نیاز پارامتر بپذیرند، اما باید فقط یک مقدار برگردانند. تنها استثنا این است که اگر می‌خواهید یک خطا را به عنوان مقدار دوم برگردانید، در این صورت نیز مشکلی نیست.

حالا می‌توانیم از تابع humanDate() خود به همان روش توابع قالب داخلی استفاده کنیم:

File: ui/html/pages/home.tmpl
{{define "title"}}Home{{end}}

{{define "main"}}
    <h2>Latest Snippets</h2>
    {{if .Snippets}}
     <table>
        <tr>
            <th>Title</th>
            <th>Created</th>
            <th>ID</th>
        </tr>
        {{range .Snippets}}
        <tr>
            <td><a href='/snippet/view/{{.ID}}'>{{.Title}}</a></td>
            <!-- Use the new template function here -->
            <td>{{humanDate .Created}}</td>
            <td>#{{.ID}}</td>
        </tr>
        {{end}}
    </table>
    {{else}}
        <p>There's nothing to see here... yet!</p>
    {{end}}
{{end}}
File: ui/html/pages/view.tmpl
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}}

{{define "main"}}
    {{with .Snippet}}
    <div class='snippet'>
        <div class='metadata'>
            <strong>{{.Title}}</strong>
            <span>#{{.ID}}</span>
        </div>
        <pre><code>{{.Content}}</code></pre>
        <div class='metadata'>
            <!-- Use the new template function here -->
            <time>Created: {{humanDate .Created}}</time>
            <time>Expires: {{humanDate .Expires}}</time>
        </div>
    </div>
    {{end}}
{{end}}

پس از انجام این کار، برنامه را مجدداً راه‌اندازی کنید. اگر به http://localhost:4000 و http://localhost:4000/snippet/view/1 در مرورگر خود مراجعه کنید، باید ببینید که تاریخ‌های جدید و به خوبی فرمت شده استفاده می‌شوند.

05.06-01.png
05.06-02.png

اطلاعات اضافی

خط لوله

در کد بالا، تابع قالب سفارشی خود را به این صورت فراخوانی کردیم:

<time>Created: {{humanDate .Created}}</time>

یک رویکرد جایگزین، استفاده از کاراکتر | برای خط لوله کردن مقادیر به یک تابع است. این کار کمی شبیه خط لوله کردن خروجی‌ها از یک دستور به دستور دیگر در ترمینال‌های Unix است. می‌توانیم کد بالا را به این صورت بازنویسی کنیم:

<time>Created: {{.Created | humanDate}}</time>

یک ویژگی خوب خط لوله این است که می‌توانید یک زنجیره به طور دلخواه طولانی از توابع قالب ایجاد کنید که خروجی یکی را به عنوان ورودی بعدی استفاده می‌کند. به عنوان مثال، می‌توانیم خروجی تابع humanDate خود را به تابع داخلی printf خط لوله کنیم، به این صورت:

<time>{{.Created | humanDate | printf "Created: %s"}}</time>