QuickForm позволяет создавать калькуляторы любой сложности, в том числе с динамическими полями и динамическими группами полей. Для этого в компонент включено четыре типа калькуляторов, которые считают по формуле, без формулы, а также из логики, записанной в php файле. Калькуляторы рассчитаны на средний уровень подготовки вебмастера, но нужно понимать, что вам понадобится знание математики. В этой статье я постараюсь описать каждый из четырех типов и приведу простые примеры работы. Чтобы вернуться на страницу QuickForm, перейдите по ссылке: QuickForm.

1. Тип калькулятора default.

Давайте сделаем какую-нибудь простую форму, например, расчёта пластиковых окон. Пусть она будет выглядеть так:

0руб.

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

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

Основным отличием этого типа калькулятора является отсутствие формулы. Это позволяет делать расчет формы без привязки к id её полей, а значит, позволяет делать динамические калькуляторы. Математическая логика пишется прямо в настройках поля (параметр math), и если это поле появилось в форме, эта математика просто добавляется к расчету.

Например, если нам нужно вывести в качестве результата ширину окна в метрах, то math поля ширины будет таким: v/1000. Если нужно вывести площадь окна, тогда math поля высоты будет: *v/1000. В итоге получится v/1000*v/1000. Дальше мы добавим math к полю стеклопакет, умножив площадь на стоимость (пусть стоимость 1 метра квадратного стекла будет 320 руб.). Для однокамерный стеклопакет: *2*320, для двухкамерный: *3*320. В итоге мы получили наш калькулятор.

Помимо этого, мы добавляем math к чекбоксам: +80 для откосов и +200 для подоконника. Просто заходим в настройки поля и пишем в math: +80.

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

Недостатки способа.
1. Тип калькулятора default выводит только один результат.
2. Сложно реализовать логику, требующую повторного использования значения какого-либо поля в дальнейшем расчёте.

2. Тип калькулятора multiple.

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

Структура формулы: 3.4={2.2}*{1.1}+{2.3}*{1.1}, где 3.4 - это id поля calculatorSum. В фигурных скобках заключены id рассчитываемых полей. Математическая логика может быть записана как в формуле, так и в настройках math самих полей. Например, если math поля {2.2} содержит следующую запись: (45+34)*7, тогда формула будет рассчитываться так: 3.4=(45+34)*7*{1.1}+{2.3}*{1.1}.

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

0km
0hours
0

Сама форма этого калькулятора состоит из главной и трех зависимых групп. В главной группе находятся радио кнопки выбора транспортного средства, скрипт google maps, hidden поле, которое принимает расстояние по маршруту и три поля calculatorSum. В зависимых группах находится поле "Вес" и вспомогательное hidden поле, нужное для передачи скорости транспортного средства (для расчета времени доставки).

Формула калькулятора следующая:

199.5 = {199.3};
hours = {199.5} /({200.1}{201.1}{202.1});
199.6 = {hours}<1 ? 1 : {hours};
weight = {200.0}{201.0}{202.0} + 0;
199.7 = {199.5}*0.7 + {199.5}*{weight}/10;

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

1. Первая строка формулы: 199.5 = {199.3}; - это расстояние доставки. Запись означает, что QuickForm должен взять math из поля с id 199.3 и вывести результат в поле calculatorSum с id 199.5. Поле 199.3 представляет из себя вспомогательное hidden поле, в которое скрипт google maps передает расстояние. Скрипт карты добавлен только для наглядности, а это поле может быть обыкновенным текстовым полем, в которое пользователь сам напишет какие-то цифры. Мне просто так захотелось и google maps тут не обязательно нужен. В math этого поля находится "v", что означает, текущее value поля. Таким образом, в "Расстояние доставки" пишется просто напросто значение этого поля.

2. Вторая строка формулы рассчитывает время доставки и записывает его в переменную hours. По сути, мы просто делим расстояние доставки на скорость транспортного средства. Скорость передается вспомогательным hidden полем через зависимую группу. Так как у нас всегда выбрано только одно транспортное средство, выражение ({200.1}{201.1}{202.1}) будет содержать только одно значение. Например, если мы выбрали велокурьера, у нас есть поле {200.1}, в math которого указано 15 (пусть его скорость будет 15 км/ч). Полей {201.1}{202.1} в форме нет, значит вместо них ничего не подставляется: hours = {199.3} / ({200.1});

Можно на этом остановиться и вывести значение в calculatorSum. То есть, написать: 199.6 = {199.3} /({200.1}{201.1}{202.1}); Но мне нужно, чтобы время доставки никогда не становилось меньше одного часа. Поэтому, в calculatorSum я пишу через условие: 199.6 = {hours}<1 ? 1 : {hours};

3. Стоимость доставки посчитаем по выражению: Стоимость = расстояние * 0.7 + расстояние * вес / 10. Для удобства я вывел отдельно переменную weight = {200.0}{201.0}{202.0} + 0; В принципе, поле, где пользователь указывает вес доставляемого товара, можно было указать не в зависимой, а в основной группе. Тогда оно было бы одно и было бы намного проще. Но я хотел, чтобы максимальный вес доставки соответствовал виду транспортного средства. Поэтому полей с весом три. Из них всегда активно только одно. Ноль в конце добавлен для того, чтобы избежать ошибки на стадии загрузки страницы. Ведь зависимое поле загружается в форму с некоторой задержкой, значит будет момент времени, когда ни одного из трех полей в форме не будет. Тогда бы переменная weight приняла пустое (не нулевое) значение, а математическое выражение окажется неверным: 199.7 = {199.5}*0.7 + {199.5}*/10;

Недостатки способа.
1. Привязка формулы к id полей. Это не всегда удобно, например, в динамическом калькуляторе. Если одно и то же поле динамически появляется в форме несколько раз, оно будет иметь один id, но может иметь разные значения. Конечно, такие калькуляторы - большая редкость, тем не менее это нужно учитывать.

В завершение, приведу скрипт google maps, который я задействовал в этой форме. Возможно, кому-то пригодится. Скрипт можно сделать на yandex map или на google map. Яндекс карты лучше, умеют строить разные маршруты, исходя из грузоподъемности транспорта. Но они стали платными. Поэтому тут я сделал на google.


var script = document.createElement('script');
script.src = 'https://maps.googleapis.com/maps/api/js?key=api_key&callback=initMap';
script.async = true;
document.head.appendChild(script);

window.initMap = function() {
    // создадим карту с городом Амстердам
    var map = new google.maps.Map(document.getElementById("map"), {
        zoom: 5,
        center: {
            lat: 52.3745403,
            lng: 4.89797551
        },
        scrollwheel: false
    });

    // подключим службу маршрутов
    var directionsService = new google.maps.DirectionsService();
    var directionsRenderer = new google.maps.DirectionsRenderer({
        map,
        suppressMarkers: true,
    });

    // при изменении маршрута пересчитаем расстояние
    directionsRenderer.addListener("directions_changed", () => {
        const directions = directionsRenderer.getDirections();
        if (directions) {
            computeTotalDistance(directions);
        }
    });

    // Зададим начальные точки маршрута
    var origin = new google.maps.LatLng(52.308226730765526, 4.76868436142579);
    var destination = new google.maps.LatLng(52.372439457020796, 4.865501378027353);

    // Создадим неподвижный маркер (склад)
    var marker1 = new google.maps.Marker({
        position: origin,
        draggable: false,
        map: map,
        icon: {
            url: '/images/calc/airport.png',
            scaledSize: new google.maps.Size(80, 80),
        }
    });

    // Создадим перетаскиваемый маркер (машина)
    var marker2 = new google.maps.Marker({
        position: destination,
        draggable: true,
        map: map,
        icon: {
            url: '/images/calc/deliv.png',
            scaledSize: new google.maps.Size(50, 50),
        }
    });


    // Будем перерисовывать маршрут после каждого перемещения перетаскиваемого маркера
    marker2.addListener('dragend', handleEvent);

    function handleEvent(event) {
        destination = new google.maps.LatLng(event.latLng.lat(), event.latLng.lng());
        displayRoute();
    }

    // Отрисуем стартовый маршрут (после загрузки страницы)
    displayRoute();

    // Функция отрисовки маршрута
    function displayRoute() {
        directionsService.route({
                origin: origin,
                destination: destination,
                travelMode: google.maps.TravelMode.DRIVING,
            })
            .then((result) => {
                directionsRenderer.setDirections(result);
            })
            .catch((e) => {
                alert("Could not display directions due to: " + e);
            });
    }

    // Функция пересчета расстояния
    function computeTotalDistance(result) {
        let total = 0;
        const myroute = result.routes[0];

        if (!myroute) {
            return;
        }

        for (let i = 0; i < myroute.legs.length; i++) {
            total += myroute.legs[i].distance.value;
        }

        // Запишем расстояние в поле формы с id="dist" (наше вспомогательное hidden поле).
        var dist = document.getElementById("dist");
        dist.value = total / 1000;

        // Попросим QuickForm пересчитать форму
        QuickForm.sumForm(dist.form);
    }

};

Скрипт можно вывести inline при помощи поля QuickForm customHtml или программно, при помощи customPhp. Вместо api_key нужно подставить свое значение.

3. Тип калькулятора simple.

Создадим еще один простейший калькулятор. Пусть это будет кредитный калькулятор. Для расчёта воспользуемся формулой из Википедии: AP = CA*((IR+100)/1200)/(1-(1+(IR+100)/1200)^(-NM)) для аннуитетного платежа. Здесь у нас NM – количество месяцев, IR – процентная ставка, CA – сумма кредита.

10000
100000
1
60
0

Этот тип калькулятора отличается тем, что в math полей вводится не математическое выражение, а название переменной. Например, в math поля количества месяцев записано NM. Формула этого калькулятора выглядит следующим образом:

204.3 = {CA}*(({IR}+100)/1200)/(1-(1+({IR}+100)/1200)**(-{NM}));

Недостатки способа.
1. Калькулятор считает только поля с value, заполняемым пользователем: типа number, text, range.

4. Тип калькулятора custom.

Это наиболее функциональный и удобный тип калькулятора. Мой выбор в большинстве случаев, кроме самых простых. Для работы с "custom" нужно знать php.

0
0

В этом варианте калькулятора в поле формулы пишется код. Например, код текущего калькулятора такой:


$customQFcalculator = function($project, $data)
{
   require_once(QF3_PLUGIN_DIR . 'classes/example.php');
   $exampleCalculator = new QuickForm\exampleCalculator($project, $data);

   $calculatorSum['206.1'] = $exampleCalculator->getSin();
   $calculatorSum['206.2'] = $exampleCalculator->getCos();
   return $calculatorSum;
};

Этот код представляет из себя функцию customQFcalculator($project, $data), которая принимает две переменные: $project - все параметры текущего проекта и $data - все параметры отправленных формой полей. Возвращает массив, ключами которого являются fieldid необходимых calculatorSum, а значениями вставляемая в них сумма.

Чтобы было удобно, я создал файл example.php в удобном мне месте и написал всю логику расчёта там. Содержимое этого файла в данном случае следующее:


namespace QuickForm;

\defined('QF3_VERSION') or die;

class exampleCalculator extends qfCalculator
{
    protected $angle = 0;

    public function __construct($project, $data)
    {
        $this->data = $data;
        $this->angle = $this->getAngle();
    }

    protected function getAngle()
    {
        foreach($this->data as $field) {
            if(isset($field->math) && $field->math == 'angle') {
                return (float) $field->value;
            }
        }
    }

    public function getSin()
    {
        return round(sin(deg2rad($this->angle)),8);
    }

    public function getCos()
    {
        return round(cos(deg2rad($this->angle)),8);
    }

}

Чтобы передать значение поля в файл example.php, я использовал параметр поля math. Это удобно, потому что в виде строки можно каждому полю задать сколько угодно разных значений. В моем случае в math поля написано "angle".
Удачи.