12 days ago

官方圖片解釋

Understanding Vue LifeCycle Hook

beforeCreate

在 Vue Instance、 Component(組件/元件) 初始化之後,數據觀測(data observer)和 event/watcher 事件配置之前被調用。

created

Vue Instance、 Component(組件/元件)創建完成,資料 $data 已經可以取得,屬性已經綁定,但 DOM 還未生成,並且 $el 屬性還不存在。

beforeMount

在掛載開始之前被調用,相關的 render 函數首次被調用。

mounted

el 被新創建的 vm.$el 替換,並掛載到 instance 上去之後調用該 hook。

beforeUpdate

數據更新之前被調用,發生在虛擬 DOM 重新渲染之前,可以在這個 hook 中進一步改變狀態,並不會觸發附加的重新渲染過程,這時還不會描繪 View。

updated

由於數據更改導致虛擬 DOM 重新渲染,這時 DOM 已經更新完成, View 被顯示在畫面上。

activated

有設定 keep-alive 時,這個 hook 會被呼叫。

deactivated

停用 keep-alive 時被呼叫。

beforeDestroy

Instance 被銷毀之前調用,在這一步中,該 instance 完全可以使用。

destroyed

Vue Instance 銷毀後調用,調用後該 instance 的所有東西會解除綁定,也會同時解掉所有 event listener, 所有 child instance 也會清除。

注意

  • 資料在 created 以後才存取的到(別把資料初始化跟 ajax 寫在 beforeCreate)

場景運用

  • beforeCreate: 加入 loading 事件
  • created: 在這結束 loading, 做 initialize 實現函數執行
  • mounted: 在這對 backend 發起請求, get some data 後搭配 hook do something
  • beforeDestroy: 你確認刪除__嗎?
  • destroyed: 當該 component 刪除後清空相關內容

reference

 
15 days ago

基本上只要你的國家普遍瀏覽器搜尋資料時是使用 Google 的,在做專案時通常會有 SEO 的需求,SEO的全名是(Search Engine Optimization),也就是將你的網站權重提高,能夠在搜尋業面前幾筆就能被曝光。

SEO的規則基本上一直在變化,權重的給分也隨著技術升級而有了新的規則,像以往的網站只是網站,現在的網站還要能夠支援 RWD 來讓行動裝置瀏覽時能夠更加輕鬆,也免去了做 mobile app 的勞力(有能力兩個都做當然好囉)。

Google 會定時有 bot 來爬網站,爬完之後依照權重進去排名,權重大致上要在乎的就是幾點

  • 善用 Meta Tag / Meta Data 幫網頁劃重點
  • Url 具有意義
  • HTML tag 沒有亂用,例如一大堆 h1
  • 採用 HTTPS 連線
  • 支援 RWD(Responsive Web Design)
  • 網站開啟速度
  • 語系與國家
  • Sitemap
  • 爬蟲
  • 瀏覽量

搞定 Meta Tag / Meta Data

Title Tag: 當前頁面的標題,關鍵字請往前放,字數不宜過長,否則有可能視為權重作弊,建議

  • 英文字數 70 以內
  • 中文字數 40 以內

範例

<title> Rails 網站 SEO 實務技巧 | Nic 的部落格</title>

Meta Description Tag: 每個頁面的敘述,文案好壞間接影響點閱率

  • 含標點符號的英文在 150 字內
  • 中文十二字內,關鍵字往前放

範例:

<meta name="description" content="SEO不是玄學,是一個 Rails Developer 該學會的搜尋引擎優化技巧">

以上兩個標籤非常重要,每一個頁面一定要不一樣,如果過多重複,搜尋引擎會認為你在作弊,是會扣分的哦

Canonical Url: 告訴搜尋引擎關於這個頁面的標準網址,有時候我們一個頁面會帶很多參數,可能是語系、搜尋參數、等等,這個標籤可以讓搜尋引擎只收錄正確的網址就可以了

例如:

https://www.example.com/products?category=dresses&color=green

後面就夾帶了 category 與 color 的參數傳遞,這樣的網址看起來會非常噁心

那麼 Canonical Url 這個 tag 就可以這樣寫

<link rel="canonical" href="https://www.example.com/products" />

這樣子就不會把一些帶有參數的網址收錄到搜尋引擎下了

Facebook Open Graph: 這是專門給 Facebook 抓取用的,可以在這些 meta tag 裡面調整你要分享出去的內容長什麼樣子,如果都沒有設定,他會預設抓取你的 title description tag 來作為參考。

可以用 Facebook debug tools 來作為偵錯改善的工具

Facebook 分享縮圖設定範例:

<meta property="og:image" content="https://example.me/img/about.jpg">

Twitter Cards: 這個就是專門給 Twitter 抓的,一樣可以調整分享後的樣式。

在 Rails 實作部分可以用

讓 URL 具有意義

禁用連結,如果有一些連結你不想被計入SEO,那麼你可以使用 nofollow 標籤。

情況大概會是,付費連結、留言裡的連結,或是登入登出這種沒有必要被收入的連結會用到

範例

link_to "不想被收錄", awesome_path, rel: 'nofollow'

關於 nofollow

在一般 CRUD 出來的網址,通常都會是 http://example.com/products/1

那這個產品的URL是用數據庫的 ID 作為依據,這樣一來光看 URL 是沒有任何意義的,也無法提升權重,比較好的作法是讓網址具有意義

http://example.com/products/aeron-chair
http://example.com/products/xbox-360-white

這樣一來,不僅網址就能夠看得出來這個 product 是什麼,也能提升搜尋的權重。

怎麼做呢?

在 Rails 上面可以使用 friendly_id 這隻 Gem,

詳細的實作我在之前有寫過一篇 [Gem] 使用 Friendly_id 與 Babosa 美化你的Rails 網址

在這裡面的教學還可以讓 URL 帶中文的 slug。

幾點需要注意的是,如果要提升網域的權威性,請在主網域使用
例如:

http://example.com/products/aeron-chair

會比

http://shop.example.com/products/aeron-chair

來的更好,因為他可以直接提升 http://example.com 的網域分數

慎用 HTML tag

HTML tag 如果亂用,例如在同一個頁面有一大堆 h1,是相當扣分的行為

因為 HTML tag 其實就是拿來替你的網頁劃清楚的重點,他是一個「具有意義的語言」

HTML 當初在設計時,本身就帶有「語意」

你可以這樣理解

  • h1 是大標題
  • h2, h3 是中標題
  • h4, h5, h6 是小標題
  • p 是指 paragraph (段落)
  • strong 是重點部分,他與 b 不同的是, b 只有粗體,但 strong 是加重語語氣,如果是盲人使用閱讀器在觀看你的網站時,加重語氣可以讓輔助導讀的軟體能夠正確的念出這段話
  • img 中的 alt 是指替代文字, title 是標題

開發者希望他的網站能夠正確被收錄,那麼你就應該,正確的使用 HTML 語意標籤。

by the way, <div> 是對SEO沒有特別的加分或扣分效果,因為從以前就很流行以 div + CSS 來達到網站前端畫面開發,畢竟 div 沒什麼包袱嘛,他就是區塊囉

所以 <div><span> 是沒有語意的,他只是容器

規則標準:

  • h1 一個頁面只能有 1 個
  • h2 一個頁面只能有 2~3 個
  • h3 一個頁面可以有 6~10 個

HTML 標籤權重由高至低

title > meta description > h1 > h2 > h3 > strong > p

採用 HTTPS 連線

因為近年來非常重視資安問題,尤其現在很多金流行為是可以透過網站傳輸完成的,這當中可能會有輸入信用卡等交易的機密資料,那麼用 HTTPS 可以避免被第三方監聽,因為這過程是加密傳輸的。

HTTPS 是指你的網站有沒有安裝 SSL(Secure Sockets Layer , 傳輸層安全協議),如果你有確實安裝SSL的認證,通訊協定就會從 HTTP 改變為 HTTPS(Hypertext Transfer Protocol Secure , 超文字安全傳輸協定)。

在 2014 年時, Google 官方正式宣布 HTTPS 已被列入搜尋引擎演算法之一

但如果你的網站只是「內容型網站」,沒有涉及到用戶資安問題(例如交易),就目前來說,不使用 HTTPS 並不會有「超大的影響」。

不過值得注意的是,從 2017 年 10 月起,從 Chrome 62 開始,只要是需要輸入任何資料的網頁,若未使用HTTPS,即會被標示為不安全。

參考 Google Chrome 官方文章

也因為 SSL 的憑證基本上都是要錢的,但也有很佛心的公司 Let's Encrypt 大力推廣這種加密憑證的安全,也有釋出三個月免費的版本,過期的話就要重新簽名。

申請憑證的流程大致上是:

  1. 先自己生成一個私鑰、一個申請用的憑證CSR
  2. 將 CSR 傳上去給憑證機構
  3. 憑證機構認證你真的擁有這個網域
  4. 下載 SSL 憑證
  5. 將私鑰和 SSL 憑證安裝到網站服務器上

如果主要客戶不是在中國,那麼可以更簡單的申請使用 Cloudflare 這個 CDN 服務,這包含免費的 SSL 憑證,可以免去申請安裝的麻煩。它的 Flexible SSL 模式,可以讓終端使用者到 CloudFlare 是加密連線,而你的服務器不需要安裝。使用 Cloudflare 需要將 DNS 也給它管理,在註冊的流程中會要求你去修改 Namecheap 改用 Custom DNS。

支援 RWD 響應式網頁設計

做 RWD 的好處是可以保有連結單一性,讓原本的流量不會因為行動裝置而沖散,因為這個連結的瀏覽量會讓他的權重變高,更容易出現在搜尋排名上面,但如果分別做 mobile/desktop 版,就會有兩種不同的網址連結,會讓 SEO 的效果打折。

RWD,也不是有做就好,而是會去依照載入速度有不一樣的評分。

詳細可以到 Google 提供的簡易測試工具來測試一下你的讀取秒數吧,依照 Google 說法是人的耐性只有三秒XD,那就盡可能的壓到 3 秒以下吧 XDrz。

網站開啟速度

通常一個網站會慢多半不是後端處理 query 的速度,都會是在前端資源情況吃太緊,既然要作最佳化就必須要用科學化的方式量測,這樣一來我們才知道最佳化有沒有成功。

Chrome 的除錯器本身就很詳細,可以觀察 Network 在 Load page 花了多長的時間。

對前端來說,重視的是 Page load time, 而不是單一 request 的時間,因為網頁的組成有 HTML + CSS + JavaScript

Google 提供的好用工具 PageSpeed Insights

PageSpeed Insight 評分標準

或是用 Chrome 外掛 Lighthouse

都有清楚的報表以及提示你如何改善。

知識點,在 HTTP2 以後,有 async 和 defer 屬性可以用。

以往的關鍵渲染會是把 <script> 移出 <head>, 讓 CSS 優先加載達到畫面快速渲染,在載入能夠有互動式效果的 JS file。

不過新的屬性可以讓瀏覽器知道要異步加載

例如

app/views/layout/application.html.erb
-  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload', :async => true %>

或是

app/views/layout/application.html.erb
-  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload', :defer => true %>

async 與 defer 差別在於

  • async, 瀏覽器不會阻擋 HTML 渲染,當 JavaScript 下載完時就會直接執行,不會等 HTML DOM加載
  • defer, 瀏覽器也不會阻擋 HTML 渲染,但是當 JavaScript 下載完成後,會等 HTML 加載完成,才會執行 JavaScript。如果你的 JavaScript 裡面有依賴 DOM 的話,適合用這個方式。

CSS 也被瀏覽器視為一種 render blocking 的資源,當瀏覽器解析 HTML 看到 <link href="style.css" rel="stylesheet" 時,就會等待完整解析這個 CSS 後才會繼續渲染頁面。

語系與國家

如果你的網站本身有支援多語系,那麼沒有設定好的情況會變成說,不論哪個國家的搜尋引擎搜到你,進來都還要自己調整語系,而且在 google 搜尋結果上面也會變得怪異,例如:美國 IP 搜出來的標題是中文之類的。

所以別忘記在你的 html tag 裡面加入

<html lang="zh">

可以參考 Apple 官方網站的作法,該網站是很標準的能夠支援多語系 SEO 的寫法,具體如下

<link rel="canonical" href="https://www.apple.com/">
<link rel="alternate" href="https://www.apple.com/" hreflang="en-US">
<link rel="alternate" href="https://www.apple.com/ae-ar/" hreflang="ar-AE">
<link rel="alternate" href="https://www.apple.com/ae/" hreflang="en-AE">
<link rel="alternate" href="https://www.apple.com/am/" hreflang="en-AM">
...

這樣就能夠讓搜尋引擎正確在每個國家直接顯示相對應語系的網站了。

這個方法不是唯一,你也可以選擇用 Sitemap 的方法來讓搜尋引擎正確收錄。

擇一即可。

Sitemap

Sitemap基本是描述一個網站的架構,它可以是任意形式的網頁頁面,通常是採用分級或分類的形式,能夠消化龐大的往架構,對於網站的訪問者在網站瀏覽上的資訊需求也能快速獲得,且能提高SEO搜尋引擎優化的效果,對網站的幫助是非常大。

參考 Apple 繁體中文官網的 Sitemap

在這部份用 Gem "sitemap_generator" 可以輕鬆幫你完成,並且有 rake task 能夠讓你每次產生新內容的時候更新上去。

爬蟲

在 Rails 的路徑 public/robots.txt 是專門拿來給搜尋引擎爬蟲讀取的,這隻檔案會告訴他什麼資源不需要被收錄。

參考 Apple 官方網站的 robots.txt

不過有時候我們除了 Production 環境以外,可能還有 Staging ,避免被收錄的話可以依照環境動態生成 robots 這隻檔案。

config/routes.rb
get '/robots.txt' => 'home#robots'
home_controller.rb
def robots                                                                                                                                      
  robots = File.read(Rails.root + "config/robots.#{Rails.env}.txt")
  render :text => robots, :layout => false, :content_type => "text/plain"
end

分別在 public/ 下生成 robots.staging.txt, robots.production.txt

就可以做到環境控制 robots.txt 生成囉

參考 Gist

瀏覽量

你的網域權重的提升也會因為瀏覽量的高低有不一樣的排名,所以適時的導流進去,讓更多用戶喜歡上這個產品能夠不斷回訪網站,會讓權重分數慢慢往上爬,這就跟養小孩一樣,需要一點時間才會成長。

Google 是會將大量的垃圾連結或不優質的內容降低權重的,所以非常重視網站經營者生成的內容,他們的演算法會一直更新,阻擋那些嘗試作弊欺騙權重的網站,最後一點就是認真經營網站囉。

參考資源

 
about 2 months ago

平常我在記筆記的時候,最常使用的是 MWeb 、 Evernote,之所以會特地分兩款的原因是因為 evernote 不支援 markdown 書寫。

對於我這種已經寫習慣的人,實在是很難轉換他的 syntax,不過好玩的是, Sublime 裝了 Plugin 就可以解決這件事情了,還可以做到雙向轉換

  • 在 sublime 寫 markdown
  • 在 evernote 是正常的 syntax

安裝

sublime-evernote

  • 打開 Sublime 用 Install Package 安裝
  • 設置關聯,在此之前要去申請一組 API,點擊 Create a developer token
  • 打開 Preferences > Package Settings > Evernote >Settings - User

填入你的 API 資訊

{
 "noteStoreUrl": "",
 "token": ""
}

通過呼叫 Command + Shift + P,輸入 Evernote,後顯示 Evernote 選單,表示安裝成功。

點擊Evernote:list recent notes,如果看到選單中出現最新的筆記,則說明授權成功!

常用選項

  • Evernote: List resent notes 最近幾筆筆記
  • Evernote: New empty note 創建筆記
  • Evernote: Search note 搜索筆記

嫌麻煩?那就用Marxi吧~

使用心得

其實我寫這篇文還是用了 MWeb,想說也許有人需要就寫了,我自己還是用不習慣,主要有兩點

  • 我希望能夠即時預覽 (MWeb can do it!)
  • Sublime 不知道要寫到哪裡才會換行,有時候螢幕太寬會忘我 XD
參考資源
 
about 2 months ago

Rails 5 實作第三方登入 (Facebook + google)

雖然我去年寫過一篇如何在 Rails 4 Devise使用Google實作登入

但後來要用的時候,發現原本的文章只能對應一種第三方登入,如果要實作多個第三方登入在同一個網站的時候,可擴充性就顯得相當重要了。

再加上這種套件或是API接口的升級變化越來越快,還是寫一篇新的吧(應該不會每年都寫一篇啦XD)

Gem install

Gemfile
gem `devise`
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'
gem 'koala' # 可以將錯誤的 session 刪掉避免註冊失敗

因為 devise 有支援第三方登入,所以實作起來並不特別難。

申請官方接口 Facebook

先去 Facebook 開發者官方註冊,如果已有帳號直接登入應該就行了。

然後右上角,下拉選單選擇「新增應用程式」

輸入你的 app name

接著會跳轉到這個 app 下的後台設定,點擊左邊 Side bar 去建立新平台

新建網站平台

輸入網址,這邊注意一下,如果你想要先在 localhost 測試,可以先填入 http://localhost:3000 ,等到上去正式站或測試站在調整正確網址就行了,因為這個欄位是 Facebook 要確定打回去的 redirect url 是否跟設定一樣

最後 Deploy 上去要給別人測試的時候,記得到這裡把應用程式設定公開,否則只有開發者帳號能夠用 Facebook 的第三方登入哦

申請官方接口 Google

Google 開發者官方申請 API

點下拉選單來新增一個應用程式的專案

點擊「+」符號

接著會讓你輸入這個專案的名稱,輸入完之後會跳回原本的畫面,在選一次下拉選單,應該就會看到他正在建立了,等待建立好(約十秒)就可以點進去

然後啟用 Web Fonts Developer API and Google+ API

點擊新增 API 就會出現搜索欄可以找了,

找到之後點選啟用,接著會叫你建立憑證,憑證建立後就會拿到 api key 和密鑰

Controller 設定

新建 Users::OmniauthCallbacksController

指令 rails g controller users::omniauth_callbacks

代碼如下

app/controller/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def facebook
    @user = User.from_omniauth(request.env["omniauth.auth"], current_user)
    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication
      set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
    else

      if @user.email.nil?
        @user.delete_access_token(request.env["omniauth.auth"])
        redirect_to new_user_registration_url, alert: "需要您同意 Email 授權唷!"
      else
        session["devise.facebook_data"] = request.env["omniauth.auth"]
        redirect_to new_user_registration_url
      end
    end
  end

  def google_oauth2
    @user = User.from_omniauth(request.env["omniauth.auth"], current_user)

    if @user.persisted?
      flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.google_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to new_user_session_path, alert: "無法獲得驗證!"
  end
end

建立一個紀錄第三方資料的 model

rails g model identity user:references provider:string uid:string

rake db:migrate

初始化 model

app/models/identity.rb
class Identity < ApplicationRecord
  belongs_to :user

  validates_presence_of :uid, :provider
  validates_uniqueness_of :uid, :scope => :provider

  def self.find_for_oauth(auth)
    find_or_create_by(uid: auth.uid, provider: auth.provider)
  end
end

驗證如果有相同 email 帳戶直接登入,沒有的話新建一個並登入,如果你有用驗證信請記得要 skip confirm

app/models/user.rb
devise: :omniauthable, :omniauth_providers => [:facebook, :google_oauth2]

  def self.from_omniauth(auth, signed_in_resource = nil)
    identity = Identity.find_for_oauth(auth)

    user = signed_in_resource ? signed_in_resource : identity.user

    if user.nil?
      email = auth.info.email

      user = User.where(email: email).first if email

      if user.nil?
        user = User.new(name: auth.info.name.gsub(/\s+/, '_'),
                        email: auth.info.email,
                        avatar: auth.info.image,
                        password: Devise.friendly_token[0,20])
        user.save!
      end
    end

    if identity.user != user
      identity.user = user
      identity.save!
    end

    user
  end

  def delete_access_token(auth)
    @graph ||= Koala::Facebook::API.new(auth.credentials.token)
    @graph.delete_connections(auth.uid, "permissions")
  end

devise 的 routes 用我們剛建立的 controller 覆蓋

config/routes.rb
  devise_for :users, :controllers => { omniauth_callbacks: "users/omniauth_callbacks" }

將你拿到的第三方金鑰與密碼填在這裡,如果不想把密鑰寫在 commit 裡面,可以使用 dotenv 這個 gem 來管理變數

app/config/initializers/devise.rb
Devise.setup do |config|
...
  config.omniauth :facebook, "KEY", "SECRET"
  config.omniauth :google, "KEY", "SECRET"
...
end

到這邊其實就好了,你會疑問,那 link button 呢?,可以到 devise 註冊頁面看一下,應該會有連結了,因為他在原先預設的頁面裡面就有提到這第三方登入,請參考

app/views/devise/shared/_links.html.erb
<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
  <% end -%>
<% end -%>

如果你要任意放你的 link buuton 就用

Google user_google_oauth2_omniauth_authorize_path
Facebook user_facebook_omniauth_authorize_path

如果要擴充其他第三方,就依照此模版繼續新增就可以了

參考來源:

 
about 2 months ago

Implement background animation colors for comments(like stack over flow share answer link)

Rails version 5.1.3
Ruby version 2.3.2

原本如果實作一個 Post has_many Comments 的 CRUD

最陽春的版本就是在留言之後,跳出一個 flash 顯示

「你已留言成功」

不過因為是用 http post,所以在 controller action 就會 redirect_to 一個 path 回來,

在這裡我們可以用

  • Rails 4 redirect_to :back
  • Rails 5 redirect_back(fallback_location: root_path)

來做到留言後返回同一頁。

不過看似沒有導去任何的頁面,但是頁面卻彈到最頂部了。

因為這其實是在重新 load 當前頁面。

所以當留言越來越長,我們每次留言完就會又彈回頂部。這樣實在太糟糕了

秉持著現代流行的 SPA 精神,我們用 ajax 來完成吧

目標讓留言系統有

  • Load more 按鈕載入更多留言
  • ajax 留言
  • 留言後下拉至留言處,並對留言區塊產生顏色漸層

對於漸層從亮色到底色這種標記區塊的方法,是從 StackOverFlow 分享 answer 裡面看到的,所以就思考了一下並實作,你可以點看看這個在 stackoverflow 經典的答案分享體驗一下。

Gem install

Gemfile
gem 'jquery-rails'
gem 'will_paginate', '~> 3.1'
application.js
//= require rails-ujs

//= require jquery

Load more 按鈕載入更多留言

CRUD 這邊就略過不贅述了 XD

在 post#show 顯示留言,直接先分頁

app/controllers/posts_contoller.rb
def show
    @comments = @post.comments.paginate(page: params[:page], per_page: 5).order('created_at DESC')

    respond_to do |format|
      format.html
      format.js
    end
end

先寫 helper

app/helpers/application_helper.rb
  def render_ajax_more_comments_link(post, comments)
    link_to("SHOW MORE", post_path(post, page: comments.next_page), remote: true, class: 'btn coin-comment__load-more-btn', id: "js-load-more-comments" )
  end

再 show 頁面使用這個 load more 按鈕,並且在 form 的地方補上 remote: true

app/views/posts/show.html.erb
// 留言表單獨立出來做 partial
<%= render "comment_form"%>

// Load more 按鈕
<% if @comments.next_page %>
  <div class="coin-comment__load-more">
     <%= render_ajax_more_comments_link(@post, @comments) %>
  </div>
<% end %>

comment form 的 partial

app/views/posts/_comment_form.html.erb
<%= simple_form_for [@post, Comment.new], remote: true, html: { id: 'js-comment-form' } do |f| %>

    <%= f.input :message %>

    <%= f.submit "COMMENT", class: "btn btn-sm", data: { disable_with: "COMMENT..." } %>
<% end %>

設定 js.erb

app/views/posts/show.js.erb
$('#js-comments-list').append("<%= j render @comments %>");

<% if @comments.next_page %>
  $('#js-load-more-comments').replaceWith('<%= j render_ajax_more_comments_link(@post, @comments) %>');
<% else %>
  $(window).off('scroll');
  $('#js-load-more-comments').remove();
<% end %>

Load more button 示範

ajax 留言

controller 在 create 留言時會紀錄哪位使用者留言的

app/controllers/comments_controller.rb
  def create
    @post = Post.friendly.find(params[:post_id])
    @comment = @post.comments.build(comment_params)
    @comment.user = current_user

    respond_to do |format|
      if @comment.save
        format.html { redirect_to(post_path(@post)) }
        format.js
      else
        format.html { redirect_back(fallback_location: root_path) }
        format.js
      end
    end
  end

因為 create 行為沒有 html.erb ,不過要有 js 行為,所以

app/views/comments/create.js.erb
$('#js-comments-list').append("<%= j render @comment %>");

// clear form input 留言後能夠把 form 上面的 input 欄位清掉

$('#js-comment-form')[0].reset();

// scroll to bottom 依照當前網頁高度,去計算拉到底部的高度,這邊採用 jquery 裡面的 animate 實作

height = $('#js-comments-list').offset().top;
$('html, body').animate({scrollTop : height+ $('#js-comments-list').height()},500);

// comment moment height light 先給予顏色,之後再用 animate 將顏色動畫漸變回底色

$('#js-comments-<%= @comment.id %>').css("backgroundColor", "rgba(255, 210, 46, 0.39)");
$('#js-comments-<%= @comment.id %>').animate({backgroundColor: "#fff"}, 800);

由於原本的 jquery 是沒有加入 backgroundColor 的功能,這是額外的擴充,所以請在 layout 裡面加入

一定要在 javascript 載入之後再載入 jquery.color-animation ,因為他是依賴在 jquery 之下

app/views/layout/appliaction.rb
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<script src="//cdn.jsdelivr.net/jquery.color-animation/1/mainfile"></script>

記得做一塊 partial ,裡面是每一個 comment

app/views/comments/_comment.html.erb
<div id="js-comments-<%= comment.id %>">
  <%= comment.user.name %>
  <%= comment.message %>
</div>

這邊的 js-comments-<%= comment.id %> 用意是為了能夠辨別每一個唯一的 comment 加上的標籤,這樣一來,我們就可以做到留言後自動標記了

到這邊,應該可以實現如下圖的功能了

 
about 2 months ago

CSS開發規範

命名禁止縮寫

請精簡扼要的對 class 命名,請勿使用自定義縮寫

class name 的命名必須是行為、有語意的

禁止在非特殊情況寫 !important

CSS裡面本有權重設計,這樣一來任意的使用 !important 會造成權重混亂而無法維護,最後的情況就是互相
!important來!important去

不可輕易侷限寬高

請不要忘記用戶可以設定自己的瀏覽器,例如 andorid 的手機可以設定顯示字體大小,寫死的高度會讓字體相互重疊

RWD 失效

行動裝置(手機, ipad)的高是無限,寬是有限

請不要把寬寫死

當你在桌機板把高寫死

  • 頂多就是往下掉

當你在桌機板把寬寫死

  • 手機版就破版
  • 手機版就破版
  • 手機版就破版

img 請讓他自動縮放

請不要替 img 的容器設定寬或高,請讓他依裝置縮放

請使用 bootstrap 的 img-responsive

如果你非要設定請用

width:100%;
height: auto;

假設如果要給 img border-radius 的 style

請用父元素控制行為,保持 img 只載入圖片,不有樣式

優先使用 grid 排版

請不要花一堆時間在寫 media query,定一堆 breakpoint,自己寫元件樣式,自己控制每種裝置上的容器寬度。

請使用 grid sysytem,這些都是已經成熟的框架,而且有些也已經幫你處理了瀏覽器相容問題,請不要引入了然後都不使用,都自己寫

不可直接 over write 或加料在原本框架的 class

請直接寫一個新的 class,不要覆蓋原有的設計,舉例來說

如果你真的很討厭 bootstrap container 自帶的 padding

那你可以寫一隻

.no-padding {}

在使用的情況下

<body class="container-fluid no-padding">
</body>

可以一目了然這是沒有 padding 的 container。

也請不要在以有的 class 上加料,請額外單獨寫一個

.container-fluid {
 font-size :18px
}

手機先決 如果製作 RWD網站,設計請以手機排版優先設計

設計師,設計順序,以桌面版優先,再設計手機版
前端工程師,拿到視覺圖,欲刻 HTML / CSS 時以手機版為第一優先

手機開啟網頁很吃手機效能和網路狀況
關於前端工程師開發
一開始就以Mobile為優先,
可以讓 HTML一開始載入,在不必推移下,動用最少的效能快速載入網頁
當你開始製作 Desktop 時,只會些微跑版,畫面不易跑版到無法找到的位置。

情況反過來,先作桌面版時,當手機版畫面被切掉或是跑位
你需要花更多時間去通靈,要移動多少px才能回到畫面上?

再來是Iphone 上的 retina ,會將圖片放到手機上時,自動做兩倍縮小
在一開始製作時即可發現圖片載入是否吃效能
那也當然為了讓圖片能在iphone 上有更好的體驗,會花較多工時
所以建議Mobile first

Mobile First 優點是
在 mobile 時沒有任何 media query 載進來

當偵測到 media query 時,表示效能已經提升了(因為已經不是行動裝置了)

RWD 精隨

行動裝置(手機, ipad)的高是無限,寬是有限

請不要把寬寫死

當你在桌機板把高寫死

  • 頂多就是往下掉

當你在桌機板把寬寫死

  • 手機版就破版
  • 手機版就破版
  • 手機版就破版

不可使用 html tag selector

樣式不必要讓子子孫孫都背負,請直接定義 class 的樣式就好,不需要指定 html tag。
如果我要換 html tag, 那 CSS 不就要跟著改?

層級不可以超過三層

超過表示耦合度太高,不具有彈性、可維護性

用一樣的 Element 時不要把一堆東西全部寫在裡面,請把排版相關的獨立出來

把 border-radius 做在 img 上面,請把 img 保持乾淨,
定位,例如 postition absoulate

不要隨意 none 掉畫面上的 tag 或行為

請注意如果要 none 掉一些樣式,請依照使用程度決定

使用程度遍及整個網站,請直接做 reset.css

使用程度中等以下,請定義一個 class

例如 Rails 裡面的 simple form

<%= f.input :title %>

他會多帶一個 label tag 出來,如果你想關掉,請直接寫

<%= f.input :title, label: false %>

reset.css

常見的

  • a tag 不要有 underline
  • list 消除原有樣式

請在 reset.css 上定義,並且設為第一隻載入

(套用框架 bootstrap 可以在文件上統一 overwrite 掉)

有 JavaScript 行為的 class 可以為命名加入 name space

#js-project-show {}

請勿任意使用 br hr tag

br是換行,請使用在 p tag 裡面,當 p 裡面文字過多時可以使用。
hr 是快速劃線,但是快被時代淘汰了,請直接用 border 寫在 class 裡面

br 必須去思考父區塊是不是 display:block;,如果要換行,應該思考是不是下一段文字?

線條請都使用 border 去寫

 
about 2 months ago

Infinite Scrolling to load more records from database using Ruby on Rails

Gems installed

gem 'will_paginate', '~> 3.1'

gem 'bootstrap-sass', '~> 3.3', '>= 3.3.6'

gem 'bootstrap-will_paginate', '~> 0.0.10'

Gem removed

gem 'turbolinks'

建立 Post Controller

運用場景:

Post#index 的 view ,可以無限下拉來達到 Load more

controller 要能 respond JS

class PostsController < ApplicationController
  def index
    @posts = Post.paginate(page: params[:page],
                           per_page: 5).order('created_at DESC')

    respond_to do |format|
      format.html
      format.js
    end
  end
end
assets/javascripts/infinite_scrolling.js
$( document ).ready(function() {

  //infinite scrolling

  if ($('.loading-next-page').size() > 0) {
    $(window).on('scroll', function() {
      more_posts_url = $('.loading-next-page').attr('href');
      if (more_posts_url && ($(window).scrollTop() > $(document).height() - $(window).height() - 60)) {
        $('.loading-next-page').replaceWith('<p class="loading-next-page">Load more...</p>');
        $.getScript(more_posts_url);
      }
    });
  };
});

先做一個 Ajax helper

app/helpers/application_helper.rb
  def render_ajax_next_page_link(posts)
    link_to("next page", posts_path(page: posts.next_page), remote: true, class: 'btn btn-primary loading-next-page hidden' )
  end

class 裡面的 hidden 可加可不加,不加的話在 Load more 會有一瞬間跑出來這個按鈕就是了

app/views/posts/index.html.erb
<div id="listing-infinite-scrolling">
    <%= render @posts %>
</div>

<div>
    <%= render_ajax_next_page_link(@posts) %>
</div>

記得把 partial 補上

app/views/posts/_post.html.erb
<h2><% post.title %></h2>
<p><% post.description %></p>

這邊 tag 跟樣式可以自訂,本文章不多贅述

寫 js.erb

app/views/posts/index.js.erb
$('#listing-infinite-scrolling').append('<%= j render @posts %>');

<% if @posts.next_page %>
  $('.loading-next-page').replaceWith('<%= j render_ajax_next_page_link(@posts) %>');
<% else %>
  $(window).off('scroll');
  $('.loading-next-page').remove();
<% end %>

接下來應該可以在 post#index 頁面,做到無限捲軸下拉載入了

如果要做成用按鈕載入新的樣式,只要把 JS 行為拔掉,改用 render_ajax_next_page_link 這個 helper 出來的 button 就可以了(記得把 class 裡面的 hidden 移除掉,否則你會看不到這個按鈕)

Done.

參考來源

3 ways to load more records from database using Ruby on Rails

 
about 2 months ago

我們平時最開始學習寫 CSS 代碼的時候,我們都是這麼寫的:

.main-part .tabs {
  /*...*/
}

其實這樣的寫法是不好的,因為它過度的與頁面中的某一部分耦合了。我們在軟件工程的思想中追求的就是解耦。

BEM 風格不只一種

BEM 的命名規範不是死的,可以參考 bem-conventions

這裡有一些可供選擇的基於 BEM 命名約定的方案。

Two Dashes style(雙連字符風格)

block-name__elem-name--mod-name

  • 名字全部使用小寫。
  • BEM 實體的名稱中的每一個單詞使用一個連字符分隔。
  • 使用雙下划線(__)將元素的名稱和模塊的名稱分離開。
  • 使用雙連字符(--)分隔 Boolean 類型的修飾符。
  • 不使用 key-value 類型的修飾符
  • 重要提示!在注釋中,雙連字符被視為注釋的一部分,因此在文檔驗證時,雙破折號可能會引起錯誤。HTML5 Specification

CamelCase style(駝峰命名風格)

MyBlock__SomeElem_modName_modVla

這種風格的命名方案的不同點在於,在 BEM 實體中分隔單詞時使用駝峰命名法代替了一個連字符(-)。

"Sans underscore" style(無下划線風格)

blockName-elemName--modName--modVal

名稱使用駝峰命名法書寫。
元素名稱與模塊名稱使用一個連字符(-)分隔。
修飾符使用雙連字符(--)與模塊或元素分隔,
修飾符的名稱和值使用雙連字符(--)分隔。
重要提示!

在注釋中,雙連字符被視為注釋的一部分,因此在文檔驗證時,雙破折號可能會引起錯誤。HTML5 Specification

BEM 說明(這裡用 Two Dashes style(雙連字符風格))說明

在這邊使用 BEM 的命名規範,每行 CSS 代碼都只有一個選擇器

BEM代表 區塊(block),元素(element),修飾符(modifier)」,我們常用這三個實體開發組件。

在選擇器中,由以下三種符合來表示擴展的關係:

-   dash :僅作為連字符使用,表示某個塊或者某個子元素的多單詞之間的連接記號。
__  Two underline:雙下划線用來連接塊和塊的子元素
--  Two dash:雙減號用來描述一個塊或者塊的子元素的一種狀態

type-block__element--modifier

區塊(block)

一個區塊是設計或佈局的一部分,它有具體且唯一地意義 ,要麼是語義上的要麼是視覺上的。

在大多數情況下,任何獨立的頁面元素(或複雜或簡單)都可以被視作一個區塊。它的HTML容器會有一個唯一的CSS類名,也就是這個區塊的名字。

針對塊的CSS類名會加一些前綴( ui-),這些前綴在CSS中有類似 name space 的作用。

一個區塊的定義有下面三個基本原則:

  1. CSS中只能使用類名(不能是ID)。
  2. 每一個區塊名應該有一個命名空間(前綴)
  3. 每一條CSS規則必須屬於一個區塊。

例如:一個自定義列表 .product 是一個區塊,通常自定義列表是算在 mod 類別的,在這種情況下,一個 product 列表的block寫法應該為:

.product 

元素(element)

塊中的子元素是塊的子元素,並且子元素的子元素在 BEM 里也被認為是塊的直接子元素。一個塊中元素的類名必須用父級塊的名稱作為前綴。

如上面的例子,li.item 是列表的一個子元素,

.list{}
.list .item{}


.list{}
.list__item{}

這裡可以使用偽嵌套寫成

.list {
    &__item {}
}

修飾符(modifier)

一個「修飾符」可以理解為一個塊的特定狀態,標識著它持有一個特定的屬性。

用一個例子來解釋最好不過了。一個表示按鈕的塊默認有三個大小:小,中,大。為了避免創建三個不同的塊,最好是在塊上加修飾符。這個修飾符應該有個名字(比如:size )和值( smallnormal 或者 big )。

如上面的例子中,表示一個選中的列表,和一個激活的列表項

原本的寫法

.list {}
.list.select {}
.list .item {}
.list .item.active {}
    
用 BEM 的寫法

.list {}
.list--select {}
.list__item {}
.list__item--active {}

用偽嵌套優化之後為

.list {
  &--select {}
  &__item {
    &--active {}
  }
}

使用偽嵌套

使用 & 符號進行偽嵌套

.product {}
.product__item {}

等同於

.product {
    &__item {}
}

嵌套不得大於兩層

原則以 B + E + M 完成,不得使用 B + E + E + M,或是 B + B + E + M 之類的寫法

兩層是指從 sass compiler 後的 CSS 不得有超過兩層階層

.product {
  &__item {
    &--selected {}
  }
}

上面的 SASS 寫法這樣其實也才一層而已哦,因為 compiler 出來之後是

.product {}
.product__item {}
.product__item--selected {}

適當的使用 >

.list {
  &__item > a {}
}

這樣寫只會在 list__item 下一階層的第一個 a tag 會有效果而已,如果在這底下有兩個 a tag ,第二個會失效,這是特別要注意的點。

不要使用 Tag selector

BEM是不允許用 Tag selector 的

.menu li 能搞定的事情需要每個 li 都寫 .menu-item

優點:
就是避免 li 裡的 li 受影響
舉個例子,商品詳情頁是允許商家自定義標籤的,那麼商家展示區域標籤的祖先元素一旦用標籤選擇器定義了樣式,子子孫孫都要背負.

所以十分贊同在無法百分百確定不會嵌套同樣標籤的情況下不用 Tag selector

BEM 解決問題

組件之間的完全解耦,不會造成命名空間的污染,如: .mod-xxx ul li 的寫法帶來的潛在的嵌套風險。

性能

BEM 命名會使得 Class 類名變長,但經過 gzip 壓縮後這個帶寬開銷可以忽略不計

延伸閱讀 - 常用的CSS命名規則

  • 頭:header
  • 內容:content/container
  • 尾:footer
  • 導航:nav
  • 側欄:sidebar
  • 欄目:column
  • 頁面外圍控制整體佈局寬度:wrapper
  • 左右中:left right center
  • 登錄條:loginbar
  • 標誌:logo
  • 廣告:banner
  • 頁面主體:main
  • 熱點:hot
  • 新聞:news
  • 下載:download
  • 子導航:subnav
  • 菜單:menu
  • 子菜單:submenu
  • 搜索:search
  • 友情鏈接:friendlink
  • 頁腳:footer
  • 版權:copyright
  • 滾動:scroll
  • 內容:content
  • 標籤:tags
  • 文章列表:list
  • 提示信息:msg
  • 小技巧:tips
  • 欄目標題:title
  • 加入:joinus
  • 指南:guide
  • 服務:service
  • 註冊:regsiter
  • 狀態:status
  • 投票:vote
  • 合作夥伴:partner

注釋的寫法:

/* Header */
內容區
/* End Header */

Id的命名:

頁面結構
  • 容器: container
  • 頁頭:header
  • 內容:content/container
  • 頁面主體:main
  • 頁尾:footer
  • 導航:nav
  • 側欄:sidebar
  • 欄目:column
  • 頁面外圍控制整體佈局寬度:wrapper
  • 左右中:left right center
導航
  • 導航:nav
  • 主導航:mainnav
  • 子導航:subnav
  • 頂導航:topnav
  • 邊導航:sidebar
  • 左導航:leftsidebar
  • 右導航:rightsidebar
  • 菜單:menu
  • 子菜單:submenu
  • 標題: title
  • 摘要: summary
功能
  • 標誌:logo
  • 廣告:banner
  • 登陸:login
  • 登錄條:loginbar
  • 註冊:register
  • 搜索:search
  • 功能區:shop
  • 標題:title
  • 加入:joinus
  • 狀態:status
  • 按鈕:btn
  • 滾動:scroll
  • 標籤頁:tab
  • 文章列表:list
  • 提示信息:msg
  • 當前的: current
  • 小技巧:tips
  • 圖標: icon
  • 注釋:note
  • 指南:guild
  • 服務:service
  • 熱點:hot
  • 新聞:news
  • 下載:download
  • 投票:vote
  • 合作夥伴:partner
  • 友情鏈接:link
  • 版權:copyright

注意事項::

  1. 一律小寫;
  2. 盡量用英文;
  3. 盡量不縮寫,除非一看就明白的單詞。

CSS樣式表文件命名

  • 主要的 master.css
  • 模塊 module.css
  • 基本共用 base.css
  • 佈局、版面 layout.css
  • 主題 themes.css
  • 專欄 columns.css
  • 文字 font.css
  • 表單 forms.css
  • 補丁 mend.css
  • 打印 print.css
 
2 months ago

在這邊我們預計實現團隊中使用 sublime + Atom 的開發人員都能夠對 .scss 做自動縮進,並且效果必須一樣。

  • 縮進書寫規範:兩格空白

每個團隊或個人使用的 CSS 規範不盡相同,可以在細節部分自行調整,也可以參考一些CSS编码规范

有的人喜歡四格縮進,也有喜歡兩格的。

Atom

安裝 atom-beautify

command line 裡面輸入 apm install atom-beautify

安裝完畢之後,請完全關閉 Atom 後重新啟動。

接著嘗試打開 .scss 的檔案,按下 command + shift + p 並輸入 Beautify,選擇 run Beautify Editor。

你就會看到 SASS/SCSS 自動進行縮進排列。

如果要做到存檔自動 beautify ,請鍵入 cmd + ,,進入 package setting 的介面,找到 atom-beautify 點擊 settings,並往下拉找到 Sass 或是 Scss,將 Beautify On Save 打勾。

  • 將 Default Beautifier 換成 SassConvert
  • 打勾 Bautify on save (option,看個人,非必選)

那麼未來在開發 Sass 的時候,將在存檔(Command + S)的時候自動整理縮進。

Sublime

Install Package 安裝 SassBeautify

注意:正常來說不需任何設定就可以直接使用,預設自帶 4 格縮排,可以依照團隊規範調整,依此文章設定是兩格縮排,所以我們客製化內容如下

安裝開始

Preferences >> Package Settings >> SassBeautify >> Settings - User

將下面的內容複製上去

{
  // How many spaces to use for each level of indentation. "t" means use hard tabs.
  "indent": 2,
  // Convert underscores to dashes.
  "dasherize": false,
  // Output the old-style ":prop val" property syntax. Only meaningful when generating Sass.
  "old": false,
  // Custom environment PATH.
  "path": fasle,
  // Custom environment GEM_PATH.
  "gemPath": false,
  // Beautify the sass file after saving it?
  "beautifyOnSave": false,
  // Keep "inline" comments inline?
  "inlineComments": false,
  // Add a new line between selectors?
  "newlineBetweenSelectors": false,
  // Use single quotes everywhere
  "useSingleQuotes": false
}

這邊可以直接打開 .scss 檔案,用 command + shift + p 來搜尋 SassBeautify,按下 enter,就會自動縮排了

如果有跳 error ,應該是你的相依檔案路徑他找不到,可以依照官方的解法進行排除

Compatibility with RVM/rbenv

You need to specify the custom PATH and GEM_PATHvalues in your SassBeautify user settings.

Follow the steps below:

  1. Open up terminal
  2. Run: echo $PATH
  3. Copy the entire PATH into the 'path' setting
  4. Run: echo $GEM_PATH
  5. Copy the entire GEM_PATH into the 'gemPath' setting

Done.

後記心得

Atom 編輯器在使用 atom-beautify 插件後,可以選擇處理 sass 的 beautifier,預設是使用 pretty diff ,而且最坑的是下方的客製化選項 只支援pretty diff,簡單說,如果你想用 SassConvert,那你就無法調整預設 indent 為兩格的設置。

因為選項竟然都只支援 pretty diff...

偏偏 pretty diffSassConvert 整理出來的 SASS 又有些微不同

  • pretty diff 的註解與樣式中間不會有空一行
  • SassConvert 的註解與樣式中間會有空一行

所以如果團隊中用兩個不同的編輯器,只要 beautifier ,設定的不一樣,你就會看到 git commit 永遠一直在變動一大堆結構,這會使 PR 更難閱讀。

所以本文章實現了兩個不同編輯器卻能有同樣的自動化整理風格是退一步求其次,將原先的 SassConvert 自帶的四格縮進改為兩格,與 Atom 的 beautify 一樣,就可以排除這奇怪的問題了。

 
2 months ago

依照前一篇,在 在 Google platform 上架設 ShadowSocks(SS) + BBR,打造一台有 SS + BBR 提速的機器之後,接下來就是大家最關心的如何在 iphone 上面翻牆了。

不過基本上走到這一步都得花錢,花多花少而已,如果想要完全免費的話這篇文章可能幫不上你Q_Q

Surge(USD39.99 昂貴付費,但首推)

基本上這 APP 比較偏向開發者使用,過程會較繁瑣一些,操作也不是那麼平易近人,更令人不敢接近的一點就是付費昂貴,約 1500 NTD,不過這軟體對我來說,「付費就是撿便宜」,省了我很多麻煩,只要有連上網路的地方,都可以自動翻牆,並且智能的選擇路線,如果瀏覽大陸地區的資源,就不翻牆,眾多智能的功能,大概很符合我這種常常要用到網路的工程師了XD

還有,他不像以往的 VPN 什麼需要開開關關的,基本上我很少在開關他,通常都是我不想用,或者是手機重開機的時候吧!

先下載手機版

Surge Apple Store 載點

請先在電腦端下載 Nic 調整過的 SS file

這隻檔案可以正常使用 Line XDD

下載回來之後,請用編輯器打開他

編輯 [Proxy] 下方的 ##IP位置##, ##password## 請把他取代掉成你自己機器的
IP與密碼,至於 port 號和 加密方式我已經替你填寫好了

填好之後,請放入自己的 Apple iClound 裡面

接著打開手機的 Surge

依照下圖載入剛剛編輯過的檔案(如果找不到檔案,應該是你設備在同步中,請稍候幾分鐘)

P.S 如果你打開是英文版的,可以透過設定調整,這部份就不在贅述

點擊左上角的漢堡

從其他程序導入

點擊剛剛載入的檔案

基本上就完成了,可以回 Surger 首頁將通道開啟(最下排最左邊的開關 Tab)

順利翻牆 / 科學上網吧!

Shadowrocket(第二方案,約台幣九十元)

Shadowrocket Apple Store 載點

點開Add Configuration

依照上一篇架設的 SS server 的詳細資訊填入(在上一篇文章文末有教你怎麼看機器資訊)

第一次使用會有權限警示,點擇Allow即可。

只要之前設定的端口、密碼、加密方式等內容都正確的話,你就可以翻牆上網了。

電腦版呢?

這又是另一個故事了,電腦版也有 Surge ,不過又是另一個費用了,對你沒看錯,手機跟電腦版的付費竟然是分開的!!,不過我還是都買了,基本上會手機版的,電腦版的也差不多了XDD,這邊就不繼續教了

參考資源