Статьи

Строительство Риббита в Метеоре

Это продолжение серии клонов в Twitter с созданием Ribbit с нуля, на этот раз с использованием Meteor.

Для этого урока, пожалуйста, не ожидайте подробного объяснения структуры Meteor. У вас уже должен быть опыт работы с Метеором, чтобы вы могли понять некоторые важные концепции, которые будут представлены. Чтобы получить общее представление о Метеоре, я рекомендую пройти курс Andrew Burgesses по Tutsplus Premium.

Итак, начнем.


Мы начнем с создания нового приложения Meteor. Откройте командную строку и перейдите в папку, в которой вы хотите сохранить приложение. Затем запустите:

1
meteor create ribbit

Meteor создаст папку с именем ribbit содержащую несколько файлов, и попросит вас изменить каталог на ribbit и запустить в этой папке команды meteor.

Если вы ribbit папку ribbit и ribbit команду ls -la , чтобы увидеть, что было сгенерировано Meteor, вы найдете скрытую папку с именем .meteor и три файла с именами ribbit.html , ribbit.css и ribbit.js . Папка .meteor содержит только один файл с именем packages . Содержимое файла .meteor/packages информирует Meteor о том, какие умные пакеты используются приложением.

Поскольку мы начнем разработку Ribbit с нуля, мы оставим только папку .meteor и удалим все остальное.

Давайте посмотрим, какие пакеты Meteor использует по умолчанию: в командной строке введите

1
cat .meteor/packages

Выходные данные этой команды должны перечислить пакеты autopublish, unssecure и preserve-input.

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

1
meteor remove autopublish

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

И наконец, пакет preserve-input хранит данные из всех полей формы с уникальным идентификатором.

После всего этого у нас теперь должна быть папка, содержащая только .meteor и она должна стать основой нашего приложения.


Существует несколько правил, которые Метеор использует для обслуживания файлов и данных, которые мы не будем подробно здесь описывать (подробности см. В курсе Эндрю). Важно отметить, что Meteor рассматривает все файлы, присутствующие в папке сервера, как код сервера. Все файлы в клиентских и общих папках являются клиентским кодом, который должен быть передан клиенту. И, наконец, файлы за пределами этих папок обслуживаются как клиентом, так и сервером. Итак, давайте создадим следующие папки:

  • клиент — хранить весь код на стороне клиента.
  • Сервер — для хранения всего кода на стороне сервера.
  • public — содержит все ресурсы, такие как графические файлы, robots.txt
    файлы, значки и т. д.

Кроме того, важно знать, что Meteor рассматривает файлы JavaScript и CSS как часть клиент-серверного кода, а не как ресурсы, которые должны обслуживаться из общей папки.

Итак, мы получаем следующую структуру папок:

Структура нашей папки

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

После распаковки файлов скопируйте папку gfx в gfx папку нашего приложения Ribbit.

Также скопируйте файлы home.html и style.less в папку client нашего приложения. Теперь, если вы попытаетесь запустить сервер, выдав meteor из командной строки, в корне нашего приложения вы увидите сбой сервера и жалобу на установку DOCTYPE в нашем файле.

Метеор ожидает, что наши HTML-файлы будут состоять только из трех базовых элементов: заголовка , тела и тегов шаблона .

Поэтому, чтобы исправить ошибку, мы должны отредактировать файл home.html и удалить теги DOCTYPE и html , которые будут добавлены Meteor при обслуживании файлов.

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

Это легко исправить, установив смарт-пакет Meteor LESS. Итак, выпуск:

1
meteor add less

затем перезагрузите сервер и перезагрузите страницу, и теперь все должно выглядеть немного лучше.

Нам все еще нужно внести некоторые изменения в файл LESS, поскольку не все выглядит так, как должно. Найдите все вхождения style.less файле style.less и измените их, добавив косую черту перед каждым. Это должно быть сделано для того, чтобы разрешить загрузку файлов из корня проекта.

Следующим шагом является настройка файлов home.html и style.less для добавления в кнопку входа в систему и размещения всего в одной строке в заголовке. Файлы должны выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// client/style.less
input {
  width: 236px;
  height: 26px;
  border: 1px solid @border-color;
  padding: 0 10px;
  outline: none;
  font-size: 17px;
  &:focus {
    background: #FFFDF2;
  }
}
 
input[type=»submit»] {
  height: 26px;
  width: auto;
  border: 1px solid #7BC574;
  border-radius: 2px;
  color: white;
  font-size: 12px;
  font-weight: bold;
  padding: 0 20px;
  cursor: pointer;
  .gradient4f(0%, #8CD585, 23%, #82CD7A, 86%, #55AD4C, 100%, #4FA945);
}
 
header {
  background: url(/gfx/bg-header.png);
  height: 85px;
  width: 100%;
  div.wrapper {
    padding: 11px 0;
    img {
      position: relative;
      top: 10px;
      margin: 0 15px 0 0;
    }
    span {
      font-size: 18px;
      margin: 0 42px 0 0;
    }
    p {
      display: inline;
      input {
        margin: 0 0 0 14px;
        width: 180px;
        height: 28px;
      }
    }
    #btnLogOut {
      float: right;
      width: auto;
      height: 28px;
      margin: 19px 0 0 0;
    }
  }
}

А вот как должен выглядеть файл home.html :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!— client/home.html —>
<head>
</head>
<body>
  <header>
    <div class=»wrapper»>
      <img src=»gfx/logo.png»>
      <span>Twitter Clone
      <p>
        <input name=»username» placeholder=»username» type=»text»>
        <input name=»password» placeholder=»password» type=»password»>
        <input type=»submit» id=»btnLogOut» value=»Log In»>
      </p>
    </div>
  </header>
  <div id=»content»>
    <div class=»wrapper»>
      <img src=»gfx/frog.jpg»>
      <div class=»panel right»>
        <h1>New to Ribbit?</h1>
        <p>
          <input name=»email» placeholder=»email» type=»text»>
          <input name=»username» placeholder=»username» type=»text»>
          <input name=»fullname» placeholder=»fullname» type=»text»>
          <input name=»password» placeholder=»password» type=»text»>
          <input name=»password2″ placeholder=»retype password» type=»password»>
          <input type=»submit» value=»Create Account»>
        </p>
      </div>
    </div>
  </div>
  <footer>
    <div class=»wrapper»>
      Ribbit — A Twitter Clone Tutorial<img src=»gfx/logo-nettuts.png»>
    </div>
  </footer>
</body>

Теперь взгляните на страницу в браузере. Это должно выглядеть как на картинке ниже:

Наша домашняя страница.

Ну, это было довольно легко, не так ли? Но то, что мы сделали до сих пор, — это не то, чего мы действительно желали, и это определенно далеко от следования стилю ведения дел в Метеоре. Давайте исправим это!


Метеор строит HTML-страницу, отправляемую в браузер, на основе трех элементов и ожидает, что будет найдено следующее: элемент head, элемент body и элемент template. Поскольку голова и тело уже хорошо известны, особое внимание следует уделить элементу шаблона.

Шаблон объявляется с помощью <template name="foo">...</template> и его содержимое отображается в содержимом HTML, которое отправляется в браузер. С другой стороны, в файле JavaScript доступ к шаблону можно получить с помощью Template.foo , который при вызове возвращает строку HTML.

Теперь Meteor использует Handlebars в качестве основы шаблона, но при необходимости можно использовать и другие, например Jade.

Итак, давайте посмотрим, что нужно сделать, чтобы преобразовать файл home.html в страницу, совместимую с Meteor. Давайте посмотрим на модифицированный код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!— client/home.html —>
<head>
 
</head>
 
<body>
  <header>
    {{> header}}
  </header>
  {{> content}}
  <footer>
    {{> footer}}
  </footer>
</body>
 
<template name=»header»>
  <div class=»wrapper»>
    <img src=»gfx/logo.png»>
    <span>Twitter Clone
    <p>
      <input id=»username» name=»username» placeholder=»username» type=»text»>
      <input id=»password» name=»password» placeholder=»password» type=»password»>
      <input id=»btnLogOut» type=»submit» value=»Log In»>
    </p>
  </div>
</template>
 
<template name=»content»>
  <div id=»content»>
    <div class=»wrapper»>
      <img src=»gfx/frog.jpg»>
      <div class=»panel right»>
        <h1>New to Ribbit?</h1>
        <p>
          <input id=»email» name=»email» placeholder=»email» type=»text»>
          <input id=»newusername» name=»username» placeholder=»username» type=»text»>
          <input id=»fullname» name=»fullname» placeholder=»fullname» type=»text»>
          <input id=»newpassword» name=»password» placeholder=»password» type=»text»>
          <input id=»password2″ name=»password2″ placeholder=»retype password» type=»password»>
          <input id= «btnCreateAccount» type=»submit» value=»Create Account»>
        </p>
      </div>
    </div>
  </div>
</template>
 
<template name=»footer»>
  <div class=»wrapper»>
    Ribbit — A Twitter Clone Tutorial<img src=»gfx/logo-nettuts.png»>
  </div>
</template>

Как видите, мы начинаем с пустого элемента head. Поскольку Meteor загружает все необходимые нам файлы, мы можем использовать элемент head, чтобы установить заголовок страницы, но на данный момент он пуст.

Внутри элемента body мы отображаем три шаблона: шаблон заголовка, который мы помещаем в тег заголовка, шаблон содержимого, который помещается сразу после заголовка, и, наконец, шаблон нижнего колонтитула, внутри тега нижнего колонтитула.

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

Я также добавил некоторые идентификаторы в поля редактирования и кнопки, чтобы позже я смог получить к ним доступ из кода JavaScript.

Вот и все. Теперь наша страница готова и хорошо работает с Meteor Framework.

Следующим шагом будет создание страницы друзей, которая должна отображаться при входе пользователя в систему. Нам также нужно разрешить пользователю зарегистрироваться и войти в наше приложение.


Если мы выполним те же действия, что и в предыдущем разделе, чтобы преобразовать страницу buddies.html страницу Meteor, мы закончим следующим кодом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!— client/buddies.html —>
<head>
 
</head>
<body>
  <header>
    {{> header}}
  </header>
  {{> content}}
  <footer>
    {{> footer}}
  </footer>
</body>
 
<template name=»header»>
  <div class=»wrapper»>
    <img src=»gfx/logo.png»>
    <span>Twitter Clone
    <p>
      <input type=»submit» id=»btnLogOut» value=»Log Out»>
    </p>
  </div>
</template>
 
<template name=»content»>
  <div id=»content»>
    <div class=»wrapper»>
      <div id=»createRibbit» class=»panel right»>
        <h1>Create a Ribbit</h1>
        <p>
          <textarea name=»text» class=»ribbitText»></textarea>
          <input type=»submit» value=»Ribbit!»>
        </p>
      </div>
      <div id=»ribbits» class=»panel left»>
        <h1>Your Ribbit Profile</h1>
        <div class=»ribbitWrapper»>
          <img class=»avatar» src=»gfx/user1.png»>
          <span class=»name»>Frogger
          <p>
            567 Ribbits<span class=»spacing»>45 Followers
            Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
          </p>
        </div>
      </div>
      <div class=»panel left»>
        <h1>Your Ribbit Buddies</h1>
        <div class=»ribbitWrapper»>
          <img class=»avatar» src=»gfx/user2.png»>
          <span class=»name»>Kermit
          <p>
            Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
          </p>
        </div>
      </div>
    </div>
  </div>
</template>
 
<template name=»footer»>
  <div class=»wrapper»>
    Ribbit — A Twitter Clone Tutorial<img src=»gfx/logo-nettuts.png»>
  </div>
</template>

Как видите, я вынул длинный список ребер, но кроме этого, в вышеприведенном коде нет ничего более впечатляющего.

Однако с этим есть две проблемы:

  • Если вы попытаетесь просмотреть его в браузере, вы увидите, что он отображается дважды. Для этого есть две причины: Meteor отправляет файлы в браузер в алфавитном порядке, поэтому buddies.html отправляется buddies.html . У нас также много дублирования в коде, даже дублируются названия шаблонов. Поэтому, когда Meteor хочет отобразить шаблон контента, он отображает первый определенный файл, который находится в файле buddies.html .
  • Вторая проблема — также дублирование кода, которое необходимо исправить.

Вышеперечисленные проблемы можно решить, выполнив следующие действия: мы создадим файл index.html содержащий только код, который будет запускать необходимые шаблоны: один для верхнего колонтитула, один для нижнего колонтитула, один для домашней страницы и один для Страница друзей.

Давайте посмотрим на каждого из них:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<!— client/index.html —>
<head>
 
</head>
 
<body>
  <header>
    {{> header}}
  </header>
  {{#if currentUser}}
    {{> buddiescontent}}
  {{else}}
    {{> homecontent}}
  {{/if}}
  <footer>
    {{> footer}}
  </footer>
</body>

Это основной файл, в который загружаются все шаблоны. Очень интересная вещь может быть найдена при отображении шаблона контента. Мы проверяем с помощью Handlebars, if предложение, пользователь вошел или нет. currentUser Meteor currentUser содержит данные пользователя, вошедшего в систему, если пользователь вошел в систему (поэтому мы отображаем шаблон друзей), и равен нулю, если пользователь не вошел в систему (поэтому мы отображаем домашний шаблон).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<!— client/header.html —>
<template name=»header»>
  <div class=»wrapper»>
    <img src=»gfx/logo.png»>
    <span>Twitter Clone
    {{#if currentUser}}
      <nav>
        <a id=»buddies» href=»#»>Your Buddies</a>
        <a href=»#»>Public Ribbits</a>
        <a id=’profiles’ href=»#»>Profiles</a>
      </nav>
      <input type=»submit» id=»btnLogOut» value=»Log Out» />
    {{else}}
      <p>
        <input id=»username» name=»username» placeholder=»username» type=»text»>
        <input id=»password» name=»password» placeholder=»password» type=»password»>
        <input id=»btnLogOut» type=»submit» value=»Log In»>
      </p>
    {{/if}}
  </div>
</template>

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

1
2
3
4
5
6
<!— client/footer.html —>
<template name=»footer»>
  <div class=»wrapper»>
    Ribbit — A Twitter Clone Tutorial<img src=»gfx/logo-nettuts.png»>
  </div>
</template>

Затем мы создаем шаблон нижнего колонтитула, который отображает только содержимое нижнего колонтитула.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<!— client/home.html —>
<template name=»homecontent»>
  <div id=»content»>
    <div class=»wrapper»>
      <img src=»gfx/frog.jpg»>
      <div class=»panel right»>
        <h1>New to Ribbit?</h1>
        <p>
          <input id=»email» name=»email» placeholder=»email» type=»text»>
          <input id=»newusername» name=»username» placeholder=»username» type=»text»>
          <input id=»fullname» name=»fullname» placeholder=»fullname» type=»text»>
          <input id=»newpassword» name=»password» placeholder=»password» type=»password»>
          <input id=»password2″ name=»password2″ placeholder=»retype password» type=»password»>
          <input id= «btnCreateAccount» type=»submit» value=»Create Account»>
        </p>
      </div>
    </div>
  </div>
</template>

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!— client/buddies.html —>
<template name=»buddiescontent»>
  <div id=»content»>
    <div class=»wrapper»>
      <div id=»createRibbit» class=»panel right»>
        <h1>Create a Ribbit</h1>
        <p>
          <textarea name=»text» class=»ribbitText»></textarea>
          <input type=»submit» value=»Ribbit!»>
        </p>
      </div>
      <div id=»ribbits» class=»panel left»>
        <h1>Your Ribbit Profile</h1>
        <div class=»ribbitWrapper»>
          <img class=»avatar» src=»gfx/user1.png»>
          <span class=»name»>Frogger
          <p>
            567 Ribbits<span class=»spacing»>45 Followers
            Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
          </p>
        </div>
      </div>
      <div class=»panel left»>
        <h1>Your Ribbit Buddies</h1>
        <div class=»ribbitWrapper»>
          <img class=»avatar» src=»gfx/user2.png»>
          <span class=»name»>Kermit
          <p>
            Cras justo odio, dapibus ac facilisis in, egestas Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
          </p>
        </div>
      </div>
    </div>
  </div>
</template>

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

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

Meteor предоставляет нам несколько умных пакетов для обработки процесса регистрации и входа в приложение. Для нашего приложения Ribbit мы будем использовать следующие пакеты: account-base для базовой поддержки для учета и account-password для получения поддержки паролей для учета. Чтобы установить эти пакеты, выполните следующие команды:

1
2
meteor add accounts-base
meteor add accounts-password

Теперь давайте создадим файл client.js в папке client для хранения нашего клиентского кода. Для домашней страницы нам нужно обработать события для нажатия на кнопку « Log In и кнопку « Create Account .

События в Meteor связаны с определенным шаблоном, поэтому для обработки нажатия на кнопку « Log In мы добавим обработчик событий для шаблона заголовка. Чтобы обработать событие click для кнопки Create Account , мы должны добавить обработчик события, связанный с шаблоном homecontent.

Давайте посмотрим на код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
// client/client.js
// handling click event on the Log In button
Template.header.events({
  ‘click #btnLogOut’: function (event, template) {
    if (Meteor.userId()) {
      Meteor.logout();
    } else {
      var userName = template.find(‘#username’).value,
        userPassword = template.find(‘#password’).value;
      Meteor.loginWithPassword(userName, userPassword, function (error) {
        if (error) {
          console.log(error);
        }
      });
    }
  }
});

В первой строке мы прикрепляем объект событий к шаблону заголовка. Затем мы обрабатываем событие нажатия на кнопку с идентификатором btnLogOut (который мы создали в нашем HTML-шаблоне). Затем, если пользователь вошел в систему, просто выйдите из системы. Если Meteor.userId возвращает значение NULL, это означает, что ни один пользователь не вошел в систему, поэтому мы выполняем вход с предоставленными именем пользователя и паролем. Также обратите внимание на значения, которые вставляются в поля редактирования: у объекта шаблона есть функция поиска, которая принимает в качестве параметра идентификатор поля редактирования, для которого мы хотим получить значение. Если при входе в систему возникает ошибка, мы просто отображаем ее в консоли. Пока что это временный подход, так как мы реализуем отображение информации для пользователя чуть позже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// client/client.js
// hnadling click event on the Create Accounts button
Template.homecontent.events({
  ‘click #btnCreateAccount’: function (event, template) {
    var userEmail = template.find(‘#email’).value,
      userName = template.find(‘#newusername’).value,
      password = template.find(‘#newpassword’).value,
      password2 = template.find(‘#password2’).value,
      name = template.find(‘#fullname’).value;
 
    Accounts.createUser({
      username: userName,
      email: userEmail,
      password: password,
      profile: {
        name: name
      }
    }, function (error) {
      if (error) {
        console.log(«Cannot create user»);
      }
    });
  }
});

Как и раньше, мы прикрепляем объект событий к шаблону homecontent , затем получаем значения, отправленные пользователем из полей формы. Для создания учетной записи мы просто используем функцию Accounts.createUser , которая принимает в качестве параметра объект с компонентами имени пользователя, пароля, электронной почты и профиля. В случае возникновения ошибки при добавлении пользователя, ошибка отображается в консоли.


Следующий шаг в разработке нашего приложения — сделать страницу друзей динамичной. На данный момент все, что отображается на нашей странице, статически берется из файлов шаблонов. Мы хотим взять информацию из базы данных, а также позволить пользователю создавать кролики и хранить их в базе данных на стороне сервера.

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

Мы будем хранить код для этой части в файле main.js Обратите внимание, что Meteor загружает файлы со словом main в их имени после загрузки страницы.

Давайте посмотрим, как отобразить их в шаблоне:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<!— client/buddies.html —>
  <div id=»ribbits» class=»panel left»>
<h1>Your Ribbit Profile</h1>
<div class=»ribbitWrapper»>
<img class=»avatar» src=»gfx/user1.png»>
<span class=»name»>{{fullName}}
<p>
{{noOfRibbits}}<span class=»spacing»>45 Followers
{{lastRibbit}}
</p>
    </div>
  </div>

В этом шаблоне мы хотим использовать переменные Handlebars для элементов, которые мы хотели бы извлечь из базы данных. fullname username и username берутся из коллекции пользователей, а noOfRibbits и lastRibbit должны быть взяты из коллекции ribbit.

main.js выше переменные возвращаются в шаблон с помощью следующего кода JavaScript в файле main.js :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// client/main.js
Ribbits = new Meteor.Collection(‘ribbits’);
 
Template.buddiescontent.helpers({
  fullName: function () {
    return Meteor.user().profile.name;
  },
 
  userName: function () {
    return Meteor.user().username;
  },
 
  noOfRibbits: function () {
    var ribbits = Ribbits.find({user_id: Meteor.userId()}),
      retVal;
    if (ribbits.count() === 1) {
      retVal = «1 Ribbit»;
    } else {
      retVal = ribbits.count() + » Ribbits»;
    }
    return retVal;
  },
 
  lastRibbit: function () {
    var lastRibbit = Ribbits.findOne({user_id: Meteor.userId()}, {sort: {created_at: -1}}),
      retVal;
 
    if (lastRibbit) {
      retVal = lastRibbit.ribbit;
    } else {
      retVal = ‘This user has no Ribbits’;
    }
 
    return retVal;
  }
});

В двух принципах Meteor утверждается, что доступ к базе данных осуществляется с использованием одного и того же API как на сервере, так и на клиенте, а на стороне клиента выполняется моделирование модели, которое должно выглядеть как подключение к базе данных с нулевой задержкой (компенсация задержки) , Эти принципы можно увидеть в приведенном выше коде, в первой строке. Мы создаем экземпляр Ribbits из коллекции Meteor, которая является клиентской базой данных. Поскольку на стороне сервера у нас нет коллекции ribbits, определенная в нашем коде будет пустой.

Затем определяется вспомогательная функция, которая принимает в качестве параметра объект с функцией для каждой переменной Handlebars в шаблоне. Как видите, для каждого возвращаются соответствующие данные.

fullName и userName взяты из объекта профиля базы данных пользователей.

noOfRibbits и lastRibbit должны быть взяты из коллекции Ribbits. Поскольку в данный момент он пуст, мы ожидаем, что функции, приведенные выше, будут возвращать ноль в качестве числа ребят и сообщение «У этого пользователя нет ребер» вместо последнего ребра.

Давайте посмотрим на код. Первый шаблон:

01
02
03
04
05
06
07
08
09
10
11
12
<!— client/buddies.html —>
<div class=»panel left»>
<h1>Your Ribbit Buddies</h1>
{{#each ribbits}}
<div class=»ribbitWrapper»>
  <img class=»avatar» src=»gfx/user2.png»>
  <span class=»name»>{{buddyFullName}}
  <p>
  {{ribbit}}
  </p>
</div>
{{/each}}

Новым здесь является то, что мы используем Handlebars « each чтобы пройтись по всем ребрам, возвращаемым функцией JavaScript. Остальное очень похоже на код внутри раздела профиля.

Теперь для кода JavaScript:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
// client/main.js
buddyFullName: function () {
  Ribbits.find().forEach(function (ribbit) {
    var theUser = Meteor.users.findOne({_id: ribbit.user_id});
 
    return theUser.profile.name;
  });
},
 
buddyUserName: function () {
  Ribbits.find().forEach(function (ribbit) {
    var theUser = Meteor.users.findOne({_id: ribbit.user_id});
 
    return theUser.username;
  });
},
 
ribbits: function () {
  return Ribbits.find();
}

В этой области мы хотим отобразить все ленты, принадлежащие текущему зарегистрированному пользователю и всем пользователям, за которыми следует текущий зарегистрированный пользователь. Мы реализуем на сервере механизм возврата клиенту только тех ребер, которые удовлетворяют вышеуказанному условию. В клиенте сканируется коллекция Ribbits, и для каждого ищется пользователь с тем же идентификатором, что и в хранилище ribbit. Затем полное имя и имя пользователя возвращаются в шаблон. Что касается ребер, так как у нас уже есть те, которые нас интересуют, их можно просто полностью вернуть в шаблон.

При добавлении риббита необходимо выполнить две основные задачи:

  • Сначала мы должны вставить запись в коллекцию ribbits.
  • Затем обновите страницу друзей со следующей информацией: число
    ribbits, последний ribbit в разделе «Ваш профиль Ribbit» и последний
    ribbit, который мог быть опубликован любым пользователем, вместе с его автором и временем, прошедшим с момента его публикации.

Пришло время написать немного серверного кода. Начнем со следующего:

1
2
3
4
5
// server/server.js
Ribbits = new Meteor.Collection(‘ribbits’);
Meteor.publish(‘ribbits’, function () {
  return Ribbits.find({});
});

Для приведенного выше кода мы создаем экземпляр коллекции Meteor для коллекции ribbits и публикуем все данные ribbits для клиента.

Теперь для кода на стороне клиента:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
// client/client.js
Ribbits = new Meteor.Collection(‘ribbits’);
Meteor.subscribe(‘ribbits’);
 
 
// handling the click event on the Ribbit button
Template.buddiescontent.events({
  ‘click #createTheRibbit’: function (event, template) {
    var ribbitContent= template.find(‘.ribbitText’).value;
 
    Ribbits.insert({
      user_id: Meteor.user()._id,
      ribbit: ribbitContent,
      created_at: new Date()
    });
    template.find(‘.ribbitText’).value = «»;
  }
});

Так же, как на стороне сервера, клиент также содержит экземпляр Ribbits коллекции ribbits для поддержки концепции «данных везде». Кроме того, он также должен подписаться на данные, опубликованные сервером, чтобы убедиться, что изменения, сделанные в одном экземпляре приложения, реплицируются повсеместно.

После этого событие клика для Ribbit! Кнопка обрабатывается так: данные, вставленные в текстовое поле, читаются и вставляются в коллекцию ribbits. После этого содержимое текстового поля устанавливается в пустую строку.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// client/main.js
ribbits: function () {
  return Ribbits.find({}, {sort: {created_at: -1}});
},
 
buddyFullName: function (ribbitUserId) {
  var theUser = Meteor.users.findOne({_id: ribbitUserId});
  return theUser.profile.name;
},
 
buddyUserName: function (ribbitUserId) {
  var theUser = Meteor.users.findOne({_id: ribbitUserId});
  return theUser.username;
},
 
elapsedTime: function (text) {
  var currentDate = new Date(),
    ribbitDate,
    minutes_elapsed,
    hours_elapsed,
    days_elapsed,
    retVal,
    record = Ribbits.findOne({ribbit: text});
 
  ribbitDate = new Date(record.created_at);
  minutes_elapsed = (currentDate — ribbitDate) / 60000;
  if (minutes_elapsed > 60) {
    hours_elapsed = minutes_elapsed / 60;
    if (hours_elapsed > 24) {
      days_elapsed = hours_elapsed / 24;
      retVal = parseInt(days_elapsed, 10) + «d»;
    } else {
      retVal = parseInt(hours_elapsed, 10) + «h»;
    }
  } else {
    retVal = parseInt(minutes_elapsed, 10) + «m»;
  }
  return retVal;
}

Теперь вышеприведенный код вводит динамические данные в переменные шаблона Handlebars. Позвольте мне объяснить:

  • Значение ribbits заменяется экземпляром коллекции со всеми значениями, хранящимися в базе данных, в обратном порядке создания.
  • buddyFullName возвращается путем поиска в коллекции пользователей пользователя с тем же идентификатором, что и у текущего пользователя ribbit. Обратите внимание, что ribbitUserId является параметром, полученным функцией из шаблона.
  • buddyUserName получается аналогично buddyFullName.
  • elapsedTime рассчитывается на основе текущего времени и времени, когда кролик был изначально создан.

Шаблон buddies теперь выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
<!— client/buddies.html —>
<div class=»panel left»>
  <h1>Your Ribbit Buddies</h1>
  {{#each ribbits}}
  <div class=»ribbitWrapper»>
    <img class=»avatar» src=»gfx/user2.png»>
    <span class=»name»>{{buddyFullName user_id}}
    <p>
    {{ribbit}}
    </p>
  </div>
  {{/each}}
</div>

Интересно отметить, что за buddyFullName следует переменная user_id взятая из коллекции ribbits. Это параметр из функции, которую мы описали выше.


Эта задача не должна быть ограничителем показа, так как мы уже сделали нечто подобное со страницей друзей. Здесь нужно сделать файл public.html из загруженных ресурсов и превратить его в шаблон. Затем мы подключим данные, которые мы хотим отобразить на странице, к некоторым функциям JavaScript, которые возьмут эти данные из базы данных и вернут их в шаблон.

Сначала давайте посмотрим файл шаблона:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!— client/public.html —>
<template name=»public»>
  <div id=»content»>
    <div class=»wrapper»>
      <div class=»panel right»>
        <h1>Create a Ribbit</h1>
        <p>
          <textarea name=»text» class=»ribbitText»></textarea>
          <input type=»submit» value=»Ribbit!»>
        </p>
      </div><!— panel right —>
      <div id=»ribbits» class=»panel left»>
        <h1>Public Ribbits</h1>
        {{#each ribbits}}
          <div class=»ribbitWrapper»>
            <img class=»avatar» src=»gfx/user2.png»>
            <span class=»name»>{{publicUserFull user_id}}
            <p>
              {{ribbit}}
            </p>
          </div><!— ribbitWrapper —>
        {{/each}}
      </div><!— panel left —>
    </div><!— wrapper —>
  </div><!— content —>
</template>

Ничего особенного здесь нет: в разделе Public Ribbits отображаются publicUserFull и publicUserName а также user_id user_id отправляется функции, связанной с ними.

Код JavaScript выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// client/client.js
Template.public.helpers({
  ribbits: function () {
    return Ribbits.find({}, {sort: {created_at: -1}});
  },
 
  publicUserFull: function (currentRibbitId) {
    var theUser = Meteor.users.findOne({_id: currentRibbitId});
 
    return theUser.profile.name;
  },
 
  publicUserName: function (currentRibbitId) {
    var theUser = Meteor.users.findOne({_id: currentRibbitId});
 
    return theUser.username;
  },
 
  elapsedTime: function (text) {
    var currentDate = new Date(),
      ribbitDate,
      minutes_elapsed,
      hours_elapsed,
      days_elapsed,
      retVal,
      record = Ribbits.findOne({ribbit: text});
 
    ribbitDate = new Date(record.created_at);
    minutes_elapsed = (currentDate — ribbitDate) / 60000;
    if (minutes_elapsed > 60) {
      hours_elapsed = minutes_elapsed / 60;
      if (hours_elapsed > 24) {
        days_elapsed = hours_elapsed / 24;
        retVal = parseInt(days_elapsed, 10) + «d»;
      } else {
        retVal = parseInt(hours_elapsed, 10) + «h»;
      }
    } else {
      retVal = parseInt(minutes_elapsed, 10) + «m»;
    }
    return retVal;
  }
});

Обратите внимание, что все функции в этом фрагменте кода аналогичны функциям, определенным в разделе «Друзья» файла. Вы должны видеть, что у нас происходит массовое повторение кода, что не так хорошо. Но на данный момент давайте оставим все как есть, чтобы увидеть, как это работает, и мы выясним, как мы можем реорганизовать это на следующем этапе.


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

Для выполнения этой задачи мы будем использовать концепцию реактивных данных Meteor. Мы уже видели это в действии на странице друзей; при добавлении нового кролика он автоматически отображается на странице.

Мы хотим добиться того, чтобы каждый раз, когда пользователь нажимал на ссылку Public Ribbits в заголовке, чтобы перейти с текущей отображаемой страницы на страницу Public Ribbits. Поэтому нам нужна переменная currentPage , которую мы можем периодически менять, чтобы она указывала на нужную страницу.

Мы будем использовать глобальный объект Meteor’s Session, который содержит произвольный набор пар ключ-значение. Мы будем хранить строку в переменной currentPage , которая указывает на имя страницы, которую мы хотим отобразить. Затем, нажимая на ссылку, мы меняем переменную currentPage чтобы отобразить новую страницу.

Давайте начнем с кода. Во-первых, основной файл шаблона, index.html:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!— client/index.html —>
<head>
 
</head>
 
<body>
  <header>
    {{> header}}
  </header>
  {{> content}}
  <footer>
    {{> footer}}
  </footer>
</body>
 
<template name=»content»>
  {{#if currentUser}}
    {{#if currentPage «buddies»}}
      {{> buddiescontent}}
    {{/if}}
 
    {{#if currentPage «public»}}
      {{> public}}
    {{/if}}
 
    {{#if currentPage «profile»}}
      {{> profile}}
    {{/if}}
  {{else}}
    {{> homecontent}}
  {{/if}}
</template>

Изменения в этом файле небольшие. Был добавлен новый шаблон с именем content и в этом шаблоне проверяется значение переменной currentPage и отображается соответствующий суб-шаблон.

Давайте посмотрим, как это отражается в коде JavaScript:

1
2
3
4
5
6
7
// client/main.js
Template.content.helpers({
  currentPage: function (type) {
    var thePage = Session.get(«currentPage»);
    return thePage === type;
  }
});

Был добавлен помощник для шаблона content , содержащий функцию currentPage , которая возвращает логическое значение, сверяя параметр, отправленный этой функции, со значением currentPage , хранящимся в сеансе метеора. Функция возвращает true если они одинаковы, или false если нет. Таким образом, его значение может быть проверено в шаблоне, чтобы решить, какой суб-шаблон будет отображаться.

1
2
// client/client.js
Session.set(«currentPage», «buddies»);

Теперь для файла client.js значение currentPage инициализируется в начале приложения путем его сохранения в buddies , поэтому по умолчанию, если пользователь вошел в систему, отображается страница друзей.

01
02
03
04
05
06
07
08
09
10
// client/client.js
‘click #public’: function (event, template) {
  Session.set(«currentPage», «public»);
},
 
‘click #buddies’: function (event, template) {
  Session.set(«currentPage», «buddies»);
},

Затем объект событий заголовка обновляется путем добавления двух обработчиков Public Ribbits , одного для ссылки Public Ribbits и одного для ссылки Your Buddies , чтобы пользователь мог перейти на следующие страницы:

Как вы можете видеть в этой функции, единственное, что мы делаем, это устанавливаем значение Session для currentPage на желаемое значение.

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


Как вы могли заметить, при создании каждой новой страницы мы следовали шаблону, которому мы следовали на протяжении всего этого урока: мы начали с реализации шаблона страницы, а затем добавили необходимый код JavaScript для придания странице динамичности. Наконец, мы добавляем некоторые обработчики событий для действий, которые пользователь может выполнять на странице. Давайте теперь следуем этому же шаблону для создания страницы profiles .

Код шаблона profile должен выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template name=»profile»>
  <div id=»content»>
    <div class=»wrapper»>
      <div class=»panel right»>
        <h1>Search for profiles</h1>
        <p>
          <input name=»query» type=»text»>
          <input type=»submit» value=»Search!»>
        </p>
      </div>
      <div id=»ribbits» class=»panel left»>
        <h1>Public Profiles</h1>
        {{#each users}}
          <div class=»ribbitWrapper»>
            <img class=»avatar» src=»{{gravatarLink _id}}»>
            <span class=»name»>{{profile.name}}
            <p id=’last-ribbit’>
              {{lastRibbit _id}}
            </p>
          </div>
        {{/each}}
      </div>
    </div>
  </div>
</template>

Для этого шаблона наша страница разделена на две панели: правая панель позволяет искать определенный профиль пользователя, а на левой панели у нас есть список зарегистрированных пользователей. Также на левой панели зарегистрированный пользователь имеет возможность подписаться или отписаться от другого пользователя, щелкнув ссылку «следовать / отписаться».

Поскольку код на левой панели выглядит более интересным, давайте сначала начнем его анализировать: после части заголовка мы перебираем коллекцию пользователей Meteor с использованием конструкции Handlebars #each .

Для каждого пользователя мы отображаем следующую информацию:

  • Граватар пользователя (получен так же, как мы делали это на странице друзей).
  • Имя пользователя, взятое из базы данных mongoDb.
  • Имя пользователя
  • Количество пользователей следует.
  • Ссылка «следовать / отписаться»
  • Последний ребёнок пользователя.

Что касается правой панели, этот код содержит форму, которая позволяет пользователю искать определенный профиль.

Теперь давайте перейдем ко второму шагу нашего шаблона, добавив JavaScript.

Первое, что нужно шаблону — это коллекция users . Давайте посмотрим на код, который возвращает эту коллекцию:

01
02
03
04
05
06
07
08
09
10
11
12
users: function () {
  if (Session.get(‘searchedName’) !== undefined) {
    return Meteor.users.find({
      $and: [
        {_id: {$ne: Meteor.userId()}},
        {username: Session.get(‘searchedName’)}
      ]
    });
  } else {
    return Meteor.users.find({_id: {$ne: Meteor.userId()}});
  }
},

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

Позвольте мне объяснить это более подробно: когда выполняется поиск определенного профиля пользователя, как мы увидим позже, мы устанавливаем переменную профиля с именем searchedName , которая содержит имя, которое ищет пользователь. Затем мы фильтруем коллекцию пользователей с использованием конструкции $and Mongo для соответствия следующим критериям: она должна возвращать только тех пользователей, которые не являются текущими вошедшими в систему пользователями, и внутри имени username мы будем хранить searchedName . Я должен признать, что здесь я немного упрощаю, так like вместо этого мог бы реализовать предложение like , чтобы вернуть всех пользователей с именем пользователя, похожим на searchedName . Но так как это не учебник по Монго, я оставлю это как дополнительное упражнение для вас, чтобы попробовать самостоятельно.

По умолчанию должны отображаться все пользователи кроме текущего
вошел в систему пользователя, что делается в предложении else.

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

То же самое относится и к имени, имени пользователя и noOfFollowers.

Теперь для последующей части текста, мы собираемся сделать вещи немного по-другому. Поведение, которое мы намереваемся иметь здесь, состоит в отображении текста «следовать», если текущий вошедший в систему пользователь еще не следует за отображаемым пользователем. В противном случае мы хотим позволить пользователю прекратить подписку на пользователя, отображая текст «unfollow».

Код выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
followText: function (userId) {
  var followee = Follows.findOne({
    $and: [
      {followee_id: Meteor.userId()},
      {user_id: userId}
    ]
  });
  if (followee) {
    return 'unfollow';
  } else {
    return 'follow';
  }
},

Эта функция получает userIdпараметр из шаблона и выполняет поиск в таблице «Follows» для пользователя, у followee_idкоторого есть a, который совпадает с идентификатором текущего пользователя, и который также имеетuser_id , который совпадает с идентификатором , который совпадает с идентификатором пользователя, по которому щелкнули. Если этот критерий удовлетворен, это означает, что за пользователем следует текущий вошедший в систему пользователь, поэтому верните строку «unfollow», в противном случае верните строку «follow».

В действительности, на странице профиля пользователи могут выполнить только два действия: нажать на кнопку Поиск! Кнопка, чтобы искать другие профили пользователей. И второе действие — подписаться или отписаться от другого пользователя, нажав соответствующую ссылку.

Давайте разберемся с ними один за другим:

1
2
3
4
5
6
7
8
9
'click input[type="submit"]': function(event, template) {
  var searchedUser = template.find('input[type="text"]').value;
  if (searchedUser !== "") {
    Session.set('searchedName', searchedUser);
  } else {
    Session.set('searchedName', undefined);
  }
  Template.profile();
}

Когда пользователь нажимает на наш Поиск! Кнопка (поле ввода с типом отправки), мы читаем имя, введенное в текстовое поле и устанавливаем переменную сеанса с именемsearchedName для хранения этого значения. На предыдущем шаге мы уже видели, как эта переменная используется для поиска в базе данных и возврата подмножества коллекции пользователей, содержащей только пользователей с таким именем пользователя.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'click .follow': function(event, template) {
  var isFollowed, theClickedUserId = event.currentTarget.id,
    theFollowees = Follows.find({user_id: theClickedUserId});
 
  theFollowees.forEach(function (theFollowee) {
    if (theFollowee.followee_id === Meteor.userId()) {
      isFollowed = true;
    } else {
      isFollowed = false;
    }
  });
 
  if (!isFollowed) {
    Follows.insert({
      user_id: theClickedUserId,
      followee_id: Meteor.userId()
    });
  } else {
    Follows.remove({
     $and: [
       {user_id: theClickedUserId},
       {followee_id: Meteor.user()._id}
      ]
    });
  }
},

Мы начнем с theClickedUserIdпеременной, которая будет хранить идентификатор пользователя, на которого нажали. Затем мы ищем в коллекции Follows всех пользователей с этим идентификатором и сохраняем результат в theFolloweesпеременной.

Следующим шагом является цикл по theFolloweesколлекции и проверка, совпадает ли текущий подписчик с followee_idидентификатором текущего вошедшего в систему пользователя. Если это так, вошедший в систему пользователь следует за пользователем, на которого нажали.

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


На этом завершается реализация страницы профиля, а также данное руководство. Надеюсь, вам будет интересно, и вы обнаружите, что работа с Meteor действительно меняет способ разработки приложений JavaScript. Если у вас есть какие-либо вопросы, не стесняйтесь задавать их в комментариях ниже!