В этом руководстве мы рассмотрим, как создать функцию ввода текста в приложении чата. Ниже показано, как будет выглядеть окончательное приложение:

В этом руководстве предполагается предварительное знание Node.js, jQuery и RxJS. Мы сосредоточимся на реализации функции ввода текста в приложении.

Теперь давайте рассмотрим, как мы будем создавать функцию ввода текста в приложении чата.

Настройка учетной записи Pusher

Мы будем использовать Pusher для функций этого чат-приложения в реальном времени. Чтобы создать учетную запись Pusher, перейдите на https://pusher.com/signup. При первом входе в систему появляется всплывающее диалоговое окно, как показано ниже:

Если у вас уже есть учетная запись, войдите в панель управления и нажмите кнопку `Create new app` в `Your apps` слева. Выберите VanillaJS для интерфейсной технологии и Node.js для внутренней технологии. Не волнуйтесь, выбранный вами стек технологий не имеет значения, вы всегда можете изменить его позже. Его цель - сгенерировать стартовый код, который вы можете использовать, чтобы начать общение с Pusher.

После создания приложения перейдите на вкладку `App Keys` и скопируйте свои ** Идентификатор приложения **, ** Ключ ** и ** Секрет ** учетные данные. Мы будем использовать их позже в уроке.

1. Настройте сервер Node.js.

Теперь, когда у вас есть клавиши-толкатели, давайте приступим к созданию приложения для чата в реальном времени.

Сначала сгенерируйте приложение Node.js с помощью этой команды:

npm init -y

Затем установите Express, Pusher и некоторые другие зависимости, которые потребуются серверу:

 npm install --save body-parser ejs express pusher

Когда все будет готово, раздел зависимостей вашего файла package.json должен выглядеть следующим образом:

"dependencies": {
    "body-parser": "^1.16.0",
    "ejs": "^2.5.5",
    "express": "^4.14.1",
    "pusher": "^1.5.1"
  }

Для обслуживания нашего приложения нам нужно сделать три вещи:

  1. Настроить экспресс и толкатель.
  2. Создавайте маршруты для обслуживания страниц нашего приложения и прослушивания веб-запросов.
  3. Запустите экспресс-сервер.

2. Настройте Express и Pusher.

Внутри файла `server.js` мы инициализируем express и pusher следующим образом:

const express = require('express');
const bodyParser = require('body-parser');
const ejs = require('ejs');
const path = require('path');
const Pusher = require('pusher');
const config = require('./config');
const app = express();
//Initialize Pusher
const pusherConfig = config.pusher;
const pusher = new Pusher({
  appId: pusherConfig.appId,
  key: pusherConfig.key,
  secret: pusherConfig.secret,
  encrypted: true,
});

ПРИМЕЧАНИЕ. Обновите файл `config.js`, используя ключи API Pusher, которые вы скопировали ранее.

3. Создайте маршруты для обслуживания нашего приложения.

Настройте Express для обслуживания наших статических файлов из папки `public` и загрузки наших HTML-представлений из `views`.

app.use(express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.get('/', function(req, res) {
  res.render('index', {
    //pass pusher key to index.ejs for pusher client
    pusherKey: pusherConfig.key
  });
});

Затем мы создаем маршрут, который использует толкатель для трансляции события `user_typing`.

const chatChannel = 'anonymous_chat';
const userIsTypingEvent = 'user_typing';
const newMessageEvent = 'user_message';
app.post('/userTyping', function(req, res) {
  const username = req.body.username;
  pusher.trigger(chatChannel, userIsTypingEvent, {username});
  res.status(200).send();
});

Затем маршрут транслирует запросы имени пользователя всем, кто подписан на канал. Более подробную информацию о каналах-пушерах и событиях можно найти в их документации здесь и здесь.

4. Запустите экспресс-сервер.

Затем запустите экспресс-сервер, чтобы прослушивать порт приложения в файле `config.js`.

app.listen(config.appPort, function () {
  console.log('Node server running on port 3000');
});

Теперь у нас есть настройка сервера приложений. Далее мы приступаем к разработке пользовательского интерфейса и функций приложения чата.

5. Настройте веб-страницу приложения чата.

HTML-код приложения чата находится в файле `views/index.ejs`. Сначала мы загружаем jquery, bootstrap, RxJS и наш собственный стиль и javascript:

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Who is typing functionality with Pusher</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,600,700,900,200italic,300italic,400italic,600italic,700italic,900italic' rel='stylesheet' type='text/css'>
  <link rel="stylesheet" href="/css/style.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/cuid/1.3.8/browser-cuid.min.js"></script>
  <script src="https://js.pusher.com/4.0/pusher.min.js"></script>
  <script type="text/javascript" src="/js/app.js"></script>
</head>

Пользовательский интерфейс приложения состоит из трех разделов:

  1. Верхнее меню: содержит заголовок приложения и раздел, показывающий, кто в данный момент набирает текст.
  2. Сообщения: содержит список всех отправленных и полученных сообщений.
  3. Ввод: содержит текстовое поле для ввода сообщений, а также кнопку отправки.

А вот HTML для него:

<div class="chat_window">
  <div class="top_menu">
    <div class="is_typing">
      <em id="user-is-typing"></em>
    </div>
    <div class="title">Anonymous Chat App</div>
  </div>
  <ul class="messages">
  </ul>
  <div class="input bottom_wrapper clearfix">
    <div>
      Your username: <em><span id="username">loading...</span></em>
    </div>
    <br/>
    <div class="message_input_wrapper">
      <input id="message-text-field" class="message_input" placeholder="Type your message here..." />
    </div>
    <div id="send-button" class="send_message">
      <div class="icon"></div>
      <div class="text">Send</div>
    </div>
  </div>
</div>

Теперь, когда пользовательский интерфейс настроен, давайте добавим в приложение функцию ввода текста. Откройте для них `public/js/app.js` файл.

6. Pusher и RxJS

В библиотеке JavaScript Pusher есть функция `subscribe()` для подписки на каналы. Затем вы можете `bind()` использовать каналы, чтобы прослушивать события на них.

pusher.subscribe(channel).bind(event, callbackFunction);

`callbackFunction()` вызывается с данными события, когда на канале идет широковещательная передача. Чтобы преобразовать эту функцию в Observable Stream, давайте создадим вспомогательную функцию:

function createPusherObservable(pusher, channel, event) {
  var pusherMessageStream = new Rx.Subject();
  pusher.subscribe(channel).bind(event, pusherMessageStream.next
                                    .bind(pusherMessageStream));
  return pusherMessageStream;
}

В `createPusherObservable()` мы создаем Rx.Subject и используем его `next()` функцию как `callbackFunction()`. Затем мы возвращаем объект как наблюдаемое, на которое можно подписаться. Итак, мы можем использовать это так:

var pusher = new Pusher(PUSHER_KEY, {});
createPusherObservable(pusher, 'channel', 'event')
  .subscribe();

Для получения дополнительной информации о реактивном программировании посетите сайт ReactiveX.

Теперь давайте посмотрим, как мы можем использовать нашу вспомогательную функцию для прослушивания `user_typing`event.

7. Подписка на событие `user_typing`

Во-первых, нам нужно инициализировать толкатель и некоторые другие переменные.

var pusher = new Pusher(PUSHER_KEY, {encrypted: true,});
var chatChannel = 'anonymous_chat';
var userIsTypingEvent = 'user_typing';
var newMessageEvent = 'user_message';
var currentUsername = generateUsername();
$('#username').html(currentUsername);
function ignoreCurrentUsername(payload) { 
  return payload.username != currentUsername
};

Затем нам нужно будет создать функцию для подписки на наблюдаемое событие `user_typing`.

function handleUserIsTypingEvent(data) {
  if(data === null) {
  //clear user is typing message
    $('#user-is-typing').html('');
  } else {
    $('#user-is-typing').html(data.username + 'is typing...');
  }
}

Он обновляет вводимый пользователем раздел пользовательского интерфейса с именем пользователя или очищает его, если данные равны нулю.

Затем мы создаем Observable для события `user_typing` и подписываемся на него.

var userIsTypingStream = createPusherObservable(pusher, chatChannel, userIsTypingEvent)
     .filter(ignoreCurrentUsername)
userIsTypingStream
  .subscribe(handleUserIsTypingEvent);      

Обратите внимание на `.filter(ignoreCurrentUsername)`, это предотвращает реагирование на события ввода текущего пользователя.

Теперь приложение обновляет свой интерфейс, добавляя имя пользователя, набирающего текст. Но нам также нужно очистить, что пользователь вводит сообщение, когда пользователь перестает печатать или сообщение остается навсегда (а мы определенно этого не хотим). Простое решение - установить таймер, который очищает пользователя от ввода сообщения после нескольких секунд отсутствия события. По опыту, таймер 0,8 секунды дает наилучшие результаты.

RxJS упрощает работу с синхронизированными событиями. Итак, давайте создадим наблюдаемый таймер, который начинает выдавать значение `null` с интервалом 0,8 секунды.

var clearInterval = 800; //0.8 seconds
var userIsTypingClearTimer = Rx.Observable.timer(clearInterval, clearInterval)
  .mapTo(null);

Но нам нужен этот таймер сброса для перезапуска, когда `userIsTypingStream` генерирует новое событие. Этого эффекта можно добиться, объединив два оператора Stream: takeUntil и repeat.

var clearInterval = 800; //0.8 seconds
var userIsTypingClearTimer = Rx.Observable.timer(clearInterval, clearInterval)
  .mapTo(null)
  .takeUntil(userIsTypingStream)
  .repeat();

Таким образом, таймер сброса выдает нулевые значения каждые 0,8 секунды и перезапускается, когда `userIsTypingStream` выдает значение.

Затем обновите код подписки `userIsTypingStream`, объединив с ним поток таймера очистки.

userIsTypingStream
  .merge(userIsTypingClearTimer)
  .distinctUntilChanged()
  .subscribe(handleUserIsTypingEvent);

Обратите внимание на `.distinctUntilChanged()`, это предотвращает генерирование последовательных одинаковых событий, так как это расточительно. См. Больше о uniqueUntilChanged () здесь.

Теперь у нас есть пользователь, который вводит событие, очищенное через 0,8 секунды бездействия. Собрав все коды вместе, мы получим:

var userIsTypingStream = createPusherObservable(pusher, chatChannel, userIsTypingEvent)
  .filter(ignoreCurrentUsername);
var clearInterval = 800; //0.8 seconds
var userIsTypingClearTimer = Rx.Observable.timer(clearInterval, clearInterval)
  .mapTo(null)
  .takeUntil(userIsTypingStream)
  .repeat();
//subscribe to channel events
userIsTypingStream
  .merge(userIsTypingClearTimer)
  .distinctUntilChanged()
  .subscribe(handleUserIsTypingEvent);

Вот и все. RxJS - это круто, правда?

Затем давайте посмотрим, как мы будем транслировать текущее событие `user_typing` пользователей.

8. Трансляция `user_typing` события

Во-первых, давайте создадим функцию для отправки имен пользователей по маршруту `/userTyping` на сервере Node.js.

function publishUserTyping(username) {
  return $.post('/userTyping', {username: username}).promise();
}

Затем нам нужно захватить событие ввода текущего пользователя. Мы используем `Rx.Observable.fromEvent()` для преобразования событий элемента DOM в наблюдаемый поток.

var messageTextField = $('#message-text-field');
var messageInputStream = Rx.Observable.fromEvent(messageTextField, 'keyup');

Теперь нам просто нужно сопоставить это событие с именем пользователя текущего пользователя и подписаться на него.

messageInputStream
  .mapTo(currentUsername)
  .subscribe(publishUserTyping);

Это будет транслировать событие с каждым нажатием клавиши пользователя в текстовом поле сообщения. Хотя это хорошо, но не оптимально, потому что каждая трансляция будет обращаться к серверу.

Лучший способ сделать это - ограничить скорость отправки событий `messageInputStream`. Вы можете увидеть больше информации о дросселировании здесь.

var ENTER_KEY = 13;
var typingStream =  messageInputStream
  .filter(function(event){return (event.which !== ENTER_KEY);})
  .throttleTime(200); //0.2 seconds 
typingStream
  .mapTo(currentUsername)
  .subscribe(publishUserTyping);

Итак, мы создали новый поток из `messageInputStream` под названием `typingStream`. `typingStream` регулирует свои значения на 0,2 секунды. Но вы можете использовать любое время дроссельной заслонки, которое захотите, только убедитесь, что оно меньше, чем время чистого интервала. Затем мы подписываемся на `typingStream`.

Также обратите внимание на `.filter()`, который предотвращает выдачу события набора текста при нажатии `Enter Key`.

Итак, сложив все вместе, окончательный код будет выглядеть так:

function publishUserTyping(username) {
  return $.post('/userTyping', {username: username}).promise();
}
var messageTextField = $('#message-text-field');
var messageInputStream = Rx.Observable.fromEvent(messageTextField, 'keyup');
var ENTER_KEY = 13;
var typingStream =  messageInputStream
  .filter(function(event){return (event.which !== ENTER_KEY);})
  .throttleTime(200); //0.2 seconds
typingStream.mapTo(currentUsername).subscribe(publishUserTyping);

Тестирование

Во-первых, убедитесь, что вы обновили файл `config.js`, указав свои учетные данные pusher.

Чтобы запустить приложение, запустите файл `server.js`, используя следующую команду:

node server.js 

Приложение должно быть запущено. Посетите веб-страницу чата по адресу http: // localhost: 3000. Чтобы опробовать функцию "Кто общается", откройте две веб-страницы чата рядом друг с другом. Когда вы начнете печатать в одном из них, вы заметите, что другое окно показывает, что вы в данный момент печатаете.

Заключение

В этом руководстве мы увидели, как создать функцию ввода текста с помощью Pusher с Node.js и RxJS.

Дайте мне знать в комментариях, как вы считаете, что я мог бы сделать это лучше. Также не забудьте указать этот пост как один или несколько 👏 👏, если вам понравилось то, что вы читали.

Я написал гостевой пост на Pusher о том, как создать функцию чата кто печатает, которая была реализована на простом javascript, поэтому я решил переписать пост, используя RxJS, чтобы реализовать те же функции.

Особая благодарность Охансу Эммануэлю за просмотр этого сообщения.