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

توابع سفارشی قالب (Custom Template Functions)

در این فصل، می‌خواهیم در مورد توابع سفارشی قالب (Custom Template Functions) صحبت کنیم. این توابع به شما اجازه می‌دهند تا منطق سفارشی (Custom Logic) را در قالب‌های خود اجرا کنید.

به عنوان مثال، بیایید یک تابع کمکی (Helper Function) ایجاد کنیم که یک رشته زمانی (Time String) را به عنوان ورودی می‌گیرد و آن را به قالب انسان‌خوان (Human-Readable Format) تبدیل می‌کند.

در آخرین بخش از این مبحث درباره قالب‌ها و داده‌های پویا، می‌خواهم توضیح دهم که چگونه می‌توانید توابع سفارشی خود را برای استفاده در قالب‌های 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.letsgofa.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>

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

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

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

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

واژه‌نامه اصطلاحات فنی

اصطلاح فارسی معادل انگلیسی توضیح
توابع سفارشی قالب Custom Template Functions توابعی که برای پردازش داده‌ها در قالب‌ها تعریف می‌شوند
منطق سفارشی Custom Logic پیاده‌سازی خاص برای پردازش داده‌ها
تابع کمکی Helper Function تابعی که عملیات رایج را ساده می‌کند
رشته زمانی Time String متنی که نشان‌دهنده تاریخ و زمان است
قالب انسان‌خوان Human-Readable Format نمایش داده به شکلی که برای انسان قابل درک باشد
نقشه توابع Function Map مجموعه‌ای از توابع قابل استفاده در قالب
تجزیه زمان Time Parsing تبدیل متن به مقدار زمانی
قالب‌بندی خروجی Output Formatting تنظیم شکل نمایش داده‌ها
مدیریت خطا Error Handling رسیدگی به خطاهای احتمالی در پردازش
تابع بازگشتی Return Function تابعی که مقداری را برمی‌گرداند