Devahoy Logo
PublishedAt

Web Development

มาหัดเขียนบล็อกด้วย Middleman ตอนที่ 2 (Custom Layout)

มาหัดเขียนบล็อกด้วย Middleman ตอนที่ 2 (Custom Layout)

Table of Contents

หลักจากที่ มาหัดเขียนบล็อกด้วย Middleman ตอนที่ 1 กันไปแล้ว ซึ่งบทความนั้นจะพูดถึง Overview ของ Middleman มาในบทความนี้ จะเป็นการทำการ Custom Middleman รวมถึงจัด CSS/Layout ใหม่

Getting Started

เริ่มต้น จะใช้ธีม Clean Blog เพื่อทำการแปลงจาก Template HTML เป็น Middleman

ตัวโปรเจ็คของ Middleman จากบทความที่แล้ว ที่ชื่อ my_blog จะมีโครงสร้างดังนี้

Terminal window
$ tree
.
├── Gemfile
├── Gemfile.lock
├── config.rb
└── source
├── 2012-01-01-example-article.html.markdown
├── 2015-06-16-my-new-article.html.markdown
├── calendar.html.erb
├── feed.xml.builder
├── images
├── index.html.erb
├── javascripts
├── layout.erb
├── stylesheets
└── tag.html.erb
4 directories, 10 files

ชำแหละธีม Clean Blog

เมื่อเราเลือกใช้ Clean Blog ก็ถึงเวลาชำแหละดูข้างในซักหน่อย ว่ามีไฟล์อะไรบ้าง โดยสามารถดาวน์โหลดได้จากลิงค์ข้างบน หรือจะโคลนจาก Github ก็ได้

Terminal window
git clone https://github.com/IronSummitMedia/startbootstrap-clean-blog.git

ภายในโปรเจ็คของ Clean Blog จะมีไฟล์ประมาณนี้

Terminal window
$ tree
.
├── LICENSE
├── README.md
├── about.html
├── contact.html
├── css
│   ├── bootstrap.css
│   ├── bootstrap.min.css
│   ├── clean-blog.css
│   └── clean-blog.min.css
├── fonts
│   ├── glyphicons-halflings-regular.eot
├── img
│   ├── about-bg.jpg
├── index.html
├── js
│   ├── bootstrap.js
│   ├── bootstrap.min.js
│   ├── clean-blog.js
│   ├── clean-blog.min.js
│   ├── jquery.js
│   └── jquery.min.js
├── less
│   ├── clean-blog.less
│   ├── mixins.less
│   └── variables.less
├── mail
│   └── contact_me.php
└── post.html
6 directories, 30 files

สิ่งที่เราสนใจคือไฟล์

  • index.html : เป็นหน้าแรกของเว็บไซต์ซึ่งจะเอาไว้แสดง Post ต่างๆ
  • post.html : หน้ารายละเอียดของแต่ละ Post
  • about.html : หน้า Page About
  • contact.html : หน้า Page Contact

และไฟล์ CSS/JS อื่นๆ เราจะไม่ทำการ Custom จะใช้ default ของ Clean Blog ไปเลย

เข้าใจโครงสร้างของ Middleman

ก่อนที่เราจะทำการ Custom ได้เราต้องเข้าใจโครงสร้างของไฟล์ Middleman เพิ่มเติมซักหน่อย

Terminal window
cd my_blog/source

เมื่อลองเข้าไปดูโฟลเดอร์ source ของ my_blog ที่สร้างจาก middleman จะเห็นว่ามีไฟล์ดังนี้

1
├── 2012-01-01-example-article.html.markdown
2
├── 2015-06-16-my-new-article.html.markdown
3
├── calendar.html.erb
4
├── feed.xml.builder
5
├── images
6
├── index.html.erb
7
├── javascripts
8
├── layout.erb
9
├── stylesheets
10
└── tag.html.erb
  • index.html.erb : ก็คือหน้า index ของเว็บเรา โดยมี extension เป็น erb คือสามารถเขียนโค๊ด ruby ลงไปใน html ได้
  • layout.erb : เป็นไฟล์​ default layout ของเว็บเรา บางครั้งหน้าเว็บแต่ละหน้า จะมี header, navbar เหมือนๆกัน เราก็แค่สร้าง layout อันนี้ แล้วเปลี่ยนแค่เนื้อหาภายใน ไม่จำเป็นต้องมี header, navbar เขียนอยู่ทุกๆไฟล์
  • tag.html.erb : หน้าที่เอาไว้แสดง เวลาเราเลือกกดดูรายละเอียด tag
  • images/javascripts/stylesheets : โฟลเดอร์สำหรับเก็บไฟล์ assets ของเรา

เข้าใจ Helper Methods เบื้องต้น

เมื่อลองเปิดไฟล์ layout.erb เราสามารถแทรกโค๊ด Ruby ลงไปใน HTML ได้เลย ตัวอย่าง เช่น

1
<% name = "Chai" %>
2
<h1>Hello <%= name %></h1>
  • <% %> : ไว้สำหรับ statement
  • <%= %> : เอาไว้สำหรับ output ค่า

เราสามารถที่จะใช้ link_to เพื่อสร้าง <a href=""> สำหรับ html ได้ ตามตัวอย่าง syntax ข้างล่างนี้

1
<%= link_to 'My Site', 'http://mysite.com' %>

มีค่าเท่ากับ

1
<a href="http://mysite.com">My Site</a>

Assets Helper

เราสามารถใช้ลิงค์สำหรับไฟล์ assets ของเราได้ เช่นไฟล์ js, css แบบนี้

1
<%= stylesheet_link_tag 'main' %>
2
<%= javascript_include_tag "main" %>

จะมีค่าเท่ากับการลิงค์ไปยังไฟล์ main.css ในโฟลเดอร์ stylesheets และไฟล์ main.js ในโฟลเดอร์ javascripts

Yield

หากสังเกต ในไฟล์ layout.erb จะเห็นโค๊ดบรรทัดนี้อยู่

1
<%= yield %>

มันคือ block ที่เอาไว้สำหรับ inject ค่า content ต่างๆของแต่ละหน้า เช่น หน้า index ก็จะแสดง content อีกอย่าง หน้า about ก็จะแสดง content อีกอย่าง

เริ่มต้น Custom

ทำการก็อปปี้ไฟล์ css/js/img ใน Clean Blog มาใส่ไว้ในโฟลเดอร์ source/stylesheets , source/javascripts และ source/images ตามลำดับ

Terminal window
└── source
├── images
│   ├── about-bg.jpg
│   ├── contact-bg.jpg
│   ├── home-bg.jpg
│   ├── post-bg.jpg
│   └── post-sample-image.jpg
├── javascripts
│   ├── bootstrap.js
│   ├── bootstrap.min.js
│   ├── clean-blog.js
│   ├── clean-blog.min.js
│   ├── jquery.js
│   └── jquery.min.js
├── stylesheets
│   ├── bootstrap.css
│   ├── bootstrap.min.css
│   ├── clean-blog.css
│   └── clean-blog.min.css

Edit index.html.erb

มาเริ่มลงมือ Edit ไฟล์ index.html.erb เลย ก่อนการ Edit ก็ทำการ backup ของเก่าไว้ดูก่อนก็ได้

Terminal window
cp index.html.erb index.html.erb.tmp

โดยทำการก็อปปี้โค๊ดทั้งหมดจาก index.html ของ Clean Blog มาใส่ใน index.html.erb จะได้ดังนี้

1
<!doctype html>
2
<html lang="en">
3
<head>
4
<meta charset="utf-8" />
5
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
<meta name="viewport" content="width=device-width, initial-scale=1" />
7
<meta name="description" content="" />
8
<meta name="author" content="" />
9
10
<title>Clean Blog</title>
11
12
<!-- Bootstrap Core CSS -->
13
<link href="css/bootstrap.min.css" rel="stylesheet" />
14
15
<!-- Custom CSS -->
16
<link href="css/clean-blog.min.css" rel="stylesheet" />
17
18
<!-- Custom Fonts -->
19
<link
20
href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css"
21
rel="stylesheet"
22
type="text/css"
23
/>
24
<link
25
href="http://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic"
26
rel="stylesheet"
27
type="text/css"
28
/>
29
<link
30
href="http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800"
31
rel="stylesheet"
32
type="text/css"
33
/>
34
35
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
36
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
37
<!--[if lt IE 9]>
38
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
39
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
40
<![endif]-->
41
</head>
42
43
<body>
44
<!-- Navigation -->
45
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
46
<div class="container-fluid">
47
<!-- Brand and toggle get grouped for better mobile display -->
48
<div class="navbar-header page-scroll">
49
<button
50
type="button"
51
class="navbar-toggle"
52
data-toggle="collapse"
53
data-target="#bs-example-navbar-collapse-1"
54
>
55
<span class="sr-only">Toggle navigation</span>
56
<span class="icon-bar"></span>
57
<span class="icon-bar"></span>
58
<span class="icon-bar"></span>
59
</button>
60
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
61
</div>
62
63
<!-- Collect the nav links, forms, and other content for toggling -->
64
<div class="navbar-collapse collapse" id="bs-example-navbar-collapse-1">
65
<ul class="nav navbar-nav navbar-right">
66
<li>
67
<a href="index.html">Home</a>
68
</li>
69
<li>
70
<a href="about.html">About</a>
71
</li>
72
<li>
73
<a href="post.html">Sample Post</a>
74
</li>
75
<li>
76
<a href="contact.html">Contact</a>
77
</li>
78
</ul>
79
</div>
80
<!-- /.navbar-collapse -->
81
</div>
82
<!-- /.container -->
83
</nav>
84
85
<!-- Page Header -->
86
<!-- Set your background image for this header on the line below. -->
87
<header class="intro-header" style="background-image: url('img/home-bg.jpg')">
88
<div class="container">
89
<div class="row">
90
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
91
<div class="site-heading">
92
<h1>Clean Blog</h1>
93
<hr class="small" />
94
<span class="subheading">A Clean Blog Theme by Start Bootstrap</span>
95
</div>
96
</div>
97
</div>
98
</div>
99
</header>
100
101
<!-- Main Content -->
102
<div class="container">
103
<div class="row">
104
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
105
<div class="post-preview">
106
<a href="post.html">
107
<h2 class="post-title">Man must explore, and this is exploration at its greatest</h2>
108
<h3 class="post-subtitle">Problems look mighty small from 150 miles up</h3>
109
</a>
110
<p class="post-meta">Posted by <a href="#">Start Bootstrap</a> on September 24, 2014</p>
111
</div>
112
<hr />
113
<div class="post-preview">
114
<a href="post.html">
115
<h2 class="post-title">
116
I believe every human has a finite number of heartbeats. I don't intend to waste any
117
of mine.
118
</h2>
119
</a>
120
<p class="post-meta">Posted by <a href="#">Start Bootstrap</a> on September 18, 2014</p>
121
</div>
122
<hr />
123
<div class="post-preview">
124
<a href="post.html">
125
<h2 class="post-title">Science has not yet mastered prophecy</h2>
126
<h3 class="post-subtitle">
127
We predict too much for the next year and yet far too little for the next ten.
128
</h3>
129
</a>
130
<p class="post-meta">Posted by <a href="#">Start Bootstrap</a> on August 24, 2014</p>
131
</div>
132
<hr />
133
<div class="post-preview">
134
<a href="post.html">
135
<h2 class="post-title">Failure is not an option</h2>
136
<h3 class="post-subtitle">
137
Many say exploration is part of our destiny, but it’s actually our duty to future
138
generations.
139
</h3>
140
</a>
141
<p class="post-meta">Posted by <a href="#">Start Bootstrap</a> on July 8, 2014</p>
142
</div>
143
<hr />
144
<!-- Pager -->
145
<ul class="pager">
146
<li class="next">
147
<a href="#">Older Posts &rarr;</a>
148
</li>
149
</ul>
150
</div>
151
</div>
152
</div>
153
154
<hr />
155
156
<!-- Footer -->
157
<footer>
158
<div class="container">
159
<div class="row">
160
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
161
<ul class="list-inline text-center">
162
<li>
163
<a href="#">
164
<span class="fa-stack fa-lg">
165
<i class="fa fa-circle fa-stack-2x"></i>
166
<i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
167
</span>
168
</a>
169
</li>
170
<li>
171
<a href="#">
172
<span class="fa-stack fa-lg">
173
<i class="fa fa-circle fa-stack-2x"></i>
174
<i class="fa fa-facebook fa-stack-1x fa-inverse"></i>
175
</span>
176
</a>
177
</li>
178
<li>
179
<a href="#">
180
<span class="fa-stack fa-lg">
181
<i class="fa fa-circle fa-stack-2x"></i>
182
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
183
</span>
184
</a>
185
</li>
186
</ul>
187
<p class="copyright text-muted">Copyright &copy; Your Website 2014</p>
188
</div>
189
</div>
190
</div>
191
</footer>
192
193
<!-- jQuery -->
194
<script src="js/jquery.js"></script>
195
196
<!-- Bootstrap Core JavaScript -->
197
<script src="js/bootstrap.min.js"></script>
198
199
<!-- Custom Theme JavaScript -->
200
<script src="js/clean-blog.min.js"></script>
201
</body>
202
</html>

จะสังเกตเห็นว่า มีส่วนที่ทำซ้ำๆ นั่นก็คือภายใน <div class="post-preview"> ตรงส่วนนี้เราจะใช้การ วนลูป เพื่อแสดง Post ทั้งหมดของเรา ด้วย

1
<% page_articles.each_with_index do |article, i| %>
2
<!-- ruby code go here -->
3
<% end %>

Edit layout.erb

ต่อมาทำการ Edit ไฟล์ layout.erb เนื่องจากว่า เราจะไม่เอาพวก header หรือ footer ต่างๆ ไว้ในไฟล์ index.html.erb แต่จะใช้ในไฟล์ layout.erb แทน

ไฟล์ layout.erb จะเป็นแบบนี้

1
<!doctype html>
2
<html lang="en">
3
<head>
4
<meta charset="utf-8" />
5
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
<meta name="viewport" content="width=device-width, initial-scale=1" />
7
<meta name="description" content="" />
8
<meta name="author" content="" />
9
10
<title>Clean Blog</title>
11
12
<!-- Bootstrap Core CSS -->
13
<%= stylesheet_link_tag 'bootstrap.min' %>
14
15
<!-- Custom CSS -->
16
<%= stylesheet_link_tag 'clean-blog.min' %>
17
18
<!-- Custom Fonts -->
19
<link
20
href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css"
21
rel="stylesheet"
22
type="text/css"
23
/>
24
<link
25
href="http://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic"
26
rel="stylesheet"
27
type="text/css"
28
/>
29
<link
30
href="http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800"
31
rel="stylesheet"
32
type="text/css"
33
/>
34
35
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
36
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
37
<!--[if lt IE 9]>
38
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
39
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
40
<![endif]-->
41
</head>
42
43
<body>
44
<!-- Navigation -->
45
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
46
<div class="container-fluid">
47
<!-- Brand and toggle get grouped for better mobile display -->
48
<div class="navbar-header page-scroll">
49
<button
50
type="button"
51
class="navbar-toggle"
52
data-toggle="collapse"
53
data-target="#bs-example-navbar-collapse-1"
54
>
55
<span class="sr-only">Toggle navigation</span>
56
<span class="icon-bar"></span>
57
<span class="icon-bar"></span>
58
<span class="icon-bar"></span>
59
</button>
60
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
61
</div>
62
63
<!-- Collect the nav links, forms, and other content for toggling -->
64
<div class="navbar-collapse collapse" id="bs-example-navbar-collapse-1">
65
<ul class="nav navbar-nav navbar-right">
66
<li>
67
<a href="index.html">Home</a>
68
</li>
69
<li>
70
<a href="about.html">About</a>
71
</li>
72
<li>
73
<a href="post.html">Sample Post</a>
74
</li>
75
<li>
76
<a href="contact.html">Contact</a>
77
</li>
78
</ul>
79
</div>
80
<!-- /.navbar-collapse -->
81
</div>
82
<!-- /.container -->
83
</nav>
84
85
<!-- Page Header -->
86
<!-- Set your background image for this header on the line below. -->
87
<header class="intro-header" style="background-image: url('images/home-bg.jpg')">
88
<div class="container">
89
<div class="row">
90
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
91
<div class="site-heading">
92
<h1>Clean Blog</h1>
93
<hr class="small" />
94
<span class="subheading">A Clean Blog Theme by Start Bootstrap</span>
95
</div>
96
</div>
97
</div>
98
</div>
99
</header>
100
101
<%= yield %>
102
103
<!-- Footer -->
104
<footer>
105
<div class="container">
106
<div class="row">
107
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
108
<ul class="list-inline text-center">
109
<li>
110
<a href="#">
111
<span class="fa-stack fa-lg">
112
<i class="fa fa-circle fa-stack-2x"></i>
113
<i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
114
</span>
115
</a>
116
</li>
117
<li>
118
<a href="#">
119
<span class="fa-stack fa-lg">
120
<i class="fa fa-circle fa-stack-2x"></i>
121
<i class="fa fa-facebook fa-stack-1x fa-inverse"></i>
122
</span>
123
</a>
124
</li>
125
<li>
126
<a href="#">
127
<span class="fa-stack fa-lg">
128
<i class="fa fa-circle fa-stack-2x"></i>
129
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
130
</span>
131
</a>
132
</li>
133
</ul>
134
<p class="copyright text-muted">Copyright &copy; Your Website 2014</p>
135
</div>
136
</div>
137
</div>
138
</footer>
139
140
<!-- jQuery -->
141
<%= javascript_include_tag "jquery" %>
142
143
<!-- Bootstrap Core JavaScript -->
144
<%= javascript_include_tag "bootstrap.min" %>
145
146
<!-- Custom Theme JavaScript -->
147
<%= javascript_include_tag "clean-blog.min" %>
148
</body>
149
</html>

และไฟล์ index.html.erb ก็จะเหลือแค่ส่วนแสดง content เท่านั้น เป็น

1
---
2
pageable: true
3
per_page: 10
4
---
5
6
<div class="container">
7
<div class="row">
8
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
9
<% page_articles.each_with_index do |article, i| %>
10
11
<div class="post-preview">
12
<h2 class="post-title"><%= link_to article.title, article %></h2>
13
<h3 class="post-subtitle"><%= article.summary %></h3>
14
<p classs="post-meta">Posted by Author on <%= article.date.strftime('%B %d, %Y') %></p>
15
</div>
16
<hr />
17
18
<% end %>
19
</div>
20
</div>
21
</div>
22
23
<hr />

เมื่อเปิดหน้าเว็บ ก็จะเห็นเว็บบล็อกของเรา เป็น Clean-Blog แล้ว

1
middleman

Clean Blog

หากมีปัญหา run server ไม่ได้ ให้ทำการเพิ่ม gem 'nokogiri' ในไฟล์ Gemfile และสั่ง bundle install

สำหรับ Part 2 ก็จบเท่านี้ แต่ยังมีหลายๆส่วนที่ต้องปรับ เช่น Author, ย้าย Navbar, Footer (Partials Template), และหน้า Page อื่นๆ เช่น หน้า Post

สำหรับ Source Code Part 2 ดูได้จากที่นี่

Authors
avatar

Chai Phonbopit

เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust

Related Posts