Введение

В этой статье рассказывается, как создать научный калькулятор с использованием VueJS.

Вот визуальное отображение конечного результата:

Исходный код этой статьи доступен здесь, на Github.

Давайте начнем.

Настроить VueJS

В рамках этого руководства мы будем использовать Vue CLI для установки и настройки, альтернативой будет ссылка на Vue из CDN.

https://unpkg.com/vue

Мы можем установить Vue CLI с помощью этой команды:

$ npm install --global vue-cli

Затем давайте создадим новый проект:

$ vue init webpack-simple vueCalulator

После ответа на несколько запросов терминала мы бы все настроили.

Давайте перейдем в рабочий каталог, установим зависимости и запустим наше приложение:

$ cd vueCalulator 
$ npm install 
$ npm run dev

Определение компонентов Vue

Поскольку мы создаем калькулятор с двумя режимами [базовый и расширенный], давайте определим два компонента для представления каждого из них.

Во-первых, давайте создадим папку components в каталоге src [в этом нет необходимости, но мне нравится поддерживать чистый рабочий каталог].

Затем мы создаем два новых файла Vue в каталоге components. Мы будем называть эти файлы:

Логика, лежащая в основе различных режимов этого калькулятора:

Корневой компонент [App.vue] будет содержать поле ввода, в то время как компоненты Basic и Advanced будут переключаться в пространстве под полем ввода.

А теперь приступим к делу!

Создайте базовый компонент

Давайте напишем шаблон, данные и методы, которые будут находиться в Basic компоненте.

Шаблон Basic будет содержать кнопки калькулятора, которые прослушивают события нажатия на любую из кнопок.

<template>
<div @click="buttonClick">
<button class="btn btn-primary">7</button>
<button class="btn btn-primary">8</button>
<button class="btn btn-primary">9</button>
<button class="btn btn-primary">/</button>
<button class="btn btn-primary">&larr;</button>
<button class="btn btn-primary">C</button>
<button class="btn btn-primary">4</button>
<button class="btn btn-primary">5</button>
<button class="btn btn-primary">6</button>
<button class="btn btn-primary">*</button>
<button class="btn btn-primary"> ( </button>
<button class="btn btn-primary"> ) </button>
<button class="btn btn-primary">1</button>
<button class="btn btn-primary">2</button>
<button class="btn btn-primary">3</button>
<button class="btn btn-primary">-</button>
<button class="btn btn-primary">x²</button>
<button class="btn btn-primary">±</button>
<button class="btn btn-primary">0</button>
<button class="btn btn-primary">.</button>
<button class="btn btn-primary">%</button>
<button class="btn btn-primary">+</button>
<button class="equals btn btn-primary">=</button>
</div>

</template>

Мы видим, что кнопки заключены в корневой тег div, который вызывает метод buttonClick при каждом нажатии кнопки.

Затем давайте экспортируем и определим методы в нашем Basic компоненте, мы просто сделаем это, написав тег скрипта:

<script>
export default {
data () {
return {}
},
methods: {
buttonClick: function(e){
let value = e.target.innerText;
this.$emit('addNumber', value)
}
}
}
</script>

В этом теге скрипта мы не определили никаких данных [нам они не нужны для этого компонента], мы определили единственный buttonClick метод, который улавливает события кликов, получает значение innerText и отправляет событие корневому компоненту.

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

Создайте расширенный компонент

Расширенный компонент очень похож на базовый компонент по структуре и логике.

Во-первых, давайте напишем шаблон, который будет удерживать Advanced кнопки калькулятора и прослушивать события нажатия на любую из кнопок.

<template>
<div @click="buttonClick">
<button class="btn btn-primary">sin</button>
<button class="btn btn-primary">cos</button>
<button class="btn btn-primary">tan</button>
<button class="btn btn-primary">x^</button>
<button class="btn btn-primary">&larr;</button>
<button class="btn btn-primary">C</button>
<button class="btn btn-primary">log</button>
<button class="btn btn-primary">ln</button>
<button class="btn btn-primary">e</button>
<button class="btn btn-primary">∘</button>
<button class="btn btn-primary">rad</button>
<button class="btn btn-primary">√</button>
<button class="btn btn-primary">7</button>
<button class="btn btn-primary">8</button>
<button class="btn btn-primary">9</button>
<button class="btn btn-primary">/</button>
<button class="btn btn-primary">x²</button>
<button class="btn btn-primary">x!</button>
<button class="btn btn-primary">4</button>
<button class="btn btn-primary">5</button>
<button class="btn btn-primary">6</button>
<button class="btn btn-primary">*</button>
<button class="btn btn-primary">(</button>
<button class="btn btn-primary">)</button>
<button class="btn btn-primary">1</button>
<button class="btn btn-primary">2</button>
<button class="btn btn-primary">3</button>
<button class="btn btn-primary">-</button>
<button class="btn btn-primary">%</button>
<button class="btn btn-primary">+</button>
<button class="btn btn-primary">±</button>
<button class="btn btn-primary">0</button>
<button class="btn btn-primary">.</button>
<button class="btn btn-primary">&#x003C0;</button>
<button class="btn btn-primary">+</button>
<button class="btn btn-primary equals">=</button>
</div>

</template>

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

<script>
export default {
name: 'Advanced',
data () {
return {}
},
methods: {
buttonClick: function(e){
let value = e.target.innerHTML;
this.$emit('addNumber', value)
}
}
}

</script>

Мы видим, что buttonClickmethod в расширенном компоненте точно такой же, как в базовом компоненте.

Давайте углубимся в корневой компонент!

Наконец, давайте соединим все приложение вместе через корневой компонент [App.vue].

Структура кода корневого компонента довольно проста.

Так же, как мы сделали с двумя дочерними компонентами, нам нужно определить шаблон, который будет содержать поле ввода и включать переключение двух других компонентов [дочерние компоненты в основном содержат кнопки для калькулятора].

В корневом шаблоне будут:

  1. Механизм переключения между расширенным и основным режимами [с использованием v-show]

2. Поле ввода для отображения перфорированных цифр.

3. Тег корпуса для дочерних компонентов [в данном случае это тег div с классом, называемым кнопками].

Вот визуальное отображение кода шаблона:

<template>
<div id="this">
<div class="container box">
<p> {{ mode }} </p>
<div class="row input-data">
<div class="col-lg-12">
<div class="input-group">
<span class="input-group-addon"> <input class="input-data" @click="changeToggle" type="checkbox" aria-label="Checkbox for following text input"> </span>
<input type="text" class="form-control text-right" v-model="current" aria-label="Text input with checkbox" disabled>
</div>
</div>
</div>
<hr>
<div class="buttons">
<Basic v-show="!toggle" v-bind:current="current" @addNumber="doStuff($event)"></Basic>
<Advanced v-show="toggle"  v-bind:current="current" @addNumber="doStuff($event)"></Advanced>
</div>
</div>
</div>

</template>

Из этого кода мы видим, что компоненты Basic и Advanced вложены в тег div [с классом «buttons»].

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

Давайте посмотрим, как все работает в разделе сценария корневого компонента [:

<template>
<div id="this">
<div class="container box">
<p> {{ mode }} </p>
<div class="row input-data">
<div class="col-lg-12">
<div class="input-group">
<span class="input-group-addon"> <input class="input-data" @click="changeToggle" type="checkbox" aria-label="Checkbox for following text input"> </span>
<input type="text" class="form-control text-right" v-model="current" aria-label="Text input with checkbox" disabled>
</div>
</div>
</div>
<hr>
<div class="buttons">
<Basic v-show="!toggle" v-bind:current="current" @addNumber="doStuff($event)"></Basic>
<Advanced v-show="toggle"  v-bind:current="current" @addNumber="doStuff($event)"></Advanced>
</div>
</div>
</div>
<script>
import Basic from './components/Basic';
import Advanced from './components/Advanced';
export default {
name: 'Calculator',
components: {
'Basic' : Basic,
'Advanced' : Advanced
},
data () {
return {
current: 0,
toggle: false,
mode: 'Basic'
}
},
methods: {
changeToggle: function(){
this.toggle = !this.toggle;
if (this.mode == 'Basic'){
this.mode = 'Advanced'
} else {
this.mode = 'Basic'
}
},
doStuff: function(data){
let temp;
if(Number(data) || data == 0){
temp =  data
if( this.current === 0 ) this.current = "";
this.current += "" + temp;
}
if( !Number(data) ) {
switch ( data ) {
case "/" : {
this.divide();
break;
}
case "" : {
this.backspace();
break;
}
case "C" : {
this.clear();
break;
}
case "*" : {
this.multiply();
break;
}
case "-" : {
this.minus();
break;
}
case "+" : {
this.plus();
break;
}
case "" : {
this.square();
break;
}
case "(" : {
this.openbracket();
break;
}
case ")" : {
this.closebracket();
break;
}
case "=" : {
this.equals();
break;
}
case "±" : {
this.plusMinus();
break;
}
case "%" : {
this.percent();
break;
}
case "." : {
this.decimal();
break;
}
case "sin" : {
this.sin();
break;
}
case "cos" : {
this.cos();
break;
}
case "tan" : {
this.tan();
break;
}
case "ln" : {
this.ln();
break;
}
case "e" : {
this.exp();
break;
}
case "" : {
this.degrees();
break;
}
case "x!" : {
this.factorial();
break;
}
case "rad" : {
this.radians();
break;
}
case "" : {
this.squareRoot();
break;
}
case "x^" : {
this.exponent();
break;
}
case "π" : {
this.pi();
break;
}
case "log" : {
this.log();
break;
}

}
}
},

clear: function() {
this.current = 0;
},
backspace: function() {
this.current = this.current.substring(0, this.current.length - 1);
},
multiply: function() {
this.current += "*";
},
divide: function() {
this.current +=  "/";
},
openbracket: function() {
this.current +=  "(";
},
closebracket: function() {
this.current +=  ")";
},
plus: function() {
this.current +=  "+";
},

minus: function() {
this.current +=  "-";
},

decimal: function() {
this.current +=  ".";
},

plusMinus: function() {
if ( (this.current !== 0) && this.current.charAt(0) === "-" )  {
this.current = this.current.slice(1);
} else {
this.current = "-" + this.current;
}
},
equals: function() {
if ((this.current).indexOf("^") > -1) {
var base = (this.current).slice(0, (this.current).indexOf("^"));
var exponent = (this.current).slice((this.current).indexOf("^") + 1);
this.current = eval("Math.pow(" + base + "," + exponent + ")");
} else {
this.current = eval(this.current);
}
},
factorial: function () {
var number = 1;
if (this.current === 0) {
this.current = "1";
} else if (this.current < 0) {
this.current = NaN;
} else {
var number = 1;
for (var i = this.current; i > 0; i--) {
number *=  i;
}
this.current = number;
}
},
pi: function() {
this.current = (this.current * Math.PI);
},
square: function() {
this.current = (this.current * this.current);
},
squareRoot: function () {
this.current = Math.sqrt(this.current);
},
percent: function() {
this.current = this.current / 100;
},
sin: function () {
this.current = Math.sin(this.current);
},
cos: function () {
this.current = Math.cos(this.current);
},
tan: function () {
this.current = Math.tan(this.current);
},
log: function () {
this.current = Math.log10(this.current);
},
ln: function () {
this.current = Math.log(this.current);
},
exponent: function () {
this.current += "^";
},
exp: function () {
this.current = Math.exp(this.current);
},
radians: function () {
this.current = this.current * (Math.PI / 180);
},

degrees: function () {
this.current = this.current * (180 / Math.PI);
}
}
}

</script>

Сначала мы импортируем базовый и расширенный компоненты в корневой компонент, потому что нам нужно на них ссылаться.

Затем мы объявляем имя приложения и создаем объект компонентов (здесь мы регистрируем компоненты).

В разделе данных регистрируем три key/value пары:

  1. current - отслеживает текущее входное значение
  2. toggle - сохраняет текущее значение переключения.
  3. mode - сохраняет текущий режим

Далее регистрируем несколько методов:

changeToggle отвечает за переключение между базовым и расширенным режимами, а также за обновление значения mode.

Метод doStuff обрабатывает события, которые генерируются дочерними компонентами. Он получает параметр data и обрабатывает его в нескольких случаях. Когда регистр соответствует, он вызывает правильную функцию для обработки математических вычислений.

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

Вывод

Потрясающий! 🔥 Это все, что нужно для создания научного калькулятора с VueJS.

Чтобы запустить этот проект локально, введите в терминале следующие команды.

-- clone the repository
git clone https://github.com/Jordanirabor/Scientific-Calculator-With-VueJS

-- navigate into the directory --
cd Scientific-Calculator-With-VueJS

-- install dependencies --
npm install

-- serve with hot reload at localhost:8080 --
npm run dev

-- build for production with minification --
npm run build

Конец!

Первоначально опубликовано на dev.to.