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

اعمال و توابع قالب

در این بخش، ما قصد داریم به اعمال و توابع قالب که Go ارائه می‌دهد نگاه کنیم.

ما قبلاً در مورد برخی از اعمال صحبت کرده‌ایم — {{define}}، {{template}} و {{block}} — اما سه مورد دیگر وجود دارد که می‌توانید برای کنترل نمایش داده‌های پویا استفاده کنید — {{if}}، {{with}} و {{range}}.

Action Description
{{if .Foo}} C1 {{else}} C2 {{end}} اگر .Foo خالی نباشد، محتوای C1 را رندر کنید، در غیر این صورت محتوای C2 را رندر کنید.
{{with .Foo}} C1 {{else}} C2 {{end}} اگر .Foo خالی نباشد، سپس dot را به مقدار .Foo تنظیم کنید و محتوای C1 را رندر کنید، در غیر این صورت محتوای C2 را رندر کنید.
{{range .Foo}} C1 {{else}} C2 {{end}} اگر طول .Foo بزرگتر از صفر باشد، سپس روی هر عنصر حلقه بزنید، dot را به مقدار هر عنصر تنظیم کنید و محتوای C1 را رندر کنید. اگر طول .Foo صفر باشد، محتوای C2 را رندر کنید. نوع زیرین .Foo باید یک آرایه، slice، map یا channel باشد.

چند نکته در مورد این اعمال برای اشاره وجود دارد:

پکیج html/template همچنین برخی توابع قالب ارائه می‌دهد که می‌توانید از آن‌ها برای افزودن منطق اضافی به قالب‌های خود و کنترل آنچه در زمان اجرا (runtime) رندر می‌شود استفاده کنید. می‌توانید یک فهرست کامل از توابع را اینجا پیدا کنید، اما مهم‌ترین آن‌ها عبارتند از:

Function Description
{{eq .Foo .Bar}} true را yield می‌کند اگر .Foo برابر با .Bar باشد
{{ne .Foo .Bar}} true را yield می‌کند اگر .Foo برابر با .Bar نباشد
{{not .Foo}} نفی بولی (boolean negation) .Foo را yield می‌کند
{{or .Foo .Bar}} .Foo را yield می‌کند اگر .Foo خالی نباشد؛ در غیر این صورت .Bar را yield می‌کند
{{index .Foo i}} مقدار .Foo در ایندکس i را yield می‌کند. نوع زیرین .Foo باید یک map، slice یا آرایه باشد، و i باید یک مقدار عدد صحیح باشد.
{{printf "%s-%s" .Foo .Bar}} یک رشته فرمت شده حاوی مقادیر .Foo و .Bar را yield می‌کند. به همان روش fmt.Sprintf() کار می‌کند.
{{len .Foo}} طول .Foo را به عنوان یک عدد صحیح yield می‌کند.
{{$bar := len .Foo}} طول .Foo را به متغیر قالب $bar اختصاص می‌دهد

ردیف آخر مثالی از اعلان یک متغیر قالب است. متغیرهای قالب به خصوص مفید هستند اگر می‌خواهید نتیجه از یک تابع را ذخیره کنید و در چندین مکان در قالب خود استفاده کنید. نام‌های متغیر باید با علامت دلار پیشوند شوند و فقط می‌توانند شامل کاراکترهای الفبایی-عددی باشند.

استفاده از عمل with

یک فرصت خوب برای استفاده از عمل (action) {{with}} فایل view.tmpl است که در فصل قبل ایجاد کردیم. ادامه دهید و آن را مانند این به‌روزرسانی کنید:

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'>
            <time>Created: {{.Created}}</time>
            <time>Expires: {{.Expires}}</time>
        </div>
    </div>
    {{end}}
{{end}}

پس اکنون، بین {{with .Snippet}} و تگ مربوطه {{end}}، مقدار dot به .Snippet تنظیم می‌شود. Dot اساساً به ساختار models.Snippet به جای ساختار والد templateData تبدیل می‌شود.

استفاده از اعمال if و range

بیایید همچنین از اعمال {{if}} و {{range}} در یک مثال ملموس استفاده کنیم و صفحه اصلی خود را برای نمایش یک جدول از آخرین snippetها به‌روزرسانی کنیم، کمی شبیه این:

05.02-01.png

ابتدا ساختار templateData را به‌روزرسانی کنید تا یک فیلد Snippets برای نگه‌داری یک slice از snippetها داشته باشد، مانند این:

File: cmd/web/templates.go
package main

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

// Include a Snippets field in the templateData struct.
type templateData struct {
    Snippet  models.Snippet
    Snippets []models.Snippet
}

سپس تابع handler home را به‌روزرسانی کنید تا آخرین snippetها را از مدل پایگاه داده ما واکشی کند و آن‌ها را به قالب home.tmpl ارسال کند:

File: cmd/web/handlers.go
package main

...

func (app *application) home(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Server", "Go")
    
    snippets, err := app.snippets.Latest()
    if err != nil {
        app.serverError(w, r, err)
        return
    }

    files := []string{
        "./ui/html/base.tmpl",
        "./ui/html/partials/nav.tmpl",
        "./ui/html/pages/home.tmpl",
    }

    ts, err := template.ParseFiles(files...)
    if err != nil {
        app.serverError(w, r, err)
        return
    }

    // Create an instance of a templateData struct holding the slice of
    // snippets.
    data := templateData{
        Snippets: snippets,
    }

    // Pass in the templateData struct when executing the template.
    err = ts.ExecuteTemplate(w, "base", data)
    if err != nil {
        app.serverError(w, r, err)
    }
}

...

حالا بیایید به فایل ui/html/pages/home.tmpl برویم و آن را برای نمایش این snippetها در یک جدول با استفاده از اعمال {{if}} و {{range}} به‌روزرسانی کنیم. به طور خاص:

این نشانه‌گذاری است:

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>
            <td>{{.Created}}</td>
            <td>#{{.ID}}</td>
        </tr>
        {{end}}
    </table>
    {{else}}
        <p>There's nothing to see here... yet!</p>
    {{end}}
{{end}}

مطمئن شوید که همه فایل‌های شما ذخیره شده‌اند، برنامه را مجدداً راه‌اندازی کنید و به http://localhost:4000 در یک مرورگر وب بروید. اگر همه چیز طبق برنامه پیش رفته باشد، باید صفحه‌ای ببینید که کمی شبیه این است:

05.02-02.png

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

ترکیب توابع

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

به عنوان مثال، تگ زیر محتوای C1 را رندر می‌کند اگر طول Foo بزرگتر از 99 باشد:

{{if (gt (len .Foo) 99)}} C1 {{end}}

یا به عنوان مثال دیگر، تگ زیر محتوای C1 را رندر می‌کند اگر .Foo برابر با 1 باشد و .Bar کمتر یا مساوی 20 باشد:

{{if (and (eq .Foo 1) (le .Bar 20))}} C1 {{end}}

کنترل رفتار حلقه

درون یک عمل {{range}} می‌توانید از دستور {{break}} برای پایان زودهنگام حلقه، و {{continue}} برای شروع فوری تکرار بعدی حلقه استفاده کنید.

{{range .Foo}}
    // Skip this iteration if the .ID value equals 99.
    {{if eq .ID 99}}
        {{continue}}
    {{end}}
    // ...
{{end}}
{{range .Foo}}
    // End the loop if the .ID value equals 99.
    {{if eq .ID 99}}
        {{break}}
    {{end}}
    // ...
{{end}}