یادگیری و استفاده از قالب های Go – از صفر تا صد
برخی اپلیکیشنهای Go در سمت فرانتاند نیازمند اجرای اپلیکیشنهای با امکانات کامل نیستند. اغلب این اپلیکیشنها محتوای استاتیک عرضه میکنند و به ندرت با مقادیر دینامیک سر و کار دارند. برای نمونه فید رسانه اجتماعی خود را در نظر بگیرید. هر پست از نوع خاص شبیه هم به نظر میرسد، اما با دادههای خاصی برای هر کاربر مقداردهی میشود. در واقع در اینجا از نوعی قالببندی استفاده شده است. در این مقاله به بررسی قالب های Go می پردازیم.
قالب چیست؟
قالبها اساساً فایلهای متنی هستند که برای ایجاد محتوای دینامیک مورد استفاده قرار میگیرند. برای نمونه تابع جاوا اسکریپت زیر name را به عنوان یک آرگومان میگیرد و رشتههای مختلفی تولید میکند.
از همین مفهوم میتوان برای تولید صفحههای وب در Go نیز استفاده کرد. قالبهای وب به ما امکان میدهند که نتایج شخصیسازیشده در اختیار کاربران خود قرار دهیم. تولید قالبهای وب با استفاده از الحاق رشته یک وظیفه دشوار است و ممکن است منجر به حملات تزریق نیز بشود.
قالبها در Go
دو پکیج در Go جهت کار با قالبها به شرح زیر وجود دارند:
- text/templates: برای تولید خروجی متنی استفاده میشود.
- html/templates: برای تولید خروجی HTML امن در برابر تزریق کد مورد استفاده قرار میگیرد.
هر دو پکیج فوق اینترفیس واحدی دارند که تفاوتهای مختص وب ظریفی در زمینه تگهای اسکریپت انکودینگ برای جلوگیری از اجرا شدن، تحلیل نقشهها به صورت JSON در View و موارد دیگر دارند.
نخستین قالب
1<h1>Hello, {{.Name}}</h1>
2<p>
3 Here is your discount coupon worth {{.Amount}}:
4 <span>{{.Coupon}}</span>
5<p>
- بسط یک فایل قالب میتواند هر چیزی باشد. ما از gohtml. استفاده میکنیم چون شامل ابزارهای توسعه پشتیبانی شده روی IDE-های مختلف است.
- اکشن ها یعنی ارزیابیهای داده یا ساختارهای کنترل درون {{ و }} قرار دارند. ارزیابی دادهها درون آن به نام pipeline نامیده میشود. هر چیزی خارج از آنها به صورت بدون تغییر به خروجی ارسال میشود.
1package main
2
3import (
4 "log"
5 "os"
6 "text/template"
7)
8
9func main() {
10 tpl, err := template.ParseFiles("index.gohtml")
11 if err != nil {
12 log.Fatalln(err)
13 }
14 type User struct {
15 Name string
16 Coupon string
17 Amount int64
18 }
19 user := User{
20 Name: "Rick",
21 Coupon: "IAMAWESOMEGOPHER",
22 Amount: 5000,
23 }
24 err = tpl.Execute(os.Stdout, user)
25 if err != nil {
26 panic(err)
27 }
28}
شما میتواند چندین فایل را در خط 10 به صورت رشتههای جدا شده با کاما (مسیر فایلها) تحلیل کنید یا این که میتوانید همه فایلهای درون یک دایرکتوری را تحلیل کنید. به مثال زیر توجه کنید:
tpl, err:= template.ParseFiles(“index1.gohtml”, “index2.gohtml”) tpl, err:= template.ParseGlob("views/templates/*")
- این کد یک کانتینر قالب (که در اینجا tpl نامیده شده است) و یک خطا بازگشت میدهد. ما میتوانیم از دادههای خود برای اجرای tpl به وسیله اجرای متد استفاده کنیم. در حالت وجود چند قالب، نام قالب به عنوان دومین آرگومان و دادهها به عنوان آرگومان سوم ارسال میشوند.
- ما ساختمان داده خود را (در اینجا struct است) تعریف میکنیم. این ساختمان داده میتواند هر چیزی در GO مانند slice، map، struct، slice-of-structs، و structs-of-slice of structs باشد. همه این موارد را در ادامه بررسی میکنیم.
- دادهها با استفاده از نقطه واکشی میشوند. از آن برای دسترسی به متغیرهای داده درون قالبها استفاده میکنیم. به خاطر داشته باشید که شناساگرهای درون دادههای ارسالی باید با حرف بزرگ آغاز شوند. ما میتوانیم از آنها برای مقداردهی یک متغیر درون اکشنهایی مانند myCoupon:=.Coupon$ استفاده کنیم.
- میتوانیم نتیجه اجرای صفحه وب یا خروجی استاندارد را در خروجی ارائه کنیم، زیرا قالب، متدهایی اجرا میکند که هر مقداری که اینترفیس با نوع Writer را پیادهسازی کند، میپذیرد.
این قالب به صورت متنی ساده در خروجی کنسول رندر میشود؛ اما اگر از آن برای ارسال دادهها به صورت پاسخی به یک درخواست وب استفاده کنیم، به صورت یک صفحه وب رندر میشود و بدین ترتیب میتوانیم در آن از تگهای HTML استفاده کنیم.
ترجیح بر این است که کار تحلیلی درون تابع init انجام یابد و اجرا در مکانهای لازم صورت گیرد.
1func init() {
2 // Must is a helper that wraps a function returning
3 // (*Template, error) and panics if the error is non-nil.
4 tpl = template.Must(template.ParseGlob(“templates/*”))
5}
استفاده از ساختمانهای داده مختلف در قالبهای وب
در این بخش به توضیح روش استفاده از ساختمانهای داده مختلف زبان برنامهنویسی Go در قالبهای وب میپردازیم.
Slice یا آرایه
یک Slice از رشتهها را در نظر بگیرید:
1func main() {
2 sages := []string{"Tom Cruise", "Jack Nicholson", "Demi Moore", "Kevin Bacon", "Wolfgang Bodison"}
3 err := tpl.Execute(w, sages)
4 if err != nil {
5 log.Fatalln(err)
6 }
7}
این کد میتواند در قالب با تعریف بازهای روی اسلایس (و همچنین آرایه) درون اکشنها استفاده شود. اگر مقدار pipeline دارای طول صفر باشد، چیزی اجرا نخواهد شد، در غیر این صورت از نقطه روی عناصر متوالی اسلایس (و همچنین آرایه) استفاده میشود و قالب اجرا خواهد شد.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>A few good men</title>
6</head>
7<body>
8<ul>
9 {{range $index, $element := .}}
10 <li>{{$index}} - {{$element}}</li>
11 {{end}}
12</ul>
13</body>
14</html>
Map
به مثالی از ساختمان داده Map توجه کنید.
1func main() {
2 superheroes := map[string]string{
3 "Lt. Daniel Kaffee": "Tom Cruise",
4 "Col. Nathan R. Jessep": "Jack Nicholson",
5 "Lt. Cdr. JoAnne Galloway": "Demi Moore",
6 "Capt. Jack Ross": "Kevin Bacon",
7 "Lance Cpl. Harold W. Dawson": "Wolfgang Bodison",
8 }
9 err := tpl.Execute(w, superheroes)
10 if err != nil {
11 log.Fatalln(err)
12 }
13}
مقدار pipeline درون Actions یک Map است. اگر هیچ جفت کلید/مقدار در map وجود نداشته باشد، هیچ چیز در خروجی ارائه نمیشود، در غیر این صورت از نقطه برای عناصر متوالی map استفاده میشود و قالب بدینصورت اجرا میشود که همه کلیدها به صورت $key و همه مقادیر متناظر به صورت $val در نظر گرفته میشوند. اگر کلیدها از نوع ابتدایی با ترتیب تعریف شده (comparable) باشند، عناصر در ترتیب کلید مرتبشده بازدید میشوند.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>My Peeps</title>
6</head>
7<body>
8<ul>
9 {{range $key, $val := .}}
10 <li>{{$key}} - {{$val}}</li>
11 {{end}}
12</ul>
13</body>
14</html>
Struct
در این بخش مثالی از یک Struct با مجموعهای از فیلدها که به صورت inline اعلان شدهاند را بررسی میکنیم:
1func main() {
2 superhero := struct {
3 Name string
4 Motto string
5 }{
6 Name: "Bruise Wayne",
7 Motto: "I am batman",
8 }
9 err := tpl.Execute(w, superhero)
10 if err != nil {
11 log.Fatalln(err)
12 }
13}
ابتدا یک نقطه و سپس نام فیلد struct داده به صورت Name. به عنوان آرگومان قالب استفاده میشود. نتیجه مقدار فیلد است. فراخوانی فیلدها را میتوان به صورت Field1.Field2. به هم زنجیر کرد. فیلدها میتوانند روی متغیرها ارزیابی شوند که شامل زنجیرههای $x.Field1.Field2 است. قالب زیر فیلدهای ذخیره شده در متغیرها را نشان میدهد و سپس در Actions ارزیابی میشود:
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>My Peeps</title>
6</head>
7<body>
8<ul>
9 {{$x := .Name}}
10 {{$y := .Motto}}
11 <li>{{$x}} - {{$y}}</li>
12</ul>
13</body>
14</html>
Slice of Structs
در این بخش به بررسی مثالی از قالب با استفاده از ساختمان داده اسلایس struct میپردازیم:
1func main() {
2 im := superhero{
3 Name: "Iron man",
4 Motto: "I am iron man",
5 }
6 ca := superhero{
7 Name: "Captain America",
8 Motto: "Avengers assemble",
9 }
10 ds := superhero{
11 Name: "Doctor Strange",
12 Motto: "I see things",
13 }
14 superheroes := []superhero{im, ca, ds}
15 err := tpl.Execute(w, superheroes)
16 if err != nil {
17 log.Fatalln(err)
18 }
19}
ما از یک بازه استفاده کردهایم تا روی یک اسلایس حلقهای تعریف کنیم. مقدار pipeline در Action باید یک آرایه (یا اسلایس) باشد. اگر مقدار اسلایس صفر باشد، هیچ خروجی ارائه نمیشود، اما در غیر این صورت از نقطه برای عناصر متوالی آرایه یا اسلایس استفاده میشود و قالب اجرا خواهد شد.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>My Peeps</title>
6</head>
7<body>
8<ul>
9 {{range .}}
10 <li>{{.Name}} - {{.Motto}}</li>
11 {{end}}
12</ul>
13</body>
14</html>
ساختمانهای داده مختلف در Go میتوانند با هم ترکیب نیز بشوند تا قالبهای مفیدی ایجاد کنند.
نکتهای در مورد استفاده از گزارههای شرطی روی قالبها
امکان نوشتن قالبهای شرطی مانند زیر نیز وجود دارد:
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
بدین ترتیب تواناهایی شگفتانگیزی برای تولید محتوای دینامیک پیدا میکنیم. اگر مقدار pipeline خالی (یعنی flase، 0 یا هر اشارهگر تهی یا مقدار اینترفیس و همچنین هر آرایه، اسلایس، نقشه یا رشته با طول صفر) باشد، T0 اجرا میشود و در غیر این صورت T1 اجرا خواهد شد.
تابع در قالبها
میتوان از هر تابعی در یک قالب Go استفاده کرد؛ به شرط این که تعریف شده باشد. به صورت پیشفرض هیچ تابعی در قالب تعریف نشده است، اما متد Funcs روی یک قالب میتواند برای اضافه کردن تابع از طریق ایجاد نگاشتهایی مانند زیر استفاده شود:
1func monthDayYear(t time.Time) string {
2 return t.Format("January 2, 2006")
3}
4
5func homeFunc(w http.ResponseWriter, r *http.Request) {
6 w.Header().Set("Content-Type", "text/html")
7 var fm = template.FuncMap{
8 "fdateMDY": monthDayYear,
9 }
10 tpl := template.Must(template.New("").Funcs(fm).ParseFiles("index.gohtml"))
11 if err := tpl.ExecuteTemplate(w, "index.gohtml", time.Now()); err != nil {
12 log.Fatalln(err)
13 }
14}
برای ایجاد یک نگاشت از نامها به تابعها باید از نوع FuncMap استفاده کنیم. هر تابع یا باید یک مقدار بازگشتی منفرد داشته باشد و یا دو مقدار بازگشتی داشته باشد که دونی خطای نوع داشته باشد.
1type FuncMap map[string]interface{}
در خط 7 کد فوق ما یک نگاشت از تابع monthDayYear به یک نام fdateMDY تعریف کردهایم. اینک این تابع میتواند درون قالب استفاده شود. به خاطر داشته باشید که نقطه (.) دادههای ارائه شده به قالب را نگهداری میکند.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>functions</title>
6</head>
7<body>
8 <b>Without date formatting: {{.}}</b>
9 <hr>
10 <b>With date formatting: {{fdateMDY .}}</b>
11</body>
12</html>
روش های مختلفی برای استفاده از قالبها در Go وجود دارند. در این نوشته با برخی از این روشها آشنا شدیم.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش توسعه وب با زبان برنامه نویسی Go
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- زبان برنامه نویسی Go — راهنمای شروع به کار
- چرا باید زبان برنامه نویسی Go را بیاموزیم؟ — راهنمای جامع
==