蒼時弦也
蒼時弦也
資深軟體工程師
發表於

COSCUP 2013 - Lighting Talk 補充

其實我以為我很快就沒東西,沒想到還能超過一分鐘。

第一次不知道該講什麼,我真的該分享一下怎麼制作的,是個很簡單卻又非常有趣的作品。

因為 Github 的 API 是允許 Cross-Domain 存取的,也因為這樣,我才能夠順利地從 Github 上把我們 Staff 的個人簡介拉出來,然後透過 Backbone 去呈現。

我一共用到了這些 JavaScript Libary / Tools

  • RequireJS (AMD)
    • RequireJS Text Plugin
  • Backbone.js (MVP/MVC)
    • Underscore.js (相依)
  • jQuery (DOM操作)
  • Mustache.js (Template)
  • Markdown.js (Praser)
  • Modernizr

實作也非常簡單,只需要約六七個檔案就能完成。

我個人的習慣會先以 H5BP 來建制預設的 Project 原型,再繼續製作後續的處理。

開始 Project 前,也會先將以確定需要的 Library 放到目錄,然後做一次 Initialize 的 Commit 進去。

(我是使用 Fire.app 開發的,在 JavaScript 套件管理上有點不方便,希望有時間可以送 Bower-like function 的 Pull Request 到 Fire.app)

每次使用 RequireJS 前,我會先把設定寫好,然後一個一個測試(在 main 中直接 require 個模組,然後測試是否正常運作)是否正常運行。

 1require.config {
 2  baseUrl: 'JavaScripts'
 3  paths: {
 4    jquery: [
 5      '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min'
 6      'vendor/jquery-1.9.1.min'
 7    ]
 8    mustache: 'vendor/mustache'
 9    markdown: 'vendor/markdown'
10    backbone: 'vendor/backbone-min'
11    underscore: 'vendor/underscore-min'
12    text: 'vendor/require-text'
13  }
14  shim: {
15    'jquery': {
16      exports: '$'
17    }
18    markdown: {
19      exports: 'markdown'
20    }
21    underscore: {
22      deps: ['jquery']
23      exports: '_'
24    }
25    backbone: {
26      deps: ['jquery', 'underscore']
27      exports: 'Backbone'
28    }
29  }
30}

處理完畢後,我會習關另外開一個檔案,然後從 main 中讀取進來,並且進行一些操作。

1require [
2  'backbone', 'App'
3], (Backbone, App) ->
4  new App
5  Backbone.history.start {pushState: true}

下面的 Code 我都會補上註解,我不太擅長一句句解釋完成的程式(牆角

 1define [ # 定義所有相依的 Library 但是僅在需要時才做出 require 的動作
 2  'jquery', 'backbone', 'require',
 3  # Models
 4  'Model/Information', 'Model/Biography'
 5  # View
 6  'View/InformationView', 'View/BiographyView'
 7], ($, Backbone) ->
 8
 9  Backbone.Router.extend { # 傳回 Backbone Router 的物件
10    initialize: -> # 初始化
11      @.el = $("#app") # 這次 Prototyping 並沒有使用到,原本應該放一個 Template 覆蓋後才建構子元件上去
12
13    routes: { # 定義 Router 這邊只需要 index 列出所有成員和 /username 網址格式的建構
14      '': 'index',
15      ':username': 'userPage'
16    }
17    index: -> # 首頁尚未實作,這邊會搭配 Staff Issue Tracker 系統列出成員
18      # notings
19      console.log "Index"
20
21    userPage: (username) ->
22    	# 讀取 Model 的 Class
23      Information = require('Model/Information')
24      Biography = require('Model/Biography')
25			
26      # 讀取 View 的 Class
27      InformationView = require('View/InformationView')
28      BiographyView = require('View/BiographyView')
29
30			# 建構 Model (帶入 username 以利查詢使用者)
31      info = new Information {username: username}
32      bio = new Biography {username: username}
33			
34      # 建構 View 並且傳入綁定的元素和 Model
35      infoView = new InformationView {el: $("#info"), model: info}
36      bioView = new BiographyView {el: $("#bio"), model: bio}
37
38			# 從 Github 讀取資料
39      info.fetch()
40      bio.fetch()
41
42			# 讀取完畢後更新頁面
43      info.on 'sync', (model) ->
44        infoView.render()
45      bio.on 'sync', (model) ->
46        bioView.render()
47  }

事實上,這還能夠做出改進。

先把 on 的 Event 放入 View 裡面使用 listenTo 作處理,會比另外作更加有效率。

想要讀取 Github 的資料非常簡單,只需要用一般的 Ajax 即可。 不過預設傳回 json 以及內容的 base64 編碼資料,不過也可以改以請求 Raw 這部分可以透過加入 headers 的 Hash 完成。

 1define [
 2  'backbone'
 3], (Backbone) ->
 4
 5  Backbone.Model.extend {
 6    url: -> # 設定 Model 網址格式(因為要帶入使用者名稱,所以改以函示呈現)
 7      "https://api.Github.com/repos/sitcon-tw/staff-card/contents/#{@.get('username')}/info.json"
 8    sync: (method, model, options) -> # 改變預設的 sync 方法
 9      options = options || {}
10      options.headers = options.headers || {}
11     	# 對 HTTP Request 加入 Accept 要求傳回 Github 的 raw 格式,而非 json
12      options.headers['Accept'] = "application/vnd.Github.raw"
13
14			# 傳給 Backbone 預設的 sync 進行處理
15      Backbone.sync method, model, options
16  }

前面有提到過,透過 Cross-Domain 的支援可以做到,另一方面是 jQuery 還能夠協助幫你把 text 的 json 轉回,非常貼心。也因為這樣,我們可以順利的把 info.json 這個檔案資訊直接放到 Model 裡面使用。

 1define [
 2  'jquery', 'backbone'
 3], ($, Backbone, Markdown)->
 4
 5  Backbone.Model.extend {
 6    url: ->
 7      "https://api.Github.com/repos/sitcon-tw/staff-card/contents/#{@.get('username')}/biography.md"
 8    sync: (method, model, options) ->
 9    	# Backbone 非 JSON 不收,自己實作
10      self = @
11      $.ajax {
12        headers: {
13          'Accept': 'application/vnd.Github.raw'
14        }
15        url: self.url() # 取得 URL (呼叫 Model 定義的)
16        success: (data, xhr) ->
17          self.parse(data, xhr) # Backbone 收到資料可以交給 parse 方法格式化資料 Ex. 過濾、替換文字
18          self.trigger 'sync', self # 手動觸發 sync 事件,通知完成(可以自定,這裡是為了與 Backbone 統一)
19      }
20
21    parse: (res, options) ->
22      @.set('content', res) # 強制將收到的 raw Markdonw 內容存到 content 的欄位
23      {content: res} # parse 需要回傳處理後的資料
24  }

其實這部分還是可以透過像是直接寫 jQuery 處理,或者覆蓋原有 Backbone.sync 方法,不過我最後選擇了這個比較笨,但是能確保原有方法乾淨的做法。

 1define [
 2  'jquery', 'backbone', 'mustache', 'text!JavaScripts/template/info.js.html'
 3], ($, Backbone, Mustache, infoTemplate) ->
 4
 5  Backbone.View.extend {
 6    initialize: (options) ->
 7      options = options || {}
 8      @.template = infoTemplate # 設定樣板資料(text! 是 plugin 功能,將檔案內容完整存入)
 9    render: ->
10      rendered = Mustache.to_html @.template, @.model.toJSON() # 用 Mustache 編譯樣板,第二個參數直接讓 Model 傳回 JSON 即可
11      $(@.el).html(rendered) # 更新網頁元素(使用 @.$el.html(rendered) 應該也是能運作的)
12  }
 1define [
 2  'jquery', 'backbone', 'markdown'
 3], ($, Backbone, Markdown) ->
 4
 5  Backbone.View.extend {
 6    initialize: (options) ->
 7      options = options || {}
 8    render: ->
 9      rendered = Markdown.toHTML @.model.get('content') # 因為目前不需要樣板,直接用 Markdown.js 轉回 HTML
10      rendered = "<h1>Biography</h1>" + rendered # 手動加上 Title
11      $(@.el).html(rendered) # 更新網頁元素
12  }

其實整體來說這兩個 View 非常簡單,只要實作 Render 即可。

 1{% raw %}
 2<ul>
 3  <li><h1>{{nickname}}</h1></li>
 4  {{#website}}
 5  <li><a href="{{website}}" target="_blank">Website</a></li>
 6  {{/website}}
 7  {{#facebook}}
 8  <li><a href="https://fb.me/{{facebook}}" target="_blank">Facebook</a></li>
 9  {{/facebook}}
10  {{#plurk}}
11  <li><a href="https://plurk.com/{{plurk}}" target="_blank">Plurk</a></li>
12  {{/plurk}}
13</ul>
14{% endraw %}

樣板部分也非常簡單,把需要的替換上去即可。

最後就是替首頁加上 #app, #info, #bio 三個元素,讓 Backbone 可以捕捉並且替換。

最後附上簡報:https://slid.es/elct9620/coscup2013-sitcon-webiste

希望大家會喜歡,之後網站組也會繼續改進這套系統。

當然,網站組也非常喜歡在 Github 分享作品,之後也會陸續追加網站組出品的各種小工具與網站。