From 2b999c38eb8ca8efa89f9181bbd9511e9cc078dc Mon Sep 17 00:00:00 2001 From: advocatux Date: Mon, 23 Apr 2018 21:48:20 +0200 Subject: [PATCH] Create chapter-04-s04 Translation of chapter-04-s04 to English --- en/chapter-04-s04.md | 539 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 539 insertions(+) create mode 100644 en/chapter-04-s04.md diff --git a/en/chapter-04-s04.md b/en/chapter-04-s04.md new file mode 100644 index 0000000..caa91ee --- /dev/null +++ b/en/chapter-04-s04.md @@ -0,0 +1,539 @@ +# Introduction to JavaScript +So far the use of QML has been studied to create the user interface of the calculator application. In the last release, a little logic was added to the buttons so the text of the button would be concatenated to the text that showed the results label. The operation was very simple and consisted in capturing the signal generated by pressing the button and modifying the text on the label. + +When programming applications is important to separate the interface from the logic below it. This allows the programmer to maintain the operation even if the appearance of the application changes. A clear example is the appearance of an application on a device that is in vertical or horizontal configuration. For this reason all the logic will be moved to an external JavaScript file. The code will take data from the user interface, process it and act on the interface to show the results. This is not a particularly complicated release but it can be a little harder due to the notions that are introduced. As always, if you have any questions you can use the resources of the course. + +## Showing the status of operations in the interface +The sequence of the application remains: the user inserts a first number, presses on an operation, inserts a second number and finally presses the "=" button. Each one of these operations represents a step in the algorithm used to do the operation. To make it more visual you need to make a small modification to the interface of the calculator to add three labels at the top. + +![Modificaciones del interfaz de la calculadora](chapter-04-s04/01_ui_modifications.png) + +You shouldn't have any problem adding the labels using what you've learned in this course so far. The most external element is a layout Row that allows to arrange the components in horizontal. Then you have to create three labels named as the screenshot shows. Later those labels will be modified, so for this reason they must have an associated ID. Finally the labels are modified to display the text in bold. + +The code of this block is as follows: + +```js +Row { + + anchors.top: pageHeader.bottom + spacing: 150 + + Label { + id: primerNumero + + text: "Primer número" + font.bold: true + } + + Label { + id: operacion + + text: "Operación" + font.bold: true + } + + Label { + id: segundoNumero + + text: "Segundo número" + font.bold: true + } + +} +``` + +## Linking the buttons to the JavaScript file +Clicking on a button produces the onClicked signal. In the code managing the signal we'll do the call to the different functions in the JavaScript file. Text of each button will be used as a parameter, it doesn't matter if it is a number or an operation. It's good for your mental health that all the numbers use the same function to insert data. The same applies for operations, and the equals sign button. How can we distinguish between numbers and operations to do the operation? The application only knows that the user is clicking buttons, not what they're meant to do. + +Three functions will remain to be used to implement the logic of the calculator. + +* MathFunctions.insertNumber(text) +* MathFunctions.insertOperation(text) +* MathFunctions.calculate() + +The first part shows the library that includes the JavaScript functions. The library is specific for this application. We won't use third parties libraries for now. In the exercise part of the last release I proposed as homework to link all the buttons to the result label. Taking that code as a starting point you have to replace the onClicked calls with one of the previously mentioned calls. This becomes clearer in the code. Pay attention in the onClicked part of the buttons. + +```js +// ----------------- +// Grid de botones +// ----------------- +Grid { + + id: grid + anchors.bottom: page.bottom + spacing: 15 + columns: 5 + + // Fila 1 + Button { + text: "9" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "8" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "7" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "DEL" + + font.pointSize: 17 + color: UbuntuColors.red + + width: buttonWidth + height: buttonHeight + } + + Button { + text: "AC" + + font.pointSize: 17 + color: UbuntuColors.red + + width: buttonWidth + height: buttonHeight + } + + // Fila 2 + Button { + text: "4" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "5" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "6" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "*" + + font.pointSize: 17 + color: UbuntuColors.warmGrey + + width: buttonWidth + height: buttonHeight + + // Insertamos la operación + onClicked: MathFunctions.insertOperation(text) + } + + Button { + text: "/" + + font.pointSize: 17 + color: UbuntuColors.warmGrey + + width: buttonWidth + height: buttonHeight + + // Insertamos la operación + onClicked: MathFunctions.insertOperation(text) + } + + // Fila 3 + Button { + text: "1" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "2" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "3" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "+" + + font.pointSize: 17 + color: UbuntuColors.warmGrey + + width: buttonWidth + height: buttonHeight + + // Insertamos la operación + onClicked: MathFunctions.insertOperation(text) + } + + Button { + text: "-" + + font.pointSize: 17 + color: UbuntuColors.warmGrey + + width: buttonWidth + height: buttonHeight + + // Insertamos la operación + onClicked: MathFunctions.insertOperation(text) + } + + // Fila 4 + Button { + text: "0" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.insertNumber(text) + } + + Button { + text: "." + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + } + + Button { + text: "EXP" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + } + + Button { + text: "ANS" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + } + + Button { + text: "=" + + font.pointSize: 17 + color: UbuntuColors.graphite + + width: buttonWidth + height: buttonHeight + + // Insertamos el número + onClicked: MathFunctions.calculate() + } + +} +``` + +Finally we need the code to load the JavaScript library. It's normal to get errors because the file doesn't exists for now. + +```js +import "MathFunctions.js" as MathFunctions +``` + +The code won't compile with those changes. The QML file part is finished but now is time to create the JavaScript file. + +## Creating the JavaScript file +In the QtCreator project tree, you have to click on the Calculator node. + +![Selección del proyecto](chapter-04-s04/02_javascript_file.png) + +Right-click button, Add new, QT Category, JS File. + +![Creación del archivo JavaScript](chapter-04-s04/03_javascript_new.png) + +The assistant to create the file has three steps. In the first one you have to write the name, in the second one the library type, and the last step displays a summary. You have to keep the same configuration as the following screenshots. + +![Asistente del archivo JavaScript - Location](chapter-04-s04/04_javascript_location.png) + +![Asistente del archivo JavaScript - Options](chapter-04-s04/05_javascript_option.png) + +![Asistente del archivo JavaScript - Summary](chapter-04-s04/06_javascript_summary.png) + +# JavaScript file structure +All methods processing the data introduced by the user in the calculator will be included in the JavaScript file. Each method operates on the parameters it receives (from the call onClicked makes) and works on the algorithm variables. At the same time it acts on the interface too to inform the user. Feedback is very important for the user or the user could think that the application is not working or that it's blocked. + +Next we'll see the code for each one of the methods. There's quite a bit of code but it's not too hard to understand it. + +## insertNumber(number) +A method / function is an structure that takes data and make operations on it. Allows us in our issue that all the buttons showing a number have a centered spot to place the value. If a function wasn't used, the code for each button would be duplicated. This is really a bad idea because if you need to change the code at some point due to an error you would need to do the same change on every number. At the end chances of making a mistake would grow significantly. You don't have this kind of issues using methods / functions. + +To work on the number is necessary to pass it to the method as parameter. This value is processed by an algorithm (certain steps to follow) that allows us to make the calculations. + +```js +function insertNumber(number) { + + if (isSecondNumberReady == false) { + + // Componemos el número y actualizamos la etiqueta de resultado + firstNumber = firstNumber * 10 + parseInt(number) + labelResult.text = firstNumber + + // Indicación del estado de la operacion + labelFirstNumber.color = UbuntuColors.orange + labelOperation.color = UbuntuColors.graphite + labelSecondNumber.color = UbuntuColors.graphite + + } else { + + // Componemos el número y actualizamos la etiqueta de resultado + secondNumber = secondNumber * 10 + parseInt(number) + + labelResult.text = firstNumber + " " + operation + " " + secondNumber + + // Indicación del estado de la operación + labelFirstNumber.color = UbuntuColors.graphite + labelOperation.color = UbuntuColors.graphite + labelSecondNumber.color = UbuntuColors.orange + + } + +} +``` + +The if string allows you to run different parts of the code depending on whether a condition is met. If it's fulfilled the first block between braces is carried out. Otherwise, the second block (that starting with else). Both blocks are exclusives, if one of them runs the other one can't. + +We need two numbers and one operation for the calculation algorithm. It would be reasonable to have a method for the first number and another one for the second number. The problem is, how can we know the user finished introducing the first number and wants to introduce the second one? The separating element is the operation the user does. When a button linked to an operation is pressed, the first number will be completed. The if block verifies that condition. If the algorithm isn't ready to insert the second number, it inserts the first number. The isSecondNumberReady variable acts as a flag and allows to discriminate the point of the algorithm in which the code is. + +First requirement is to process the number inserted by the user. A text string reaches the method as a parameter but a number can't be a text string. For this reason we need to convert it in a number with the parseInt method. The number accumulates as the user enters it. First time we get a number, v.g. the number 7. If the user press the number 7 again, the first number must be moved to the left, and add it to the new number. The displacement is achieved multipliying by 10 the initial number. + + +```js +firstNumber = firstNumber * 10 + parseInt(number) +``` + +Once the number has been processed it has to inform the user. We'll use a reference to the result label which is in the QML file. It's important that this label has an ID. How to link the component's IDs to the QML file? We'll see it later. The code sets the number we've calculated as text to the label. + +```js +labelResult.text = firstNumber +``` + +At the beginning of the release, three labels were added to the calculator display. Depending on each step of the algorithm, one of those labels will light up to let us clearly know the status. The operation is the same as in the previous case: the reference and the attribute we expect to modify are used. + +```js +labelFirstNumber.color = UbuntuColors.orange +labelOperation.color = UbuntuColors.graphite +labelSecondNumber.color = UbuntuColors.graphite +``` + +The working logic in the other if block is similar. The only difference is that the result label value will include the first number, the operation, and the second number. + +```js +labelResult.text = firstNumber + " " + operation + " " + secondNumber +``` + +## insertOperation Method +The explanation of the previous method affects this method too. We save the parameter introduced by the user, and the status of the labels at the top is updated. + +```js +function insertOperation(userOperation) { + + operation = userOperation + labelResult.text = firstNumber + " " + operation + + // Indicación del estado de la operación + isSecondNumberReady = true + + labelFirstNumber.color = UbuntuColors.graphite + labelOperation.color = UbuntuColors.orange + labelSecondNumber.color = UbuntuColors.graphite + +} +``` + +## calculate() Method +This method is the responsible to do the operations. It's called when the user presses the equals sign button. An important detail is that only one validation is applied in the division by zero to avoid errors. The user can press the buttons in whichever order, and funny errors will appear in the current code. For example, pressing the equals sign button after introducing the first number and the operation. As the variables used for the calculations have values by default it shouldn't be catastrophic. In the next release we'll see the way to avoid these issues. + +Depending on the kind of operation, one calculation or another is applied. When the calculation ends all labels must come back to their initial status. +```js +function calculate() { + + if (operation == "+") { + + resultado.text = firstNumber + secondNumber + + } else if (operation == "-") { + + resultado.text = firstNumber - secondNumber + + } else if (operation == "*") { + + resultado.text = firstNumber * secondNumber + + } else if (operation == "/") { + + if (secondNumber != 0) { + + resultado.text = firstNumber / secondNumber + + } else { + + resultado.text = 0 + + } + + } + + // Indicación del estado de la operación + labelFirstNumber.color = UbuntuColors.graphite + labelOperation.color = UbuntuColors.graphite + labelSecondNumber.color = UbuntuColors.graphite + + // Inicializamos las variables para la siguiente iteración + firstNumber = 0 + operation = "" + secondNumber = 0 + + isSecondNumberReady = false + +} +``` +The algorithm uses several auxiliary variables to save the information. Once the calculation has finished, the initial values must be restored. +```js +// Inicializamos las variables para la siguiente iteración +firstNumber = 0 +operation = "" +secondNumber = 0 +isSecondNumberReady = false +``` + +# Link between the QML file and the JavaScript file +To access the QML file controls from the JavaScript code, a reference to them is necessary. This reference is passed at the end of the QML file. When a QML file is loaded, a signal is generated. If it's processed is possible to pass the references to the JavaScript file. Although it's possible to do the link at the beginning, we run the risk that the file is not started and bad things could happen. + +In the QML file, we have to add before the last brace the following code: + +```js +// Cuando se ha inicializado el archivo QML hacemos lo mismo con el archivo de JavaScript +Component.onCompleted: MathFunctions.start(primerNumero, operacion, + segundoNumero, resultado) +``` + +On the other hand, is necessary to implement the previous function in the JavaScript file. +```js +function start(firstNumberComponent, operationComponent, + secondNumberComponent, resultComponent) { + + // Log en la consola + console.log("Calculadora conectada al archivo JS") + + // Asignación de las variables + labelFirstNumber = firstNumberComponent + labelOperation = operationComponent + labelSecondNumber = secondNumberComponent + labelResult = resultComponent + +} +``` + +There are a series of global variables in the JavaScript file (they aren't inside a method) that they'll save the references passed as parameter. It's possible to write debugging messages in the QtCreator console with this code: + +```js +console.log("Calculadora conectada al archivo JS") +``` + +That concludes this week's release. Many new concepts but it's important to understand them well because we'll use them frecuently. The calculator can do basic operations now. The use of decimals, and to add functionality to the rest of the buttons, remains to be implemented. Using QML and JavaScript is not complicated. Once you get the working logic is simple to add new functions. + +You can see how the application works in the following screenshots. + +![Primer número](chapter-04-s04/07_first_number.png) + +![Operación](chapter-04-s04/08_operation.png) + +![Segundo número](chapter-04-s04/09_second_number.png) + +![Resultado](chapter-04-s04/10_result.png) + +The exercise for the next release consists in thinking about the way to prevent the user introducing the calculations in the wrong order, or what could happen if the user enters errors willingly, such as entering several decimal points, or divides a small number by a very large number. + +How good is your knowledge about binary and hexadecimal operations?