建立一个前端

正如我们在 Feathers 基础知识 中看到的,Feathers在浏览器中运行良好,并附带 客户端使用, 可以轻松连接到Feathers服务器.

在本章中,我们将使用现代纯JavaScript创建一个具有注册和登录功能的实时聊天前端.与 Feathers 基础知识 一样,它只适用于最新版本的 Chrome, Firefox, Safari 和 Edge,因为我们不会使用像 Babel 这样的转换器. 最终版本可以在 这里 找到.

注解

我们不会使用前端框架,因此我们可以专注于Feathers的全部内容. Feathers是框架无关的,可以与React,VueJS或Angular等任何前端框架一起使用.有关更多信息,请参阅 与前端框架集成.

设置索引页面

我们已经在 public 文件夹中提供静态文件,并在 public/index.html 中有一个占位符页面.让我们将其更新为以下内容:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0" />
    <title>Vanilla JavaScript Feathers Chat</title>
    <link rel="shortcut icon" href="favicon.ico">
    <link rel="stylesheet" href="//cdn.rawgit.com/feathersjs/feathers-chat/v0.2.0/public/base.css">
    <link rel="stylesheet" href="//cdn.rawgit.com/feathersjs/feathers-chat/v0.2.0/public/chat.css">
  </head>
  <body>
    <div id="app" class="flex flex-column"></div>
    <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.js"></script>
    <script src="//unpkg.com/@feathersjs/client@^3.0.0/dist/feathers.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script src="app.js"></script>
  </body>
</html>

这将加载我们的聊天CSS样式,添加容器 div #app 并加载几个库:

  • 客户端使用 (因为我们没有使用像Webpack或Browserify这样的模块加载器)

  • Socket.io由聊天API提供

  • MomentJS 来格式化日期

  • 我们的代码生成的 app.js

让我们创建 public/app.js,其中所有以下代码都将存在(每个代码示例都添加到该文件的末尾).

连接到API

我们首先从最重要的事情开始,即与Feathers API的连接.我们已经在 Feathers 基础知识 中学习了如何在客户端上使用Feathers.在这里,我们做了几乎相同的事情:建立一个Socket连接并初始化一个新的Feathers应用程序.我们还为以后设置了身份验证客户端:

// Establish a Socket.io connection
const socket = io();
// Initialize our Feathers client application through Socket.io
// with hooks and authentication.
const client = feathers();

client.configure(feathers.socketio(socket));
// Use localStorage to store our login token
client.configure(feathers.authentication({
  storage: window.localStorage
}));

这允许我们通过websockets与聊天API通信,以进行实时更新.

基础 和 用户/消息 列表HTML

接下来,我们必须定义一些静态和动态HTML,当我们想要显示登录页面(也可以作为注册页面)和实际聊天界面时,我们可以将其插入到页面中:

// Login screen
const loginHTML = `<main class="login container">
  <div class="row">
    <div class="col-12 col-6-tablet push-3-tablet text-center heading">
      <h1 class="font-100">Log in or signup</h1>
    </div>
  </div>
  <div class="row">
    <div class="col-12 col-6-tablet push-3-tablet col-4-desktop push-4-desktop">
      <form class="form">
        <fieldset>
          <input class="block" type="email" name="email" placeholder="email">
        </fieldset>

        <fieldset>
          <input class="block" type="password" name="password" placeholder="password">
        </fieldset>

        <button type="button" id="login" class="button button-primary block signup">
          Log in
        </button>

        <button type="button" id="signup" class="button button-primary block signup">
          Sign up and log in
        </button>
      </form>
    </div>
  </div>
</main>`;

// Chat base HTML (without user list and messages)
const chatHTML = `<main class="flex flex-column">
  <header class="title-bar flex flex-row flex-center">
    <div class="title-wrapper block center-element">
      <img class="logo" src="http://feathersjs.com/img/feathers-logo-wide.png"
        alt="Feathers Logo">
      <span class="title">Chat</span>
    </div>
  </header>

  <div class="flex flex-row flex-1 clear">
    <aside class="sidebar col col-3 flex flex-column flex-space-between">
      <header class="flex flex-row flex-center">
        <h4 class="font-300 text-center">
          <span class="font-600 online-count">0</span> users
        </h4>
      </header>

      <ul class="flex flex-column flex-1 list-unstyled user-list"></ul>
      <footer class="flex flex-row flex-center">
        <a href="#" id="logout" class="button button-primary">
          Sign Out
        </a>
      </footer>
    </aside>

    <div class="flex flex-column col col-9">
      <main class="chat flex flex-column flex-1 clear"></main>

      <form class="flex flex-row flex-space-between" id="send-message">
        <input type="text" name="text" class="flex flex-1">
        <button class="button-primary" type="submit">Send</button>
      </form>
    </div>
  </div>
</main>`;

// Add a new user to the list
const addUser = user => {
  const userList = document.querySelector('.user-list');

  if(userList) {
    // Add the user to the list
    userList.insertAdjacentHTML('beforeend', `<li>
      <a class="block relative" href="#">
        <img src="${user.avatar}" alt="" class="avatar">
        <span class="absolute username">${user.email}</span>
      </a>
    </li>`);

    // Update the number of users
    const userCount = document.querySelectorAll('.user-list li').length;

    document.querySelector('.online-count').innerHTML = userCount;
  }
};

// Renders a new message and finds the user that belongs to the message
const addMessage = message => {
  // Find the user belonging to this message or use the anonymous user if not found
  const { user = {} } = message;
  const chat = document.querySelector('.chat');
  // Escape HTML
  const text = message.text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;').replace(/>/g, '&gt;');

  if(chat) {
    chat.insertAdjacentHTML( 'beforeend', `<div class="message flex flex-row">
      <img src="${user.avatar}" alt="${user.email}" class="avatar">
      <div class="message-wrapper">
        <p class="message-header">
          <span class="username font-600">${user.email}</span>
          <span class="sent-date font-300">${moment(message.createdAt).format('MMM Do, hh:mm:ss')}</span>
        </p>
        <p class="message-content font-300">${text}</p>
      </div>
    </div>`);

    chat.scrollTop = chat.scrollHeight - chat.clientHeight;
  }
};

这将添加以下变量和函数:

  • loginHTML 包含登录/注册页面的一些静态HTML

  • chatHTML 包含主要的聊天页面内容(一旦用户登录)

  • addUser(user) 是一个将新用户添加到左侧用户列表的函数

  • addMessage(message) 是一个向列表中添加新消息的函数.它还将确保在添加消息时始终滚动到消息列表的底部

显示登录/注册或聊天页面

接下来,我们将添加两个功能来显示登录和聊天页面,其中我们还将添加25个最新聊天消息和注册用户的列表.

// Show the login page
const showLogin = (error = {}) => {
  if(document.querySelectorAll('.login').length) {
    document.querySelector('.heading').insertAdjacentHTML('beforeend', `<p>There was an error: ${error.message}</p>`);
  } else {
    document.getElementById('app').innerHTML = loginHTML;
  }
};

// Shows the chat page
const showChat = async () => {
  document.getElementById('app').innerHTML = chatHTML;

  // Find the latest 25 messages. They will come with the newest first
  // which is why we have to reverse before adding them
  const messages = await client.service('messages').find({
    query: {
      $sort: { createdAt: -1 },
      $limit: 25
    }
  });

  // We want to show the newest message last
  messages.data.reverse().forEach(addMessage);

  // Find all users
  const users = await client.service('users').find();

  users.data.forEach(addUser);
};
  • showLogin(error) 将显示loginHTML的内容,或者,如果登录页面已经显示,则添加错误消息.当您尝试使用无效凭据登录或使用已存在的用户注册时,会发生这种情况.

  • showChat() 做了几件事。首先,我们将静态chatHTML添加到页面。然后我们使用Feathers查询语法从消息Feathers服务获取最新的25条消息(这与我们的聊天API的 /messages 端点相同)。由于列表将首先返回最新消息,因此我们需要反转数据。然后我们通过调用我们的 addMessage 函数来添加每条消息,这样它看起来就像聊天应用程序一样 - 旧的消息在向上滚动时变老。之后,我们通过调用addUser获取所有注册用户的列表,以在侧栏中显示它们。

登录和注册

好的.现在我们可以显示登录页面(包括出错时的错误消息),如果我们登录,则调用上面定义的 showChat.我们已经构建了UI,现在我们必须添加功能以实际允许人们注册,登录和注销.

// Retrieve email/password object from the login/signup page
const getCredentials = () => {
  const user = {
    email: document.querySelector('[name="email"]').value,
    password: document.querySelector('[name="password"]').value
  };

  return user;
};

// Log in either using the given email/password or the token from storage
const login = async credentials => {
  try {
    if(!credentials) {
      // Try to authenticate using the JWT from localStorage
      await client.authenticate();
    } else {
      // If we get login information, add the strategy we want to use for login
      const payload = Object.assign({ strategy: 'local' }, credentials);

      await client.authenticate(payload);
    }

    // If successful, show the chat page
    showChat();
  } catch(error) {
    // If we got an error, show the login page
    showLogin(error);
  }
};

document.addEventListener('click', async ev => {
  switch(ev.target.id) {
  case 'signup': {
    // For signup, create a new user and then log them in
    const credentials = getCredentials();

    // First create the user
    await client.service('users').create(credentials);
    // If successful log them in
    await login(credentials);

    break;
  }
  case 'login': {
    const user = getCredentials();

    await login(user);

    break;
  }
  case 'logout': {
    await client.logout();

    document.getElementById('app').innerHTML = loginHTML;

    break;
  }
  }
});
  • getCredentials() 从登录/注册页面获取用户名(电子邮件)和密码字段的值,以便直接与Feathers身份验证一起使用.

  • login(credentials) 将使用本地身份验证策略(例如用户名和密码)对getCredentials针对Feathers API返回的凭据进行身份验证,或者,如果没有给出凭据,请尝试使用存储在localStorage中的JWT。 这将首先尝试从localStorage获取JWT,一旦您成功登录,它将自动放入,这样我们就不必每次访问聊天时都登录。 只有当它不起作用时,它才会显示登录页面。最后,如果登录成功,它将显示聊天页面。

  • 我们还为三个按钮添加了单击事件侦听器. #login 将获得凭据,然后使用这些凭据登录.单击 #signup 将注册并同时登录.它将首先在我们的API上创建一个新用户,然后使用相同的用户信息登录. 最后, #logout 将忘记JWT,然后再次显示登录页面.

实时和发送消息

在最后一步中,我们将添加发送新消息的功能,并使用户和消息列表实时更新.

document.addEventListener('submit', async ev => {
  if(ev.target.id === 'send-message') {
    // This is the message text input field
    const input = document.querySelector('[name="text"]');

    ev.preventDefault();

    // Create a new message and then clear the input field
    await client.service('messages').create({
      text: input.value
    });

    input.value = '';
  }
});

// Listen to created events and add the new message in real-time
client.service('messages').on('created', addMessage);

// We will also see when new users get created in real-time
client.service('users').on('created', addUser);

login();
  • submit 按钮事件监听器从输入字段获取消息文本,在消息服务上创建新消息,然后清除该字段.

  • 接下来,我们添加了两个 created 事件监听器.一个用于 messages 调用 addMessage 函数将新消息添加到列表中,一个用于 users,它通过 addUser 将用户添加到列表中.这就是Feathers实时和我们需要做的一切,以便让所有内容自动更新.

  • 为了启动我们的应用程序,我们调用 login() ,如上所述,它将立即显示聊天应用程序(如果我们之前已登录且令牌未过期)或登录页面.

下一步是什么?

而已.我们现在有一个简单的JavaScript实时聊天前端登录和注册.此示例演示了如何与Feathers API进行交互的许多核心原则.

如果您遇到问题, 请记住您可以在 这里 找到一个完整的工作示例.

在最后一章中,我们将看看 写测试.