diff --git a/sources/tech/20180719 Building a Messenger App- Home Page.md b/sources/tech/20180719 Building a Messenger App- Home Page.md new file mode 100644 index 0000000000..ddec2c180f --- /dev/null +++ b/sources/tech/20180719 Building a Messenger App- Home Page.md @@ -0,0 +1,255 @@ +[#]: collector: (lujun9972) +[#]: translator: ( ) +[#]: reviewer: ( ) +[#]: publisher: ( ) +[#]: url: ( ) +[#]: subject: (Building a Messenger App: Home Page) +[#]: via: (https://nicolasparada.netlify.com/posts/go-messenger-home-page/) +[#]: author: (Nicolás Parada https://nicolasparada.netlify.com/) + +Building a Messenger App: Home Page +====== + +This post is the 8th on a series: + + * [Part 1: Schema][1] + * [Part 2: OAuth][2] + * [Part 3: Conversations][3] + * [Part 4: Messages][4] + * [Part 5: Realtime Messages][5] + * [Part 6: Development Login][6] + * [Part 7: Access Page][7] + + + +Continuing the frontend, let’s finish the home page in this post. We’ll add a form to start conversations and a list with the latest ones. + +### Conversation Form + +![conversation form screenshot][8] + +In the `static/pages/home-page.js` file add some markup in the HTML view. + +``` +
+ +
+``` + +Add that form just below the section in which we displayed the auth user and logout button. + +``` +page.getElementById('conversation-form').onsubmit = onConversationSubmit +``` + +Now we can listen to the “submit” event to create the conversation. + +``` +import http from '../http.js' +import { navigate } from '../router.js' + +async function onConversationSubmit(ev) { + ev.preventDefault() + + const form = ev.currentTarget + const input = form.querySelector('input') + + input.disabled = true + + try { + const conversation = await createConversation(input.value) + input.value = '' + navigate('/conversations/' + conversation.id) + } catch (err) { + if (err.statusCode === 422) { + input.setCustomValidity(err.body.errors.username) + } else { + alert(err.message) + } + setTimeout(() => { + input.focus() + }, 0) + } finally { + input.disabled = false + } +} + +function createConversation(username) { + return http.post('/api/conversations', { username }) +} +``` + +On submit we do a POST request to `/api/conversations` with the username and redirect to the conversation page (for the next post). + +### Conversation List + +![conversation list screenshot][9] + +In the same file, we are going to make the `homePage()` function async to load the conversations first. + +``` +export default async function homePage() { + const conversations = await getConversations().catch(err => { + console.error(err) + return [] + }) + /*...*/ +} + +function getConversations() { + return http.get('/api/conversations') +} +``` + +Then, add a list in the markup to render conversations there. + +``` +
    +``` + +Add it just below the current markup. + +``` +const conversationsOList = page.getElementById('conversations') +for (const conversation of conversations) { + conversationsOList.appendChild(renderConversation(conversation)) +} +``` + +So we can append each conversation to the list. + +``` +import { avatar, escapeHTML } from '../shared.js' + +function renderConversation(conversation) { + const messageContent = escapeHTML(conversation.lastMessage.content) + const messageDate = new Date(conversation.lastMessage.createdAt).toLocaleString() + + const li = document.createElement('li') + li.dataset['id'] = conversation.id + if (conversation.hasUnreadMessages) { + li.classList.add('has-unread-messages') + } + li.innerHTML = ` + +
    + ${avatar(conversation.otherParticipant)} + ${conversation.otherParticipant.username} +
    +
    +

    ${messageContent}

    + +
    +
    + ` + return li +} +``` + +Each conversation item contains a link to the conversation page and displays the other participant info and a preview of the last message. Also, you can use `.hasUnreadMessages` to add a class to the item and do some styling with CSS. Maybe a bolder font or accent the color. + +Note that we’re escaping the message content. That function comes from `static/shared.js`: + +``` +export function escapeHTML(str) { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} +``` + +That prevents displaying as HTML the message the user wrote. If the user happens to write something like: + +``` + +``` + +It would be very annoying because that script will be executed 😅 +So yeah, always remember to escape content from untrusted sources. + +### Messages Subscription + +Last but not least, I want to subscribe to the message stream here. + +``` +const unsubscribe = subscribeToMessages(onMessageArrive) +page.addEventListener('disconnect', unsubscribe) +``` + +Add that line in the `homePage()` function. + +``` +function subscribeToMessages(cb) { + return http.subscribe('/api/messages', cb) +} +``` + +The `subscribe()` function returns a function that once called it closes the underlying connection. That’s why I passed it to the “disconnect” event; so when the user leaves the page, the event stream will be closed. + +``` +async function onMessageArrive(message) { + const conversationLI = document.querySelector(`li[data-id="${message.conversationID}"]`) + if (conversationLI !== null) { + conversationLI.classList.add('has-unread-messages') + conversationLI.querySelector('a > div > p').textContent = message.content + conversationLI.querySelector('a > div > time').textContent = new Date(message.createdAt).toLocaleString() + return + } + + let conversation + try { + conversation = await getConversation(message.conversationID) + conversation.lastMessage = message + } catch (err) { + console.error(err) + return + } + + const conversationsOList = document.getElementById('conversations') + if (conversationsOList === null) { + return + } + + conversationsOList.insertAdjacentElement('afterbegin', renderConversation(conversation)) +} + +function getConversation(id) { + return http.get('/api/conversations/' + id) +} +``` + +Every time a new message arrives, we go and query for the conversation item in the DOM. If found, we add the `has-unread-messages` class to the item, and update the view. If not found, it means the message is from a new conversation created just now. We go and do a GET request to `/api/conversations/{conversationID}` to get the conversation in which the message was created and prepend it to the conversation list. + +* * * + +That covers the home page 😊 +On the next post we’ll code the conversation page. + +[Souce Code][10] + +-------------------------------------------------------------------------------- + +via: https://nicolasparada.netlify.com/posts/go-messenger-home-page/ + +作者:[Nicolás Parada][a] +选题:[lujun9972][b] +译者:[译者ID](https://github.com/译者ID) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://nicolasparada.netlify.com/ +[b]: https://github.com/lujun9972 +[1]: https://nicolasparada.netlify.com/posts/go-messenger-schema/ +[2]: https://nicolasparada.netlify.com/posts/go-messenger-oauth/ +[3]: https://nicolasparada.netlify.com/posts/go-messenger-conversations/ +[4]: https://nicolasparada.netlify.com/posts/go-messenger-messages/ +[5]: https://nicolasparada.netlify.com/posts/go-messenger-realtime-messages/ +[6]: https://nicolasparada.netlify.com/posts/go-messenger-dev-login/ +[7]: https://nicolasparada.netlify.com/posts/go-messenger-access-page/ +[8]: https://nicolasparada.netlify.com/img/go-messenger-home-page/conversation-form.png +[9]: https://nicolasparada.netlify.com/img/go-messenger-home-page/conversation-list.png +[10]: https://github.com/nicolasparada/go-messenger-demo