---
# System prepended metadata

title: RL-09 框架/套件/上線日期
tags: [Rails]

---

# RL-09 框架/套件/上線日期
8月20日

## Rails更新
https://rubygems.org/gems/rails/versions/6.1.4.1
新專案：
> gem install rails

舊專案：
改Gemfile檔案:
> gem 'rails', '~> 6.1', '>= 6.1.4.1'
bundle update

## Stimulus 框架
* 方便管理、幫你解決囉唆的小事、不用煩惱按鈕是否存在、action 寫法像 Rails
* 有 target 用，不再需要 queryselector 了
* 看框架時要先看他為什麼要做這個框架
    * Rails 是 basecamp 的副產品

安裝在rails專案目錄
> rails webpacker:install:stimulus

### 設定
在 **app/javascript/controllers/** 下面會有相應JS
可在 **export default** 下定義各種方法

#### data-controller
* 是 HTML 與 JS 之間的橋樑，就像 HTML 跟 CSS 是透過 class 溝通一樣
    * 在需要套用的網頁標籤，加上 **data-controller=**"hello" （引號中為controller名稱  可自訂）
* 用 data-controller 可以選到我要的 JS controller
* 後面的值是對到檔名，不是內容，他會執行檔案裡面的內容
* 用data…寫的好處是:如果寫錯也沒關係，他就只是個屬性，不會出錯

#### data-action
* 在網頁中作用區域，放data-action="hello#方法名稱"  令其生效
* 做事，做 controller 裡面的 function
* 每個元素都有預設行為，ex:button 是 click，可以不用寫
    * 使用data-action 代替事件捕捉click

#### data-index-target
選取到目標 = queryselector
通常 this 指的是 controller 本身

#### data-controllername-index-value
選到目標的 value

#### connect()
當東西被掛載上去就會執行裡面的動作
類似 initialize，被執行到的時候就會執行裡面的動作

## 打包
* 以前 Ruby 是用 assets pipeline，現在用 webpacker gem。
* 對webpack來說全部檔案都是js檔。
    * 所以，將各地js寫的功能收攏進來。

### 改 JS 資料夾名稱
先改資料夾名稱成你想要的名字
去 config/webpacker.yml 檔案 
把 source_path: app/javascript 改名
把 javascript 改成你想要的名字


## 上線日期
幫 note 新增一個上線欄位
1. 終端機rais g migration
1. add_column
1. db:migrate
1. index:_form
1. 改strong params
1. 到 view 那邊顯示出來

## 挑套件
看用的人多不多、有沒有在維護、最近一次更新是啥時、license 是甚麼

### flatpickr
非常輕量級的日曆套件
[flatpickr](https://flatpickr.js.org/)

### wysiwyg editor
what you see is what you get
所見及所得

### medium editor
medium 編輯器套件
[medium-editor](https://www.npmjs.com/package/medium-editor)

## html_save
  Rails 怕使用者在你表單內輸入程式碼，攻擊你的網站使用者
  預設讓輸進去的東西是字串，不會變成程式碼解讀
  如果要讓他知道你輸入的東西確定是安全的可以在資料後面加 html_save 讓他安心

show.html.erb
```rb
  <%# 用 data-controller 選到 favorite_controller.js %>
  <%# data-favorite-id-value 對應到 favorite_controller.js  %>
  <%# favorite 對應到controller 名字，id 對應到 static values 的屬性名稱 %>
  <%# 讓他的 value 對應到 @note.id %>
  
  <%# data-action 執行 favorite controller 裡的 toggleFavorite 方法 %>
  <div data-controller="favorite"
       data-favorite-id-value="<%= @note.id %>">
    <a href="#"
       id="favorite_btn"
       data-action="favorite#toggleFavorite">
      <%= favorite_icon(current_user, @note) %>
    </a>
  </div>
  
<%# 當使用者輸入資料有換行時會出現 \n %>
<%# simple_format 將換行從 \n 改成 HTML 標籤的 <br> ，預設是用 <p> 標籤包住單行文字 %>
<%= simple_format(@note.content) %>
```
user.rb
```rb
  # 是否有加到最愛，把它變方法看起來比較易懂
  # 商業邏輯 = 處理這件事的邏輯、演算法、流程、公式與規則，這邊要處理如何顯示按讚
  # 通常會用到條件判斷式
  def favorite?(n)
    favorite_notes.exists?(n.id)
  end
```
notes_helper.rb
```rb
# 當 view 需要用到邏輯時，可以丟到 helper 裡，讓 view 呈現東西就好
module NotesHelper
  def favorite_icon(user, note)
    # 自己定義一個 classes 然後控制加進來的東西是甚麼來按讚或取消
    # 因為看手冊 tag 小幫手會變成陣列，所以classes要設定成陣列
    classes = ['favorite_icon']

    # 如果目前用戶有登入而且如果有按讚就顯示(把favorite-on加入classes)
    # 有登入沒有按讚就不顯示(把favorite-off加入classes)
    # favorite?作用在 user 上，所以要定義在 user 的 model
    if user
      classes << (user.favorite?(note) ? 'favorite-on' : 'favorite-off')
    end

    # tag 小幫手(TagHelper)，可以生出 HTML 的 tag，這邊會生出 div 這個標籤
    # tag 或 content_tag 都能達到一樣效果
    # 本身引用模組 OutputSafetyHelper，裡面的 safe_join 方法有 html_safe 效果
    # 後面新增兩個 class 第一個是上面創造出來的 classes
    # 第二個是"data-favorite-target" 在 ruby 檔裡面表示 HTML 的屬性或值
    # 要用字串包起來，不然他看不懂
    # 在 HTML 會變成 data-favorite-target = "icon"
    tag.div class: classes, "data-favorite-target": "icon"
  end
end
```
application.js
```js
// 匯入預設的 function
import Rails from "@rails/ujs";
import Turbolinks from "turbolinks";
// 匯入整個模組，指定名稱為 ActiveStorage
import * as ActiveStorage from "@rails/activestorage";
import "channels";

Rails.start();
Turbolinks.start();
ActiveStorage.start();

// import "controllers" 可以省略 .js 跟 index 
// = import "controllers/index.js"
// = import "controllers/index"
import "controllers";
// 匯入 styles 資料夾的 index.js
// 而裡面的 index.js 又有匯入 form.scss 跟 ui.scss
// 所以可以抓到所有 css
import "styles";
```
favorite_controller.js
```js
import { Controller } from "stimulus";
// 原本是用 axios 打 api ，現在改用 Rails 內建的 ujs 去打
// 使用前要先引入模組
import Rails from "@rails/ujs";

// 預設輸出讓瀏覽器執行裡面內容
export default class extends Controller {
  // static = 靜態方法，static 後面的是類別，可直接被呼叫
  // 因為是類別所以可以用 new 創造實體，創造出來的實體不能被呼叫
  static targets = ["icon"];
  // 轉型成數字
  static values = { id: Number };

  // 透過 data-action 可以直接選取到按讚的按鈕
  // this 指的是 a 標籤，也就是按讚的按鈕本身
  // 把自己的 id 傳入 addFavorite 方法
  // idValue 是從 data-favorite-id-value 來的
  // 在 show.erb 抓到 @note.id，用 idValue 表示
  toggleFavorite(e) {
    e.preventDefault();
    this.addFavorite(this.idValue);
  }

  // 把剛剛抓到的 id 塞進來
  // 負責打 api ，成功就加入或移除按讚
  addFavorite(id) {
    const url = `/api/v1/notes/${id}/favorite`;

    // Rails 內建的 ujs 打 api，會自動幫你抓 token
    // url：發送非同步請求的對象，也就是索求資料的 server 的網址
    // method：發送請求的類型，如 GET、POST、DELETE、PATCH 等
    // data：要附帶在請求裡發送給 server 的資料
    // dataType：要求 server 回傳的資料格式
    // success：成功後要執行的 function，function 會帶入 server 回傳的資料
    Rails.ajax({
      url: url,
      type: "post",
      data: "",
      success: (data) => {
        // this = favorite_controller 本身，因為箭頭函式沒有自己的 this 
        // 會去找 class 本身 = favorite_controller.js
        // iconTarget 會選到 data-favorite-target 屬性
        // this.iconTarget = 選到 "favorite" 裡面的 "icon" class ，
        // 也就是用 tag 小幫手生出來的 div
        const icon = this.iconTarget;

        if (data.status === "added") {
          icon.classList.remove("favorite-off");
          icon.classList.add("favorite-on");
        } else {
          icon.classList.remove("favorite-on");
          icon.classList.add("favorite-off");
        }
      },
      error: function (err) {
        console.log(err);
      },
    });
  }
}
```
datepicker_controller.js
```js
import { Controller } from "stimulus";
import flatpickr from "flatpickr";
// 因為官網的 link 引用的 css 路徑是 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
// 所以猜測要輸入路徑是 "flatpickr/dist/flatpickr.min.css"
import "flatpickr/dist/flatpickr.min.css";

// https://stimulus.hotwired.dev/reference/controllers
// https://flatpickr.js.org/getting-started/#usage
// 看 Usage 有寫到 If using flatpickr in a framework, its recommended to pass the element directly
// flatpickr(element, {});
// 但在stimulus 的 controller 裡面取得 HTML 元素要用 this.element(看官網)
export default class extends Controller {
  connect() {
    flatpickr(this.element, {});
  }
}

```
_form.html.erb
```rb
<%= form_for(note) do |f| %>
  <div>
    <%= f.label :title, "標題" %>
    <%= f.text_field :title %>

    <%= f.label :online_date, "上線時間" %>
    <%# 在 text_field 寫 data: { controller: 'datepicker' } %>
    <%# 到 HTML 後會變成 data-controller="datepicker" %>
    <%# 這樣才能被 stimulus.js 拿到，取得 datepicker_controller.js 檔案內容 %>
    <%= f.text_field :online_date, data: { controller: 'datepicker' }
  %>
  </div>

  <div>
    <%= f.label :content, "內文" %>
    <%# 同上，可以取得取得 editor_controller.js 檔案內容  %>
    <%= f.text_area :content, data: { controller: 'editor' } %>
  </div>
```
editor_controller.js
```js
import { Controller } from "stimulus";
import MediumEditor from "medium-editor";
// 官網的 cdn 路徑 <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/medium-editor@latest/dist/css/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">
// 把版本拿掉就可以正常運作了
import "medium-editor/dist/css/medium-editor.min.css";

// https://www.npmjs.com/package/medium-editor
// 官網的 usage 有寫 You can also pass a list of HTML elements:
// var elements = document.querySelectorAll('.editable'),
// editor = new MediumEditor(elements);
// 因為有用 data: { controller: 'editor' } 所以就不用再用 querySelectorAll 去抓
// 直接寫 new MediumEditor(this.element) 就好
// this 的用法跟 datepicker_controller.js 一樣是 stimulus 來的
export default class extends Controller {
  connect() {
    new MediumEditor(this.element);
  }
}
```

## DRAFT
追溯一下  <%= favorite_icon(current_user, @note) %> 是按鈕?

?controller功能移出到lib/http/下，獨立js檔
再import回去
> import ax from "axios"

------

code越來越多 可以把 import ax開始的獨立出去，再import就好
import "script/favorite";

show網頁抓

-------------

webpacker 就像橋接器 會包裝 webpack

SCSS搬到javascript下 的styles
並新增index.js

import "./form";
import "./ui";


application再 import styles進來


若要更改javascript目錄名的話，webpack也要改


------

下拉選單日曆
flatepickr


###### tags: `Rails`


