Калькулятор взвешенных оценок

Владимир | | HTML, JavaScript, Web разработка.

weight_calc

Приветствую всех!
В этот раз речь пойдет о создании калькулятора для расчета взвешенных оценок. Такие оценки часто используют при проведении различных тестирований и сравнений.

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

Естественно некоторые параметры будут важнее чем другие. Например, цена важнее веса. И вы хотите это учесть.

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

В суммарную оценку входит сумма оценок всех параметров, умноженных на их вес.

При этом удобно, чтобы сумма весов всех параметров равнялась 1 (100%). Например, цена – 50%, размер дисплея – 40%, вес – 10%.

Теперь рассмотрим пример создания такого калькулятора для 3-х параметров.

Нам понадобятся 3 поля для ввода оценок каждого из параметров и 3 слайдера (slider) для задания их весов.

В данном случае слайдеры создадут очень наглядное представление весов параметров. К тому же мы сделаем их связанными между собой и пользователю не придется следить за тем, чтобы сумма их значений была равна 100%.

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

Demo

Кроме того, вы можете скачать архив с исходниками этого примера и запустить его локально.

Source

Сразу хочу обратить ваше внимание на чекбоксы (checkbox) справа от слайдеров. Они служат для того, чтобы заблокировать изменение соответствующего слайдера. Т.к. слайдеров у нас три, то при перемещении одного из них, будут изменяться значения остальных.

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

Структура и принцип работы калькулятора

Наше приложение состоит из одной HTML страницы, трёх JavaScript файлов (из них два – это библиотеки jQuery и jQuery UI) и 2-х файлов с CSS стилями.

Начнём со страницы – index.html.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  3.     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  4. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  5.  
  6. <head>
  7.   <title>Каклькулятор, взвешенные оценки</title>
  8.  
  9.   <meta http-equiv="content-type" content="text/html;charset=utf-8" />
  10.   <meta http-equiv="Content-Style-Type" content="text/css" />
  11.  
  12.   <meta name="description" content="Использование ползунков (slider) для создания калькулятора" />
  13.   <meta name="keywords" content="калькулятор, ползунки, slider, взвешенная оценка" />
  14.   
  15.   <link rel="stylesheet" type="text/css" href="css/ui-lightness/jquery-ui-1.7.2.custom.css" media="all" />
  16.   <link rel="stylesheet" type="text/css" href="styles.css" media="all" />
  17. </head>
  18.  
  19. <body>
  20.  
  21. <div id="parameter_1" class="calc_row">
  22.   <input type="text" name="val_1" id="val_1" class="val_field" value="0" />
  23.   <div id="weight_1" class="slider"></div>
  24.   <div id="sv_1" class="slider_val"></div>
  25.   <input type="checkbox" class="sl_enable" />
  26. </div>
  27.  
  28. <div id="parameter_2" class="calc_row">
  29.   <input type="text" name="val_2" id="val_2" class="val_field" value="0" />
  30.   <div id="weight_2" class="slider"></div>
  31.   <div id="sv_2" class="slider_val"></div>
  32.   <input type="checkbox" class="sl_enable" />
  33. </div>
  34.  
  35. <div id="parameter_3" class="calc_row">
  36.   <input type="text" name="val_3" id="val_3" class="val_field" value="0" />
  37.   <div id="weight_3" class="slider"></div>
  38.   <div id="sv_3" class="slider_val"></div>
  39.   <input type="checkbox" class="sl_enable" />
  40. </div>
  41.  
  42. <div id="results"></div>
  43.  
  44. <script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
  45. <script type="text/javascript" src="js/jquery-ui-1.7.2.custom.min.js"></script>
  46. <script type="text/javascript" src="js/main.js"></script>
  47. </body>
  48. </html>

* This source code was highlighted with Source Code Highlighter.

В заголовке страницы мы подключили CSS файлы (строки 15 и 16). Первый распространяется вместе с jQuery UI и используется для оформления слайдеров, второй – используется для оформления страницы.

Сама страница содержит три практически одинаковых блока. В каждый из них входят:

текстовое поле — задаёт оценку параметра;
слайдер – указывает вес параметра;
текстовая надпись – отображает текущее значение слайдера;
чекбокс (checkbox) – используется для включения/отключения слайдера.

За ними расположен блок, показывающий суммарную оценку (строка 42)

В конце страницы мы подключаем JavaScript файлы (строки 44-46). Сначала библиотека jQuery, затем jQuery UI и файл с нашими скриптами (main.js).

Теперь рассмотрим наш main.js. Это самая сложная и объёмная часть калькулятора. Но большая часть кода касается настройки взаимной работы слайдеров и чекбоксов.

  1. $(function() {
  2.   /* создаем слайдеры */
  3.   $(".slider").slider();
  4.   
  5.   /* устанавливаем начальные значения */
  6.   $(".sl_enable").attr('checked', false);
  7.  
  8.   $(".slider").each(function() {
  9.     $(this).slider('option', 'value', 33.3);
  10.   });
  11.   
  12.   $(".slider_val").html("33.3 %");
  13.   
  14.   $('.val_field').val('0');
  15.   
  16.   calculate();
  17.   
  18.   /* минимальное и максимальное значение активных слайдеров */
  19.   var min = 0;
  20.   var max = 100;
  21.   
  22.   /*
  23.   Устанавливаем обработчики чекбоксов.
  24.   Использовать событие change не получается, т.к. в IE оно возникает
  25.   после того как checkbox теряет фокус (т.е. после переключения
  26.   checkbox'а нужно кликнуть где-нибудь на странице, чтобы возникло
  27.   событие change)
  28.   */
  29.   $(".sl_enable").click(function() {
  30.     var curId = getCurId(this);
  31.     var curSlider = $('#weight_' + curId);
  32.     if (this.checked) {
  33.       curSlider.slider('disable');
  34.       $("#sv_" + curId).addClass('gray');
  35.     }
  36.     else {
  37.       curSlider.slider('enable');
  38.       $("#sv_" + curId).removeClass('gray');
  39.     }
  40.     setMinMax();
  41.   });
  42.   
  43.   /* обработчик событий slide слайдеров (возникает при перемещении слайдера) */
  44.   $(".slider").bind('slide', function(event, ui) {
  45.     //блокируем перемещение слайдера если оно выходит за допустимые пределы
  46.     if ((ui.value > max) || (ui.value < min)) {
  47.       return false;
  48.     }
  49.     updateSliders(this, ui.value);
  50.     updateLabels();
  51.     calculate();
  52.   })
  53.   .bind('slidestop', function() {
  54.     /*
  55.     Этот обработчик вызывается после перемещения слайдера.
  56.     Нужен для того, чтобы обновить значение текущего слайдера
  57.     (событие slide возникает раньше)
  58.     */
  59.     calculate();
  60.     updateLabels();
  61.   });
  62.   
  63.   /* Эта функция возвращает id текущего слайдера */
  64.   function getCurId(elem) {
  65.     var parentId = $(elem).parent().attr('id');
  66.     return parentId.substring(10);
  67.   }
  68.   
  69.   /*
  70.   Эта функция устанавливает минимально и максимально допустимые значения
  71.   слайдеров.
  72.   */
  73.   function setMinMax() {
  74.     var enabledSliders = $(".slider[aria-disabled!=true]");
  75.     var disabledSliders = $(".slider[aria-disabled=true]");
  76.     
  77.     // определяем сумму значений, установленных на отключенных слайдерах
  78.     var total = 0;
  79.     disabledSliders.each(function() {
  80.       total += $(this).slider('option', 'value');
  81.     });
  82.     
  83.     // задаём минимальные и максимальные значения для включенных слайдеров
  84.     max = 100 - total;
  85.     min = 0;
  86.     if (enabledSliders.length == 1) {
  87.       min = max;
  88.     }
  89.   }
  90.   
  91.   /*
  92.   Изменяет положение слайдеров так чтобы сумма их значений всегда была
  93.   равна 100
  94.   */
  95.   function updateSliders(curSlider, curVal) {
  96.     var enabledSliders = $(".slider[aria-disabled!=true]");
  97.     if (enabledSliders.length == 1) {
  98.       return;
  99.     }
  100.     var newVal = (max - curVal) / (enabledSliders.length - 1);
  101.     enabledSliders.each(function(i, slider) {
  102.       if (slider != curSlider) {
  103.         $(slider).slider('option', 'value', newVal);
  104.       }
  105.     });
  106.   }
  107.   
  108.   /* Обновляет надписи с текущими значениями слайдеров */
  109.   function updateLabels() {
  110.     for (var i = 1; i <= 3; i++) {
  111.       var curVal = $("#weight_" + i).slider('option', 'value');
  112.       //округляем до десятых
  113.       curVal = Math.round(curVal*10) / 10;
  114.       $("#sv_" + i).html(curVal + " %");
  115.     }
  116.   }
  117.   
  118.   /* проверка значений, введенных в текстовое поле */
  119.   
  120.   var textFieldVal;
  121.   
  122.   $('.val_field').bind('keydown', function(e) {
  123.     //сохраняем текущее значение
  124.     textFieldVal = $(this).val();
  125.   })
  126.   .bind('keyup', function(e) {
  127.     if (isNaN($(this).val())) {
  128.       $(this).val(textFieldVal);
  129.     }
  130.     calculate();
  131.   });
  132.   
  133.   /* Рассчет взвешенной оценки */
  134.   function calculate() {
  135.     var res = 0;
  136.     
  137.     for (var i = 1; i <= 3; i++) {
  138.       res += $('#val_' + i).val() * $('#weight_' + i).slider('option', 'value') / 100;
  139.     }
  140.     
  141.     //Округляем до сотых
  142.     res = Math.round(res*100) / 100;
  143.     
  144.     $('#results').html('Взвешенная оценка = ' + res);
  145.   }
  146. });

* This source code was highlighted with Source Code Highlighter.

Разберем код, начиная с функции calculate (строки 134-145), которая выполняет расчет суммарной оценки.

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

После этого, показываем результат пользователю.

Возвращаемся в начало файла.

Прежде всего, мы создаём слайдеры (строка 3) и устанавливаем начальные значения (строки 6-14). Все оценки ставим равными 0, а веса параметров – 33,3% (100/3).

После этого, мы устанавливаем обработчики события click для чекбоксов. При включении чекбокса мы должны сделать соответствующий слайдер неактивным (строка 33) и пересчитать максимальные и минимальные значения для остальных слайдеров. Для этого используется функция setMinMax().

Рассмотрим её подробнее (строки 73-89).

Сначала мы находим все активные и отключенные слайдеры (строки 74 и 75).

Затем, рассчитываем сумму значений отключенных слайдеров и отнимаем её от 100. Полученное значение является максимально допустимым для оставшихся слайдеров.

Если у нас остался только один активный слайдер, то очевидно, что он может принять только одно значение (иначе сумма не будет равна 100). Поэтому мы присваиваем min = max.

Переходим к обработке перемещений слайдера (событие slide, строки 44-61). Точнее мы обрабатываем 2 события: slide и slidestop. Дело в том, что при возникновении события slide окончательное значение на слайдере ещё не установлено, поэтому если не обрабатывать slidestop, то в результате получим оценку для предыдущего положения слайдера.

При возникновении события slide мы проверяем не выходит ли слайдер за допустимые пределы и если да – блокируем его (строки 46-48).

После этого, мы обновляем положение остальных слайдеров (с помощь функции updateSliders), обновляем текстовые надписи и пересчитываем результат (строки 49-51).

Теперь рассмотрим функцию updateSliders (строки 95-106). Именно она устанавливает значения слайдеров так, чтобы их сумма всегда была равна 100%.

В её параметрах мы передаём указатель на текущий слайдер (его изменять нельзя) и его значение.

Принцип расчета значений остальных слайдеров такой.

Если у нас есть только один активных слайдер – не делаем ничего, т.к. он перемещаться не может.

Если есть хотя бы один активный слайдер (кроме текущего), то рассчитываем сколько нужно прибавить к значению текущего слайдера чтобы получить максимальное значение и делим результат на количество слайдеров (строка 100).

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

После этого, мы обновляем значения слайдеров (строка 103).

Теперь слайдеры работают так как нужно. Нам остаётся добавить проверку оценок, введённых в текстовые поля (строки 120-131).

Для этого установим обработчики событий keydown и keyup для этих полей. Принцип работы следующий.

При возникновении события keydown изменение содержимого поля ещё не произошло и мы сохраняем предыдущее значение.

А при возникновении keyup поле содержит новое значение. С помощью функции isNaN мы проверяем, является ли новое значение числом и если нет – возвращаем предыдущее.

Тут же мы пересчитываем суммарную оценку.

Заключение

Как видите, ничего сверхсложного. Самое главное при разработке таких приложений – правильно выбирать обработчики событий и следить за тем, какие данные в них доступны. Иначе можно получить очень интересные эффекты 🙂

Описывать подробно CSS стили я не буду. Т.к. изменение оформления слайдеров – это отдельная тема, а стили калькулятора – простые до предела.

Если есть вопросы или замечания – пишите, постараюсь ответить 😉

  • Dez

    Ништяк тема. Мне нравится. Правда чего-то не хватает. Более красочно бы – было бы супер.

    • А что раскрасить? Страницу или сами слайдеры?

  • Dez

    Ништяк тема. Мне нравится. Правда чего-то не хватает. Более красочно бы – было бы супер.

    • А что раскрасить? Страницу или сами слайдеры?

  • отличный калькулятор-то что он выбирает взвешенные оценки из всей массы параметров

  • отличный калькулятор-то что он выбирает взвешенные оценки из всей массы параметров

  • Ivan

    Владимир, прежде всего спасибо тебе за твой блог — читаю с удовольствием! Вопрос мой вот о проверке содержимого полей:

    var textFieldVal;

    $('.val_field').bind('keydown', function(e) {
    //сохраняем текущее значение
    textFieldVal = $(this).val();
    })
    .bind('keyup', function(e) {
    if (isNaN($(this).val())) {
    $(this).val(textFieldVal);
    }
    calculate();
    });

    Казалось бы все просто и понятно, но я обратил внимание на то, что, если клавишу зажать и держать, то при добавлении символа в поле ввода событие keyup не срабатывает, и получается что пользователь в поле например «xxxxxxxx», а потом если захочет удалить это безобразие, нажав, скажем Del или backspace, у него опять же ничего не получиться, потому что значение уже сохранилось.
    Выход из ситуации я нашел в виде использования события input, которое как нельзя лучшие подходит для решения это задачи, но не работает в IE.
    В общем если есть какие-либо идеи, помоги)

    • Попробуйте добавить проверку на NaN в обработчик keydown.
      input, конечно, практически идеальное решение.
      Еще один вариант для IE — проверять значение, введённое в поле по таймеру, получается имитация input, но решение некрасивое.

  • Ivan

    Владимир, прежде всего спасибо тебе за твой блог — читаю с удовольствием! Вопрос мой вот о проверке содержимого полей:

    var textFieldVal;

    $('.val_field').bind('keydown', function(e) {
    //сохраняем текущее значение
    textFieldVal = $(this).val();
    })
    .bind('keyup', function(e) {
    if (isNaN($(this).val())) {
    $(this).val(textFieldVal);
    }
    calculate();
    });

    Казалось бы все просто и понятно, но я обратил внимание на то, что, если клавишу зажать и держать, то при добавлении символа в поле ввода событие keyup не срабатывает, и получается что пользователь в поле например «xxxxxxxx», а потом если захочет удалить это безобразие, нажав, скажем Del или backspace, у него опять же ничего не получиться, потому что значение уже сохранилось.
    Выход из ситуации я нашел в виде использования события input, которое как нельзя лучшие подходит для решения это задачи, но не работает в IE.
    В общем если есть какие-либо идеи, помоги)

    • Попробуйте добавить проверку на NaN в обработчик keydown.
      input, конечно, практически идеальное решение.
      Еще один вариант для IE — проверять значение, введённое в поле по таймеру, получается имитация input, но решение некрасивое.