這篇文章主要介紹了使用Meteor配合Node.js編寫實時聊天應用的范例,Node.js作為異步框架,其最突出的使用便是用來編寫實時應用程序,需要的朋友可以參考下
我經常見到被拿來與Derby.js做比較的框架是Meteor.js. 與Derby相似的是,它也能在多個客戶端下實時更新views, 盡管做法上可能跟Derby有點不同. Derby可以較容易的使用多種數據庫, 而Meteor則只親近於MongoDB. 事實上, 通過如Mongoose客戶端接入數據庫的API與你在服務端所期望的已經非常接近了.
雖然現在meteor是個有一些缺點和爭議的框架, 但Meteor看起來是非常有趣的選擇用來建立有實時需求的應用. 個人還是喜歡Derby基於傳統回調的編程形式更吸引我, 但在Derby的強大背後,卻缺乏健壯的文檔和一個大的開發者社區, 這無疑是個很大的打擊. 或許這會隨著時間推移而有所改變吧, 但比起Meteor來說還是會慢很多, 因為後者最近獲得了1100萬美元的資金. 這筆財政資金確保了Meteor的存在以及得到持續的支持. 對於那些需要財政與發展穩定的框架的開發者而言, 這筆資金只會讓Meteor更加優勝. 今天,讓我們一起來看看如何新建一個真實的但又簡單的Meteor應用. 本質上說, 這是基於Tom的 Vimeo screencast的一個新手指引. 與Tom的 Vimeo screencast最大的不同是處理事件的方式. 比起復制粘貼一個Meteor示例的代碼, 我會一步一步的通過自己的方式來處理使用Enter鍵來提交一則訊息. 讓我們開始吧.
創建一個 Meteor應用
Derby和Meteor 他們共有的一個大加分是他們各自的命令行工具. 與Derby使用Node的內置的 npm 工具所不同的是, Meteor使用的是它自己的.
在終端(Mac OS X 和 Linux),執行如下的命令. (在這之前請確保你已經安裝了Node)
?
1$curl https://install.meteor.com | /bin/sh
Metror會自己搞定,並安裝命令行工具.
要新建一個項目, 先轉到你的工作目錄然後運行下邊的代碼. 這會創建一個目錄, 裡邊包括有Meteor和一個最基本模板程序.
?
1$meteor create chat
現在, 你可以轉到該目錄並運行下面的代碼讓它跑起來
?
1
2$cdchat$meteor
Running on: http://localhost:3000/
想要看到這個最基礎的應用程序, 你只需要在任意一款不過時的浏覽器下打開http://localhost:3000/
只要你想, 你就可以使用Meteor內置的meteor deploy命令來部署你的應用到Meteor自己的服務器上
?
1$meteor deploy my-app-name.meteor.com
只要你更新保存了你的代碼, 所有連接上的浏覽器都會實時更新其頁面.
開發聊天應用
在Meteor Create指令產生的文件夾中,你可以看見不同的文件。如果你知道怎麼查看隱藏文件的話,你還可以看見個.meteor這個文件夾。這個文件夾包含了Meteor它本身,以及MongoDB的數據文件。
在你App的根目錄文件夾下,你應該可以看到這三個文件:chat.html, chat.css和chat.js。這三個文件都是自帶說明部分的。HTML文件包含了App的模型以及外觀,他們都是被chat.css定義的。Javascript文件包含了在client和server端要執行的腳本。有一點很重要,不要把任何東西放進這個腳本文件,比如說配置參數和密碼,因為任何人都可以通過查看你應用程序的代碼看到這些。
用你喜歡的文本編輯軟件打開chat.js這個文件。就個人而言,我喜歡用Sublime Text2,因為這個工具簡潔還有多種鼠標狀態提示。
你可以在chat.js文件中查看到下面這樣一段代碼:
?
1if (Meteor.is_client) { Template.hello.greeting = function () { return "Welcome to chat."; }; Template.hello.events = { 'click input' : function () { // template data, if any, is available in 'this' if (typeof console !== 'undefined') console.log("You pressed the button"); } }; } if (Meteor.is_server) { Meteor.startup(function () { // code to run on server at startup }); }
在Meteor.js中注意if段落中Meteor.is_client和Meteor.is_server的兩個部分。在這些區塊中的代碼會分開執行,當運行這段代碼的機器是client端則只運行clint塊中的代碼,server同理。這就說明了Meteor在實際運用中的代碼共享能力。
刪除掉if中所有Meteor.is_client和Meteor.is_server段的代碼,最後只剩下一段:
?
1if (Meteor.is_client) { }
注意,當你保存了 腳本文件之後,你的浏覽器會立刻刷新加載這段新的代碼。
創建視圖(View)
在我們正式對這個腳本文件動工之前, 我們需要先新建一個視圖用來展示聊天記錄. 在編輯器裡打開chat.html並刪除body標簽裡邊的代碼. 包括名為hello的template標簽.只留如下部分
?
1
2
3
4
5
6
7
接著在body標簽裡添加下面這句
?
1{{> entryfield}}
Meteor使用的模板系統與Mustache很相似.大括號{% raw %}{{}}{% endraw %}表示要呈現的內容. 通過簡單地在兩對大括號裡添加內容如{% raw %}{{hello}}{% endraw %}, 模板系統會用hello這個變量的值來替換它. 後面會更詳細的介紹.
注意到了在entryfield這個詞前面有個大於號>了嗎? 使用該符號來指定渲染哪一個模板.
?
1
2
3
在這個例子中,template標簽有單個屬性, 即模板的名字, 這就是我們要渲染的模板, 注意, 模板的名字要和body裡的代碼指定的模板名字一樣 ({{> entryfield}})
查看浏覽器, 你會發現頁面已經刷新了, 輸入框已經呈現出來了.
接下來, 在body裡邊添加另外的一個mutache標簽用以渲染訊息列表
?
1{{> messages}}
最後, 我們還需要新建一個名叫messages的模板. 在entryfield模板下面添加下面這段代碼
?
1
2
3
4
5
6
7
注意到each子句. 在Meteor中你可以使用如下的語法來遍歷一個數組模板
?
1
2{{#each [name of array]}}
{{/each}}
使用each循環時,上下文會有所改變. 當引用變量的時候, 實際上你引用的是每一個數組元素的值.
例如,在我們的chat應用中, 我們遍歷了數組模板"messages"裡邊的每個元素, 該數組可以像下面這樣,
?
1
2
3
4
5
6
7
8
9
10[
{
"name": "Andrew",
"message": "Hello world!"
},
{
"name": "Bob",
"message": "Hey, Andrew!""
}
]
然後, 在each循環中, 你可以看到{% raw %}{{message}}{% endraw %}{% raw %}{{name}}{% endraw %}, 這會引用 每一個數組元素的值來替代(Andrew 和 Bob 替換 name, 以及各自的問候信息.)
當返回到你的浏覽器, 你還看不到任何的改變. 因為訊息數組還沒被傳送到模板, 所以Meteor遍歷不到任何東西來呈現.
你的chat.html最後應該是這樣的
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{{> entryfield}}
{{> messages}}
Javascript
從現在開始, 我們處理的大部分代碼都是客戶端代碼, 所以, 除非特別說明, 以下的代碼都是在if (Meteor.is_client)代碼塊中.
在我們編寫展示訊息的代碼之前,讓我們先新建一個Collection. 從本質上講, 這是一組Models. 換句話說, 在這個chat應用的環境下, Messages collection保存著整個聊天記錄, 而每條訊息記錄是一個Model.
在if語句前, 添加如下代碼來初始化Collection:
?
1Messages = new Meteor.Collection('messages');
因為我們希望這個Collection可以在客戶端和服務端被創建, 所以我們把它寫在了客戶端代碼塊之外.
由於Meteor為我們做了大部分的工作, 要展示聊天記錄是非常容易的. 只需要把下面的代碼添加進if語句裡邊.
?
1
2
3Template.messages.messages = function(){
return Messages.find({}, { sort: { time: -1 }});
}
讓我們拆開來分析這段代碼:
?
1Template.messages.messages = function(){ … }
第一部分Template表示我們正在修改一個模板的行為.
?
1Template.messages.messages = function(){ … }
第二部分messages是模板的名字, 表示是在修改哪一個模板. 例如,如果我們想要對"entryfield"模板做些什麼, 只需把代碼改成
?
1Template.entryfields.variable = function(){ … }
(在這裡, 請別這麼做)
?
1Template.messages.messages = function(){ … }
第三部分的這個messages代表的是一個這個模板裡的一個變量. 還記得我們的each循環遍歷messages嗎? 這就是那個mesaages.
當你打開浏覽器時, 頁面還是沒有什麼改變. 這是意料之中的事, 因為我們只抓取的訊息, 而沒有展示出來.
此時,你的chat.js應該是這樣的. 是否很驚訝就只需在服務器寫這麼些代碼我們就能展示一個實時的聊天記錄應用.
?
1
2
3
4
5
6
7Messages = new Meteor.Collection('messages');
if (Meteor.is_client) {
Template.messages.messages = function(){
return Messages.find({}, { sort: { time: -1 }});
}
}
在console裡添加Message
這部分的內容是可選的, 當然它有助於你調試程序. 你可以直接跳過往下學習建立form來響應鍵盤事件(key press).
如果你想要測試你的訊息顯示代碼, 你可以手動插入一條記錄到數據庫. 打開你的浏覽器控制台, 並輸入如下:
?
1Messages.insert({ name: 'Andrew', message: 'Hello world!', time: 0 })
這將會在數據庫中新建一條記錄, 如果正確的操作了的話,那浏覽器就會即刻更新這條訊息在頁面上.
消息表單
回到chat.js文件當中,我們會將供輸入的form和數據庫鏈接起來以接收用戶聊天數據的提交。在底部添加下面的代碼,不過注意要在if語句塊中。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20Template.entryfield.events = {
"keydown #message": function(event){
if(event.which == 13){
// Submit the form
var name = document.getElementById('name');
var message = document.getElementById('message');
if(name.value != '' && message.value != ''){
Messages.insert({
name: name.value,
message: message.value,
time: Date.now()
});
name.value = '';
message.value = '';
}
}
}
}
代碼有點多,讓我們再回顧一遍。你也許還記得,在Template後面的第二個單詞決定了我們正在修改的是哪個模板。不過跟之前不同的是,我們寫的代碼是用來綁定數據庫和messages模板的,我們正在修改的模板是entryfield。
這個模板中events的屬性包含了一個object,events的屬性按照下面的格式呈現:
"[eventname] [selector]"
例如,如果我們想為一個ID為hello的button綁定一個點擊事件的話,我們會把下面的代碼加入到events的個結構體當中。
"click #hello": function(event){ … }
在我們的例子當中,我們是將一個函數綁定到了ID為“message”的一個keydown事件當中。如果你還記得,這段代碼早在我們在chat.html文件中建立模板的時候就已經設定好了。
在事件對象中,每個key都有一個函數作為它的值。這個函數在事件被調用時執行,其中事件對象作為第一個參數傳遞給該函數。在我們的app裡,每當ID帶有“message”的輸入欄中有任意鍵被按下(keydown)時,該函數就被調用了。
函數內的代碼相當簡單。首先,我們檢查回車鍵是否被按下(輸入中有13的關鍵代碼)。第二,我們通過ID取得兩個輸入欄的DOM元素。第三,我們檢查並確保輸入值不為空,以防止用戶提交一個空的名字或信息(name or message)。
注意下面的代碼很重要。這段代碼是將message插入數據庫。
?
1
2
3
4
5Messages.insert({
name: name.value,
message: message.value,
time: Date.now()
});
正如你看到的,這和我們插入到控制台的代碼類似,但不是硬編碼的數值,我們用的是DOM元素的值。此外,我們加入了當前時間,以保證聊天日志被正確的按時間排序。
最後,我們將兩個輸入的值簡單的設為''以清空輸入欄。
現在,如果你進入浏覽器,你可以試著輸入一個名字與信息到兩個輸入欄。按下回車以後,輸入欄將被清除,一個新的消息會出現在你的輸入字段的正下方。打開另一個浏覽器窗口,導航到同一個URL(http://localhost:3000/)。試著鍵入另一個信息,而
正如你看到的,Meteor非常強大。不需要寫一行明確更新消息日志的代碼,新的信息顯示出來並同步到多個浏覽器和客戶端。
總結
雖然Meteor工作起來非常酷,而且也有一些非常有用的應用支持它,比如Derby.js,但它是不成熟的。一個說明這一點例子就是,浏覽文檔並找找紅色的引文。例如,關於MongoDB集合該文檔做了如下陳述:
目前客戶端被給予集合的完全寫訪問權限。它們可以執行任意的更新命令。一旦我們建立鑒權認證,你將能夠限制客戶端的直接插入,更新和刪除。我們也在考慮校驗器或者其他類似ORM的功能。
任何用戶擁有完全的寫訪問權限是一個非常大的問題,因為對任何一個app產品——如果一個用戶對你的整個數據庫有寫訪問權限,這是一個相當大的安全問題。
看到Meteor(和Derby.js!)在像哪個方向前進是令人激動的,但是除非它成熟一點,它可能不是一個產品級應用的最好選擇。期待那1100萬美元資金能很好的利用起來。