ASP.NET MVC: Делаем голосование на сайте при помощи Knockout

ASP.NET MVC: Делаем голосование на сайте при помощи Knockout

Рис.1 “Опрос на сайте (пользователь еще не голосовал)”

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

106-knockout-poll-1

Рис.2 “Опрос на сайте (пользователь уже голосовал)”

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

Входные данные

В папке Scripts/App создаю новый файл site.vm.polls.js. Теперь первым делом определю формат данных для объекта (Poll) (листинг 1):

   1:      var pollData = {
   2:  "question": "За двумя зайцами погонишься...",
   3:  "answers": [
   4:              { votes: 11, name: "ни одного не поймаешь." },
   5:              { votes: 5, name: "больше двух не поймаешь." },
   6:              { votes: 15, name: "ни одного волка не поймаешь." },
   7:              { votes: 9, name: "порвешься на две части." },
   8:              { votes: 39, name: "зачем бегать, лучше на авто!" }
   9:          ],
  10:  "selectedAnswer": null};

Это, так сказать, входные данные. В моем случае, они “жестко” прописаны в коде. Вы же можете сделать получение данных для опроса с сервера, через Web API, или с сервиса WCF. Немного комментариев относительно представленного кода.

  • Строка 2: Сам вопрос.
  • Строка 3-8: Это предложенные варианты ответа на поставленный вопрос.
  • Строка 10: Ответ, который возможно уже дал пользователь, посещая сайт. Вы можете хранить его в базе данных или даже в cookies. Сейчас он у меня null, поэтому форма должна отобразить список ответов и вопрос. А если поле было заполнено (выбранный ответ), то форма должна отобразить результаты голосования.

Класс Answer (Ответ)

В предыдущем листинге (листинг 1) в строка 3 эти самые ответы на вопрос. “Завернем” класс в JavaScript (листинг 2):

   1:   site.vm.Answer = function (value, total) {
   2:          var isSelected = ko.observable(false),
   3:              votes = ko.observable(total),
   4:              name = ko.observable(value);
   5:  return {
   6:              isSelected: isSelected,
   7:              name: name,
   8:              votes: votes
   9:          };
  10:      },

Всё просто, единственное, что хотелось быть отметить, что свойство isSelected будет указыват на то выбран ли ответ в списке вариантов или нет.

Класс Poll (Опрос)

Этот класс и будет представляет “входные данные” (см. листинг 1). Самый большой класс в моем решении (листинг 3).

   1:  site.vm.Poll = function (data) {
   2:          var selectedItem = ko.observable(new site.vm.Answer()),
   3:              question = ko.observable(data.question),
   4:              answers = ko.observableArray([]),
   5:              answer = ko.observable(),
   6:              maxVotes = ko.observable(),
   7:              init = function () {
   8:                  var max = 0;
   9:                  var i;
  10:  for (i in data.answers) {
  11:                      var cur = data.answers[i].votes;
  12:  if (cur && cur>max) {
  13:                          max = cur;
  14:                      }
  15:                  }
  16:                  max = Math.round(max * 1.1);
  17:                  maxVotes(max);
  18:  for (i in data.answers) {
  19:                      answers.push(new site.vm.Answer(data.answers[i].name, 
                                                  data.answers[i].votes));
  20:                  }
  21:  if (data.selectedAnswer) {
  22:                      answer(data.selectedAnswer);
  23:                  }
  24:              },
  25:              setSelected = function (item) {
  26:  if (!item.isSelected()) {
  27:                      selectedItem(item.name());
  28:                      ko.utils.arrayForEach(answers(), function (i) {
  29:  if (i.isSelected && i.isSelected()) { i.isSelected(false); }
  30:                      });
  31:                      item.isSelected(true);
  32:                  }
  33:              };
  34:  
  35:          init();
  36:  
  37:  return {
  38:              max: maxVotes,
  39:              selectedItem: selectedItem,
  40:              question: question,
  41:              answers: answers,
  42:              answer: answer,
  43:              setSelected: setSelected
  44:          };
  45:      };

И опять немного комментариев:

  • Строка 2: Представляет выбранный пользователем ответ.
  • Строка 6: Вообще-то, это свойство (maxVotes) я ввел только для того, чтобы правильно работал jQuery UI Progressbar, при помощи которого я планирую отображать результаты.
  • Строка 7: Инициализация класса на основании входных данных. Запуск инициализации происходит в строке 35.

ViewModel для формы

На форме может быть много опросов, в моем варианте он один. Создаем ViewModel для формы Index.cshtml. Вот так выглядит pollViewModel (листинг 4):

   1:  site.vm.pollViewModel = function () {
   2:          var message = ko.observable("Выберите ваш вариант ответа на вопрос."),
   3:              poll = site.vm.Poll(pollData),
   4:              save = function () {
   5:                  poll.answer(poll.selectedItem());
   6:              };
   7:  
   8:  return {
   9:              save: save,
  10:              poll: poll,
  11:              message: message
  12:          };
  13:      }();

Строка 2: Сообщение для пользователя. Мне не пригодилось, но можно использовать для вывода ошибок при работе сервиса.

Строка 3: Создаем экземпляр класса голосования (см. листинг 3).

Строка 4: Функция сохранения результатов голосования. В моем решении это просто обновления свойства answer. Но вы можете отправить результат на сервер или сохранить в cookies.

Магия Knockout – applyBindings

Голосование (Poll) я планирую показать на главной странице (то есть в котроллере Home, метод – Index).  Я открыл представление (index.cshtml) и полностью его отчистил, и добавил туда такую разметку:

   1:  <divdata-bind="ifnot: poll.answer">
   2:  <h2data-bind="text: poll.question">h2>
   3:  <h4data-bind="text: message">h4>
   4:  <divdata-bind="foreach: poll.answers">
   5:  <divdata-bind="click: $parent.poll.setSelected"class="big">
   6:  <inputtype="radio"value="true" 
                             data-bind="checked: isSelected().toString()"class="poll"/>
   7:  <spandata-bind="text: name, css: {'selected': isSelected}">span>
   8:  div>
   9:  div>
  10:  <p>
  11:  <buttondata-bind="click: save">голосоватьbutton>
  12:  p>
  13:  div>
  14:  <divdata-bind="if: poll.answer">
  15:  <h2data-bind="text: poll.question() ' ' poll.answer()">h2>
  16:  <divdata-bind="foreach: poll.answers">
  17:  <spandata-bind="text: name">span>
  18:  <divdata-bind="progressbar: {value: votes(), max: $parent.poll.max()}">div>
  19:  <br/>
  20:  div>
  21:  div>

Всё на форме просто и, надеюсь, понятно. Единственное, что хотелось бы отметить, для отображения результатов в шаблоне (строка 18) используется bindingHandler, который я назвал “progressbar”. Когда вы установите nuget-пакет JsJite, у вас в папке scripts/app появится файл site.bindingHandlers.js,  в котором уже много полезных “штучек”.

Заключение

В качестве заключения хочу предложить создать демо-проект и поэкспериментировать.

Подробнее: http://feedproxy.google.com/~r/blogmusor/~3/ReoYRvxCyJU/106

Читать комменты и комментировать

Добавить комментарий / отзыв



Защитный код
Обновить

Дайджест новых статей по интернет-маркетингу на ваш email