Проект 3. Калькулятор на Java Script



Инструкция от loftblog: https://youtu.be/VHGaBc9OcXU
Описание: https://loftblog.ru/material/11-pervaya-programma-kalkulyator-na-javascript/
Результат: https://projectcalculator1.github.io/ 

Определяем, какие функции нам понадобятся:

numberPress - отслеживать нажатие на клавиши с цифрами и получать число
operationPress - получать значения операторов
decimal - ввод десятичной точки
clear - очистка - полная (клавиша С) и последнего действия (клавиша СЕ)

Для поиска элементов используем методы:

document.querySelectorAll(".class") - находит все элементы определённого класса, например, все кнопки с числами или все кнопки с операторами;
document.getElementById("id") - находит уникальные элементы, имеющие свой id, который, как известно, на странице может быть только один. Например, знак "равно", десятичную точку, клавишу "С" и т.д

e.srcElement.id - свойство для распознавания кнопки по её id
e.target.textContent - свойство для получения текста кнопки

Для обхода всех кнопок используем цикл.

for(var i = 0; i < numbers.length; i++) {
    var number = numbers[i];
    number.addEventListener("click", function(e){
        console.log("Клик по кнопке с номером " + e.target.textContent)
    })
};

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

Отслеживание кликов по кнопкам осуществляет функция

 number.addEventListener("click", function(e){
        ...
    })

Дальше. Переписываем безымянную функцию function(e) в коде выше на определённую нами ранее функцию numberPress.

При этом, если написать функцию со скобками, то это будет сразу вызов функции и она сработает при загрузке страницы:

number.addEventListener("click", numberPress())

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

number.addEventListener("click", numberPress)

А вызов функции будет ниже:

function numberPress() {
    ...
}

Теперь интересный момент - как отличить по какой кнопке был сделан клик

// вообще непонятно почему для этого не использовать id кнопок, прописав для каждого своё событие
// и зачем понадобилось объединять две кнопки с разным функционалом в одну функцию, чтобы потом пытаться их различить
// ок. будем мужественно преодолевать нами же созданные трудности

Напишем вот такой код

ce.addEventListener("click", function(e){
    console.log(e)
});

Здесь в console.log вызывается событие е, которое возникает при каждом клике
При клике по кнопке в консоли мы видим объект MouseEvent, в котором много разных свойств, в том числе и srcElement, содержащее информацию об элементе, в том числе и его id

Следующая функция выведет в консоль id элемента

ce.addEventListener("click", function(e){
    console.log(e.srcElement.id)
});

Переписываем код следующим образом:

for(var i = 0; i < clearBtns.length; i++) {
    var clearBtn = clearBtns[i];
        clearBtn.addEventListener("click", function(e){
        clear(e.srcElement.id);
        });
    };

function clear(id) {
        console.log("Клик по " + id)
    };

Здесь мы вначале вызываем анонимную функцию function(e){...}, так как нам нужен её аргумент е - событие, потом внутри прописываем нашу функцию clear(e.srcElement.id); аргументом которой является значение id элемента, затем запускаем эту функцию с аргументом id (название произвольное), но так как выше мы уже определили, что аргумент функции e.srcElement.id, получается, что id = e.srcElement.id. Соответственно, вызов console.log("Клик по " + id) возвращает нам "Клик по с" или "Клик по се", смотря на какую кнопку мы кликаем.

Дальше делаем то же самое для цифр и операторов, но так как нам нужно их содержание, вместо e.srcElement.id используем свойство e.target.textContent

Получится примерно так:

for(var i = 0; i < numbers.length; i++) {
    var number = numbers[i];
    number.addEventListener("click", function(e){
        numberPress(e.target.textContent);
    });
};

function numberPress(number) {
        console.log("Клик по кнопке с номером " + number)
    }

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

Теперь нам нужно написать функции для ввода чисел.

Общие структуры функций:

Функция, которая отражает введенное число на экране калькулятора:

function numberPress (number) {
if (условие) { //ввод нового значение и меняем значение флага}
else {
if (условие) { //стирает 0 с дисплея}
else { //добавляет цифру к числу на экране}
     }  
};

Функция обработки операций калькулятора и вывода результата:

function operation (op) {
//создаем локальную переменную памяти
if (условия) { //сохраняем значение на экране в переменную памяти}
else { //говорим переменной памяти о том, что мы вводим новое число}
if (условие с +) { //выполнение операции}
else if (условие с -) { //выполнение операции}
else if (условие с *) { //выполнение операции}
else if (условие с /) { //выполнение операции}
else { //действия с глобальной и локальной памятью};  
//вывод результата
//сохранение текущей операции
};


!Функция преобразования строки к числу с плавающей точкой parseFloat().
// имхо, здесь вполне можно было обойтись плюсом, преобразующим строку к числу. Зачем усложнять себе жизнь и делать сложно, если можно сделать намного проще

Функция добавления десятичной точки:

function decimal (argument) {
//создаем локальную переменную
if (условие) { //добавляем 0.}
else { 
if (условие) { //если не существует символа ‘.’ в строке – добавляй точку };
};  
};

Функция обработки кнопок очищения C и СЕ:

function clear (argument) {
if (условие с се) { //удаление введённого числа с дисплея}
else if (условие с с) { //очищения дисплея };
};

Функции в деталях:

Функция, которая отражает введенное число на экране калькулятора:

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

Глобальные переменные:

MemoryCurrentNumber = 0, // вводимое в данный момент число
MemoryNewNumber = false, // флаг, переключатель, показывающий вводим мы новое число, или ещё прежнее. Новое число будет после того, как нажали на оператор, то есть, функция operation должна этот флаг менять
MemoryPendingOperation = ""; // ожидаемая операция, на которую нажали перед вводом нового числа

Дальше. В функцию numberPress добавляем строку display.value = number;

function numberPress(number) {
        display.value = number;
        console.log("Клик по кнопке с номером " + number)
    }

display.value - значение элемента display,
number - значение кнопки калькулятора, на которую мы нажали.

Теперь при кликам по кнопкам появляются цифры. Магия начинает работать )
Но есть проблема: каждая новая цифра стирает предыдущую. Нам нужно предусмотреть возможность ввода чисел состоящих из нескольких цифр.
Это не сложно:

function numberPress(number) {
        display.value += number;
        console.log("Клик по кнопке с номером " + number)
    }

Но теперь новая цифра прибавляется и к нулю, который был введён раньше.
Добавляем условие:

function numberPress(number) {
        if(display.value === "0") {
            display.value = number;
        } else {
            display.value += number;
        }
     }

Теперь мы можем вводить числа, которые будут отображаться на дисплее калькулятора.

Перейдём к функции operation

Функция обработки операций калькулятора и вывода результата:

Как только мы нажимаем на любой оператор, нам нужно чтобы программа запомнила уже введённое на дисплей число

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

localOperationMemory = display.value;

function operationPress(op) {
        localOperationMemory = display.value;
        
        if(MemoryNewNumber && MemoryPendingOperation !== "=") {
            display.value = MemoryCurrentNumber;
        } else {
            MemoryNewNumber = true;
            if (MemoryPendingOperation === "+") {
                MemoryCurrentNumber += +localOperationMemory;  
            } else if (MemoryPendingOperation === "-") {
                MemoryCurrentNumber -= +localOperationMemory;  
            } else if (MemoryPendingOperation === "*") {
                MemoryCurrentNumber *= +localOperationMemory;  
            } else if (MemoryPendingOperation === "/") {
                MemoryCurrentNumber /= +localOperationMemory;  
            } else {
                MemoryCurrentNumber = +localOperationMemory;  
            }
            display.value = MemoryCurrentNumber;
            MemoryPendingOperation = op;
        };
        
      }

Кроме того, мы усовершенствовали функцию numberPress

function numberPress(number) {
        if(MemoryNewNumber) {
            display.value = number;
            MemoryNewNumber = false;
        } else {
            if(display.value === "0") {
                display.value = number;
            } else {
                display.value += number;
            };
        };
    };

Теперь наш калькулятор умеет считать.
Ура!

Осталось научить его понимать десятичную точку и научить стирать с экрана и памяти последнее действие или всё.