構(gòu)建一個(gè)即時(shí)消息應(yīng)用(九):Conversation頁面
本文是該系列的第八篇。
- 第一篇: 模式
- 第二篇: OAuth
- 第三篇: 對話
- 第四篇: 消息
- 第五篇: 實(shí)時(shí)消息
- 第六篇: 僅用于開發(fā)的登錄
- 第七篇: Access 頁面
- 第八篇: Home 頁面
在這篇文章中,我們將對對話頁面進(jìn)行編碼。此頁面是兩個(gè)用戶之間的聊天室。在頂部我們將顯示其他參與者的信息,下面接著的是最新消息列表,以及底部的消息表單。
聊天標(biāo)題
讓我們從創(chuàng)建 static/pages/conversation-page.js
文件開始,它包含以下內(nèi)容:
- import http from '../http.js'
- import { navigate } from '../router.js'
- import { avatar, escapeHTML } from '../shared.js'
- export default async function conversationPage(conversationID) {
- let conversation
- try {
- conversation = await getConversation(conversationID)
- } catch (err) {
- alert(err.message)
- navigate('/', true)
- return
- }
- const template = document.createElement('template')
- template.innerHTML = `
- <div>
- <a href="/">← Back</a>
- ${avatar(conversation.otherParticipant)}
- <span>${conversation.otherParticipant.username}</span>
- </div>
- <!-- message list here -->
- <!-- message form here -->
- `
- const page = template.content
- return page
- }
- function getConversation(id) {
- return http.get('/api/conversations/' + id)
- }
此頁面接收路由從 URL 中提取的會話 ID。
首先,它向 /api/ conversations/{conversationID}
發(fā)起一個(gè) GET 請求,以獲取有關(guān)對話的信息。 如果出現(xiàn)錯(cuò)誤,我們會將其顯示,并重定向回 /
。然后我們呈現(xiàn)有關(guān)其他參與者的信息。
對話列表
我們也會獲取最新的消息并顯示它們。
- let conversation, messages
- try {
- [conversation, messages] = await Promise.all([
- getConversation(conversationID),
- getMessages(conversationID),
- ])
- }
更新 conversationPage()
函數(shù)以獲取消息。我們使用 Promise.all()
同時(shí)執(zhí)行這兩個(gè)請求。
- function getMessages(conversationID) {
- return http.get(`/api/conversations/${conversationID}/messages`)
- }
發(fā)起對 /api/conversations/{conversationID}/messages
的 GET 請求可以獲取對話中的最新消息。
- <ol id="messages"></ol>
現(xiàn)在,將該列表添加到標(biāo)記中。
- const messagesOList = page.getElementById('messages')
- for (const message of messages.reverse()) {
- messagesOList.appendChild(renderMessage(message))
- }
這樣我們就可以將消息附加到列表中了。我們以時(shí)間倒序來顯示它們。
- function renderMessage(message) {
- const messageContent = escapeHTML(message.content)
- const messageDate = new Date(message.createdAt).toLocaleString()
- const li = document.createElement('li')
- if (message.mine) {
- li.classList.add('owned')
- }
- li.innerHTML = `
- <p>${messageContent}</p>
- <time>${messageDate}</time>
- `
- return li
- }
每個(gè)消息條目顯示消息內(nèi)容本身及其時(shí)間戳。使用 .mine
,我們可以將不同的 css 類附加到條目,這樣您就可以將消息顯示在右側(cè)。
消息表單
- <form id="message-form">
- <input type="text" placeholder="Type something" maxlength="480" required>
- <button>Send</button>
- </form>
將該表單添加到當(dāng)前標(biāo)記中。
- page.getElementById('message-form').onsubmit = messageSubmitter(conversationID)
將事件監(jiān)聽器附加到 “submit” 事件。
- function messageSubmitter(conversationID) {
- return async ev => {
- ev.preventDefault()
- const form = ev.currentTarget
- const input = form.querySelector('input')
- const submitButton = form.querySelector('button')
- input.disabled = true
- submitButton.disabled = true
- try {
- const message = await createMessage(input.value, conversationID)
- input.value = ''
- const messagesOList = document.getElementById('messages')
- if (messagesOList === null) {
- return
- }
- messagesOList.appendChild(renderMessage(message))
- } catch (err) {
- if (err.statusCode === 422) {
- input.setCustomValidity(err.body.errors.content)
- } else {
- alert(err.message)
- }
- } finally {
- input.disabled = false
- submitButton.disabled = false
- setTimeout(() => {
- input.focus()
- }, 0)
- }
- }
- }
- function createMessage(content, conversationID) {
- return http.post(`/api/conversations/${conversationID}/messages`, { content })
- }
我們利用 partial application 在 “submit” 事件處理程序中獲取對話 ID。它 從輸入中獲取消息內(nèi)容,并用它對 /api/conversations/{conversationID}/messages
發(fā)出 POST 請求。 然后將新創(chuàng)建的消息添加到列表中。
消息訂閱
為了實(shí)現(xiàn)實(shí)時(shí),我們還將訂閱此頁面中的消息流。
- page.addEventListener('disconnect', subscribeToMessages(messageArriver(conversationID)))
將該行添加到 conversationPage()
函數(shù)中。
- function subscribeToMessages(cb) {
- return http.subscribe('/api/messages', cb)
- }
- function messageArriver(conversationID) {
- return message => {
- if (message.conversationID !== conversationID) {
- return
- }
- const messagesOList = document.getElementById('messages')
- if (messagesOList === null) {
- return
- }
- messagesOList.appendChild(renderMessage(message))
- readMessages(message.conversationID)
- }
- }
- function readMessages(conversationID) {
- return http.post(`/api/conversations/${conversationID}/read_messages`)
- }
在這里我們?nèi)匀皇褂眠@個(gè)應(yīng)用的部分來獲取會話 ID。 當(dāng)新消息到達(dá)時(shí),我們首先檢查它是否來自此對話。如果是,我們會將消息條目預(yù)先添加到列表中,并向 /api/conversations/{conversationID}/read_messages
發(fā)起 POST 一個(gè)請求,以更新參與者上次閱讀消息的時(shí)間。
本系列到此結(jié)束。 消息應(yīng)用現(xiàn)在可以運(yùn)行了。