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 分享作品,之後也會陸續追加網站組出品的各種小工具與網站。