iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 9
0
Modern Web

向 Rails 致敬!30天寫一個網頁框架,再拿來做一個 Todo List系列 第 9

[DAY 9] 復刻 Rails - 終於有基本雛形了!在 View 上面加點東西

現在的 Mavericks 雖然已經有基本的 View,但總覺得還是缺少點什麼,寫起來還是有點不那麼方便,好像應該再加上些,來幫助開發者更快的開發出網頁,我們可以回頭參考 Rails,看看有什麼東西可以加上去

開發人員的好幫手 Helper

還記得最常用的 link_to 嗎? 想像一個 Todo list 應該都會有 link 可以讓使用者管理,不然就先來做一個 link_to,但要做在哪裏比較?想想昨天提到的,每一個頁面都是 Controller 的 object,所以應該寫在 Controller 裡面就好

再來看看 Rails 的 link_to 要怎麼使用

<%= link_to('點我去google', 'https://www.google.com.tw/') %>

相信有寫過 Rails 的人應該不會陌生,我們按照這樣的 spec,將 helper 寫在 Controller

# mavericks/lib/mavericks/controller.rb

require 'erubi'

module Mavericks
  class Controller
    attr_reader :env, :called_render
    # .
    # .
    # 略
    def link_to(name = nil, url = nil)
      "<a href=\"#{url}\">#{name}</a>"
    end
  end
end

可以看到,其實 Rails 的 helper,也就是一般的 method,我們這裡接受兩個引數,一個叫 name,一個叫 url,並且用字串拼出一個 HTML tag ,接著回到 just_do 試試看

# just_do/app/views/tasks/index.html.erb

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Index</title>
  </head>
  <body>
    <h1>任務的index</h1>
    <%= @task_name %>
    <%= link_to '點我管理', 'https://www.google.com.tw/' %>
  </body>
</html>

接著打開瀏覽器看看,應該會看到一個可以點擊的連結,代表成功了!

改版 just_do

現在有了 Mavericks 提供的這些東西,好像差不多可以先做出一個基本的網站,這裡我們在複習一下 Rails 所學,對 Rails 來說,任務是一種 resource,我們會做一個 TasksController 的 class,而 Action 就是對應到要對這個任務(resource)做什麼動作,例如我要查詢所有的任務,就在 TasksController 底下建立 index Action,我要刪除任務,就是建立 destroy Action,以此類推,那我們就先從 index 頁開始做吧

# just_do/app/controllers/tasks_controller.rb

class TasksController < Mavericks::Controller
  def index

  end
end

view 的部分

<!-- just_do/app/views/tasks/index.html.erb -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Index</title>
    <link rel="stylesheet" href="https://unpkg.com/@coreui/coreui/dist/css/coreui.min.css">
  </head>
  <body class="c-app">
    <div class="c-wrapper">
      <header class="c-header c-header-light c-header-fixed">
      </header>
      <div class="c-body">
        <main class="c-main">
          <div class="container-fluid">
            <div class="row">
              <div class="col-sm-6 col-lg-3">
<div class="card">
      <h5 class="card-header">完成30天鐵人賽</h5>
      <div class="card-body">
        <p class="card-text">每天寫一篇,連續30天</p>
        <span><a href="/tasks/1/edit">修改</a><a data-confirm="確定刪除?" rel="nofollow" data-method="delete" href="/tasks/1">刪除</a></span>
      </div>
    </div>
              </div>
            </div>
          </div>
        </main>
      </div>
    </div><!-- Optional JavaScript -->
    <!-- Popper.js first, then CoreUI JS -->
    <script src="https://unpkg.com/@popperjs/core@2"></script>
    <script src="https://unpkg.com/@coreui/coreui/dist/js/coreui.min.js"></script>
  </body>
</html>

關於 layout

如果一直做下去,你應該會發現,每個頁面都有很多共同點,例如 <html> <head> 之類的 Tag 或內容,這些都是每一頁都有的東西,好像需要一個 layout 來方便我們套用才對?這裡大家可以思考,在現有的 Mavericks 架構下,怎麼樣才能做出套用 layout 的功能?

我的做法是這樣,先修改 lib/mavericks.rb

# mavericks/lib/mavericks.rb

def call(env)
  return favicon if env["PATH_INFO"] == '/favicon.ico'
  return index(env) if env["PATH_INFO"] == '/'

  begin
    klass, act =  get_controller_and_action(env)
    controller = klass.new(env)
    
    # 修改這段由 render_layout 輸出畫面
    controller.send(act)
    default_render(controller, act) unless controller.called_render
    text = controller.render_layout
    [200, {'Content-Type' => 'text/html'},
     [text]]
  rescue
    [404, {'Content-Type' => 'text/html'},
     ['This is a 404 page!!']]
  end
end

接著再修改 controller.rb 這個檔案,意思是我會把原先每個 Action render 的畫面先存在 @content 裡面,最後再呼叫 render_layout,把 layout 的頁面輸出到網頁上,layout 在過程中,會呼叫 content 這個 method,把 action 的內容一起讀取出來,拼成完整的一頁

# mavericks/lib/mavericks/controller.rb

def initialize(env)
  @env = env
  @called_render = false
  
  # 加上一個 @content
  @content = nil
end

# 改為直接 render layout
def render_layout
  layout = File.read "app/views/layouts/application.html.erb"
  eval(Erubi::Engine.new(layout).src)
end

# 透過寫在 layout 的 <%= content %> 來讀取內容
def content
  @content
end

def render(view_name)
  @called_render = true
  filename = File.join "app", "views", controller_name, "#{view_name}.html.erb"
  template = File.read filename
  
  # 原本直接 render 畫面,改為丟到 @content
  @content =  eval(Erubi::Engine.new(template).src)
end

最後我們在 just_do 就可以這樣使用,跟 Rails 一樣建立一個 application.html.erb 的 layout,並放在預設的位置(跟 Rails 一樣的 layout 路徑)

<!-- just_do/app/views/layouts/application.html.erb -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Index</title>
    <link rel="stylesheet" href="https://unpkg.com/@coreui/coreui/dist/css/coreui.min.css">
  </head>
  <body class="c-app">
    <div class="c-wrapper">
      <header class="c-header c-header-light c-header-fixed">
      </header>
      <div class="c-body">
        <main class="c-main">
          <div class="container-fluid">
            <!-- 呼叫 content 這個 method -->
            <%= content %>
          </div>
        </main>
      </div>
    </div><!-- Optional JavaScript -->
    <!-- Popper.js first, then CoreUI JS -->
    <script src="https://unpkg.com/@popperjs/core@2"></script>
    <script src="https://unpkg.com/@coreui/coreui/dist/js/coreui.min.js"></script>
  </body>
</html>

接著我們的 index.html.erb 就會只剩下這個 Action 的內容

<!-- just_do/app/views/tasks/index.html.erb -->

<div class="row">
  <div class="col-sm-6 col-lg-3">
    <div class="card">
      <h5 class="card-header">完成30天鐵人賽</h5>
      <div class="card-body">
        <p class="card-text">每天寫一篇,連續30天</p>
        <span><a href="/tasks/1/edit">修改</a><a data-confirm="確定刪除?" rel="nofollow" data-method="delete" href="/tasks/1">刪除</a></span>
      </div>
    </div>
  </div>
</div>

最後打開瀏覽器,看看畫面是不是正確

MVC 的最後一塊拼圖

MVC 框架看起來 C 和 V 都有了,那就剩下最後一個,也是重頭戲的 Model 了,相信許多人在學習 Rails 或是 MVC 框架時,最難搞懂或是卡關最久的應該就是它了,明天開始我們將會一步一步來建立起屬於自己的 Model!

今天加班差點來不及..


上一篇
[DAY 8] 復刻 Rails - 這裡的 View 還不錯
下一篇
[DAY 10] 復刻 Rails - MVC 的最後一張拼圖 - Model
系列文
向 Rails 致敬!30天寫一個網頁框架,再拿來做一個 Todo List30

尚未有邦友留言

立即登入留言