برنامه نویسی ۲۹۹ بازدید

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

قالب چیست؟

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

قالب های Go

از همین مفهوم می‌توان برای تولید صفحه‌های وب در Go نیز استفاده کرد. قالب‌های وب به ما امکان می‌دهند که نتایج شخصی‌سازی‌شده در اختیار کاربران خود قرار دهیم. تولید قالب‌های وب با استفاده از الحاق رشته یک وظیفه دشوار است و ممکن است منجر به حملات تزریق نیز بشود.

قالب‌ها در Go

دو پکیج در Go جهت کار با قالب‌ها به شرح زیر وجود دارند:

  1. text/templates: برای تولید خروجی متنی استفاده می‌شود.
  2. html/templates: برای تولید خروجی HTML امن در برابر تزریق کد مورد استفاده قرار می‌گیرد.

هر دو پکیج فوق اینترفیس واحدی دارند که تفاوت‌های مختص وب ظریفی در زمینه تگ‌های اسکریپت انکودینگ برای جلوگیری از اجرا شدن، تحلیل نقشه‌ها به صورت JSON در View و موارد دیگر دارند.

نخستین قالب

<h1>Hello, {{.Name}}</h1>
<p>
  Here is your discount coupon worth {{.Amount}}:
  <span>{{.Coupon}}</span>
<p>
  1. بسط یک فایل قالب می‌تواند هر چیزی باشد. ما از gohtml. استفاده می‌کنیم چون شامل ابزارهای توسعه پشتیبانی شده روی IDE-های مختلف است.
  2. اکشن ها یعنی ارزیابی‌های داده یا ساختارهای کنترل درون {{ و }} قرار دارند. ارزیابی داده‌ها درون آن به نام pipeline نامیده می‌شود. هر چیزی خارج از آن‌ها به صورت بدون تغییر به خروجی ارسال می‌شود.
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	tpl, err := template.ParseFiles("index.gohtml")
	if err != nil {
		log.Fatalln(err)
	}
	type User struct {
		Name   string
		Coupon string
		Amount int64
	}
	user := User{
		Name:   "Rick",
		Coupon: "IAMAWESOMEGOPHER",
		Amount: 5000,
	}
	err = tpl.Execute(os.Stdout, user)
	if err != nil {
		panic(err)
	}
}

شما می‌تواند چندین فایل را در خط 10 به صورت رشته‌های جدا شده با کاما (مسیر فایل‌ها) تحلیل کنید یا این که می‌توانید همه فایل‌های درون یک دایرکتوری را تحلیل کنید. به مثال زیر توجه کنید:

tpl, err:= template.ParseFiles(“index1.gohtml”, “index2.gohtml”)
tpl, err:= template.ParseGlob("views/templates/*")
  1. این کد یک کانتینر قالب (که در اینجا tpl نامیده شده است) و یک خطا بازگشت می‌دهد. ما می‌توانیم از داده‌های خود برای اجرای tpl به وسیله اجرای متد استفاده کنیم. در حالت وجود چند قالب، نام قالب به عنوان دومین آرگومان و داده‌ها به عنوان آرگومان سوم ارسال می‌شوند.
  2. ما ساختمان داده خود را (در اینجا struct است) تعریف می‌کنیم. این ساختمان داده می‌تواند هر چیزی در GO مانند slice، map، struct، slice-of-structs، و structs-of-slice of structs باشد. همه این موارد را در ادامه بررسی می‌کنیم.
  3. داده‌ها با استفاده از نقطه واکشی می‌شوند. از آن برای دسترسی به متغیرهای داده درون قالب‌ها استفاده می‌کنیم. به خاطر داشته باشید که شناساگرهای درون داده‌های ارسالی باید با حرف بزرگ آغاز شوند. ما می‌توانیم از آن‌ها برای مقداردهی یک متغیر درون اکشن‌هایی مانند myCoupon:=.Coupon$ استفاده کنیم.
  4. می‌توانیم نتیجه اجرای صفحه وب یا خروجی استاندارد را در خروجی ارائه کنیم، زیرا قالب، متدهایی اجرا می‌کند که هر مقداری که اینترفیس با نوع Writer را پیاده‌سازی کند، می‌پذیرد.

این قالب به صورت متنی ساده در خروجی کنسول رندر می‌شود؛ اما اگر از آن برای ارسال داده‌ها به صورت پاسخی به یک درخواست وب استفاده کنیم، به صورت یک صفحه وب رندر می‌شود و بدین ترتیب می‌توانیم در آن از تگ‌های HTML استفاده کنیم.

قالب های Go

ترجیح بر این است که کار تحلیلی درون تابع init انجام یابد و اجرا در مکان‌های لازم صورت گیرد.

func init() {
  // Must is a helper that wraps a function returning  
  // (*Template, error) and panics if the error is non-nil.
  tpl = template.Must(template.ParseGlob(“templates/*”))
}

استفاده از ساختمان‌های داده مختلف در قالب‌های وب

در این بخش به توضیح روش استفاده از ساختمان‌های داده مختلف زبان برنامه‌نویسی Go در قالب‌های وب می‌پردازیم.

Slice یا آرایه

یک Slice از رشته‌ها را در نظر بگیرید:

func main() {
	sages := []string{"Tom Cruise", "Jack Nicholson", "Demi Moore", "Kevin Bacon", "Wolfgang Bodison"}
	err := tpl.Execute(w, sages)
	if err != nil {
		log.Fatalln(err)
	}
}

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>A few good men</title>
</head>
<body>
<ul>
    {{range $index, $element := .}}
    <li>{{$index}} - {{$element}}</li>
    {{end}}
</ul>
</body>
</html>

قالب های Go

Map

به مثالی از ساختمان داده Map توجه کنید.

func main() {
	superheroes := map[string]string{
		"Lt. Daniel Kaffee":        "Tom Cruise",
		"Col. Nathan R. Jessep":    "Jack Nicholson",
		"Lt. Cdr. JoAnne Galloway": "Demi Moore",
		"Capt. Jack Ross": "Kevin Bacon",
		"Lance Cpl. Harold W. Dawson": "Wolfgang Bodison",
	}
	err := tpl.Execute(w, superheroes)
	if err != nil {
		log.Fatalln(err)
	}
}

مقدار pipeline درون Actions یک Map است. اگر هیچ جفت کلید/مقدار در map وجود نداشته باشد، هیچ چیز در خروجی ارائه نمی‌شود، در غیر این صورت از نقطه برای عناصر متوالی map استفاده می‌شود و قالب بدین‌صورت اجرا می‌شود که همه کلیدها به صورت ‎$key‎ و همه مقادیر متناظر به صورت ‎$val در نظر گرفته می‌شوند. اگر کلیدها از نوع ابتدایی با ترتیب تعریف شده (comparable) باشند، عناصر در ترتیب کلید مرتب‌شده بازدید می‌شوند.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Peeps</title>
</head>
<body>
<ul>
    {{range $key, $val := .}}
    <li>{{$key}} -  {{$val}}</li>
    {{end}}
</ul>
</body>
</html>

قالب های Go

Struct

در این بخش مثالی از یک Struct با مجموعه‌ای از فیلدها که به صورت inline اعلان شده‌اند را بررسی می‌کنیم:

func main() {
	superhero := struct {
		Name  string
		Motto string
	}{
		Name:  "Bruise Wayne",
		Motto: "I am batman",
	}
	err := tpl.Execute(w, superhero)
	if err != nil {
		log.Fatalln(err)
	}
}

ابتدا یک نقطه و سپس نام فیلد struct داده به صورت Name. به عنوان آرگومان قالب استفاده می‌شود. نتیجه مقدار فیلد است. فراخوانی فیلدها را می‌توان به صورت Field1.Field2. به هم زنجیر کرد. فیلدها می‌توانند روی متغیرها ارزیابی شوند که شامل زنجیره‌های ‎$x.Field1.Field2 است. قالب زیر فیلدهای ذخیره شده در متغیرها را نشان می‌دهد و سپس در Actions ارزیابی می‌شود:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Peeps</title>
</head>
<body>
<ul>
    {{$x := .Name}}
    {{$y := .Motto}}
    <li>{{$x}} -  {{$y}}</li>
</ul>
</body>
</html>

قالب های GoSlice of Structs

در این بخش به بررسی مثالی از قالب با استفاده از ساختمان داده اسلایس struct می‌پردازیم:

func main() {
	im := superhero{
		Name:  "Iron man",
		Motto: "I am iron man",
	}
	ca := superhero{
		Name:  "Captain America",
		Motto: "Avengers assemble",
	}
	ds := superhero{
		Name:  "Doctor Strange",
		Motto: "I see things",
	}
	superheroes := []superhero{im, ca, ds}
	err := tpl.Execute(w, superheroes)
	if err != nil {
		log.Fatalln(err)
	}
}

ما از یک بازه استفاده کرده‌ایم تا روی یک اسلایس حلقه‌ای تعریف کنیم. مقدار pipeline در Action باید یک آرایه (یا اسلایس) باشد. اگر مقدار اسلایس صفر باشد، هیچ خروجی ارائه نمی‌شود، اما در غیر این صورت از نقطه برای عناصر متوالی آرایه یا اسلایس استفاده می‌شود و قالب اجرا خواهد شد.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Peeps</title>
</head>
<body>
<ul>
    {{range .}}
    <li>{{.Name}} - {{.Motto}}</li>
    {{end}}
</ul>
</body>
</html>

قالب های Go

ساختمان‌های داده مختلف در Go می‌توانند با هم ترکیب نیز بشوند تا قالب‌های مفیدی ایجاد کنند.

نکته‌ای در مورد استفاده از گزاره‌های شرطی روی قالب‌ها

امکان نوشتن قالب‌های شرطی مانند زیر نیز وجود دارد:

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}

بدین ترتیب تواناهایی شگفت‌انگیزی برای تولید محتوای دینامیک پیدا می‌کنیم. اگر مقدار pipeline خالی (یعنی flase، 0 یا هر اشاره‌گر تهی یا مقدار اینترفیس و همچنین هر آرایه، اسلایس، نقشه یا رشته با طول صفر) باشد، T0 اجرا می‌شود و در غیر این صورت T1 اجرا خواهد شد.

تابع در قالب‌ها

می‌توان از هر تابعی در یک قالب Go استفاده کرد؛ به شرط این که تعریف شده باشد. به صورت پیش‌فرض هیچ تابعی در قالب تعریف نشده است، اما متد Funcs روی یک قالب می‌تواند برای اضافه کردن تابع از طریق ایجاد نگاشت‌هایی مانند زیر استفاده شود:

func monthDayYear(t time.Time) string {
	return t.Format("January 2, 2006")
}

func homeFunc(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	var fm = template.FuncMap{
		"fdateMDY": monthDayYear,
	}
	tpl := template.Must(template.New("").Funcs(fm).ParseFiles("index.gohtml"))
	if err := tpl.ExecuteTemplate(w, "index.gohtml", time.Now()); err != nil {
		log.Fatalln(err)
	}
}

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

type FuncMap map[string]interface{}

در خط 7 کد فوق ما یک نگاشت از تابع monthDayYear به یک نام fdateMDY تعریف کرده‌ایم. اینک این تابع می‌تواند درون قالب استفاده شود. به خاطر داشته باشید که نقطه (.) داده‌های ارائه شده به قالب را نگهداری می‌کند.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>functions</title>
</head>
<body>
    <b>Without date formatting: {{.}}</b>
        <hr>
     <b>With date formatting: {{fdateMDY .}}</b>
</body>
</html>

روش های مختلفی برای استفاده از قالب‌ها در Go وجود دارند. در این نوشته با برخی از این روش‌ها آشنا شدیم.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

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