ASP.NET MVC: Делаем голосование на сайте при помощи Knockout
Сайтостроение | создано: 15.01.2013 | опубликовано: 15.01.2013 | обновлено: 13.01.2024 | просмотров: 12464
В предыдущей статье экспериментировали с формой обратной связи. Магия Knockout позволила максимально быстро и просто реализовать функционал формы обратной связи. На этот раз попробуем что-нибудь поинтереснее, например, "голосование".
В качестве вступления
Я буду строить решение на базе проекта, который был опубликован в предыдущей статье “ASP.NET MVC: И снова про форму обратной связи или куда еще втыкнуть Knockout”. Хочу чтобы статья получилась короткая, но максимально информативная.
Задача на эту статью – “реализовать голосование на сайте, с отображением результатов”.

Рис.1 “Опрос на сайте (пользователь еще не голосовал)”
И второй вариант отображения формы, это когда пользователь уже проголосовал.

Рис.2 “Опрос на сайте (пользователь уже голосовал)”
Звучит и выглядит просто, и поэтому я стороной обойду загрузку данных с сервера, запись данных на сервер, чтение настроек и проверку cookies, чтобы по возможности максимально рассказать о Knockout. Итак, если учесть, что все приготовления выполнены, то можно сразу приступить к программированию.
Входные данные
В папке Scripts/App создаю новый файл site.vm.polls.js. Теперь первым делом определю формат данных для объекта (Poll) (листинг 1):
var pollData = {
"question": "За двумя зайцами погонишься...",
"answers": [
{ votes: 11, name: "ни одного не поймаешь." },
{ votes: 5, name: "больше двух не поймаешь." },
{ votes: 15, name: "ни одного волка не поймаешь." },
{ votes: 9, name: "порвешься на две части." },
{ votes: 39, name: "зачем бегать, лучше на авто!" }
],
"selectedAnswer": null};
Это, так сказать, входные данные. В моем случае, они “жестко” прописаны в коде. Вы же можете сделать получение данных для опроса с сервера, через Web API, или с сервиса WCF. Немного комментариев относительно представленного кода.
- Строка 2: Сам вопрос.
- Строка 3-8: Это предложенные варианты ответа на поставленный вопрос.
- Строка 10: Ответ, который возможно уже дал пользователь, посещая сайт. Вы можете хранить его в базе данных или даже в cookies. Сейчас он у меня null, поэтому форма должна отобразить список ответов и вопрос. А если поле было заполнено (выбранный ответ), то форма должна отобразить результаты голосования.
Класс Answer (Ответ)
В предыдущем листинге (листинг 1) в строка 3 эти самые ответы на вопрос. “Завернем” класс в JavaScript (листинг 2):
site.vm.Answer = function (value, total) {
var isSelected = ko.observable(false),
votes = ko.observable(total),
name = ko.observable(value);
return {
isSelected: isSelected,
name: name,
votes: votes
};
},
Всё просто, единственное, что хотелось быть отметить, что свойство isSelected будет указыват на то выбран ли ответ в списке вариантов или нет.
Класс Poll (Опрос)
Этот класс и будет представляет “входные данные” (см. листинг 1). Самый большой класс в моем решении (листинг 3).
site.vm.Poll = function (data) {
var selectedItem = ko.observable(new site.vm.Answer()),
question = ko.observable(data.question),
answers = ko.observableArray([]),
answer = ko.observable(),
maxVotes = ko.observable(),
init = function () {
var max = 0;
var i;
for (i in data.answers) {
var cur = data.answers[i].votes;
if (cur && cur>max) {
max = cur;
}
}
max = Math.round(max * 1.1);
maxVotes(max);
for (i in data.answers) {
answers.push(new site.vm.Answer(data.answers[i].name, data.answers[i].votes));
}
if (data.selectedAnswer) {
answer(data.selectedAnswer);
}
},
setSelected = function (item) {
if (!item.isSelected()) {
selectedItem(item.name());
ko.utils.arrayForEach(answers(), function (i) {
if (i.isSelected && i.isSelected()) { i.isSelected(false); }
});
item.isSelected(true);
}
};
init();
return {
max: maxVotes,
selectedItem: selectedItem,
question: question,
answers: answers,
answer: answer,
setSelected: setSelected
};
};
И опять немного комментариев:
- Строка 2: Представляет выбранный пользователем ответ.
- Строка 6: Вообще-то, это свойство (maxVotes) я ввел только для того, чтобы правильно работал jQuery UI Progressbar, при помощи которого я планирую отображать результаты.
- Строка 7: Инициализация класса на основании входных данных. Запуск инициализации происходит в строке 35.
ViewModel для формы
На форме может быть много опросов, в моем варианте он один. Создаем ViewModel для формы Index.cshtml. Вот так выглядит pollViewModel (листинг 4):
site.vm.pollViewModel = function () {
var message = ko.observable("Выберите ваш вариант ответа на вопрос."),
poll = site.vm.Poll(pollData),
save = function () {
poll.answer(poll.selectedItem());
};
return {
save: save,
poll: poll,
message: message
};
}();
Строка 2: Сообщение для пользователя. Мне не пригодилось, но можно использовать для вывода ошибок при работе сервиса.
Строка 3: Создаем экземпляр класса голосования (см. листинг 3).
Строка 4: Функция сохранения результатов голосования. В моем решении это просто обновления свойства answer. Но вы можете отправить результат на сервер или сохранить в cookies.
Магия Knockout – applyBindings
Голосование (Poll) я планирую показать на главной странице (то есть в котроллере Home, метод – Index). Я открыл представление (index.cshtml) и полностью его отчистил, и добавил туда такую разметку:
<div data-bind="ifnot: poll.answer">
<h2 data-bind="text: poll.question">h2>
<h4 data-bind="text: message">h4>
<div data-bind="foreach: poll.answers">
<div data-bind="click: $parent.poll.setSelected" class="big">
<input type="radio" value="true"
data-bind="checked: isSelected().toString()" class="poll" />
<span data-bind="text: name, css: {'selected': isSelected}">span>
div>
div>
<p>
<button data-bind="click: save">голосоватьbutton>
p>
div>
<div data-bind="if: poll.answer">
<h2 data-bind="text: poll.question() ' ' poll.answer()">h2>
<div data-bind="foreach: poll.answers">
<span data-bind="text: name" >span>
<div data-bind="progressbar: {value: votes(), max: $parent.poll.max()}">div>
<br/>
div>
div>
Всё на форме просто и, надеюсь, понятно. Единственное, что хотелось бы отметить, для отображения результатов в шаблоне (строка 18) используется bindingHandler, который я назвал “progressbar”. Когда вы установите nuget-пакет JsJite, у вас в папке scripts/app появится файл site.bindingHandlers.js, в котором уже много полезных “штучек”.
Заключение
В качестве заключения хочу предложить демо-проект и поэкспериментировать.