Addition.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. import React, { useState, useEffect, useRef } from "react";
  2. import DataGrid, {Column} from "devextreme-react/data-grid";
  3. import {addNumbersToGrid, numbersToArrOfArr, afterCommaLen, handleKeyDown} from "./helpers.js";
  4. import "./App.css";
  5. let imdtRes;
  6. let imdtResIdx;
  7. let resArr = [];
  8. let noOfDigits = 0;
  9. function Addition() {
  10. const [input, setInput] = useState(""); // initial user input like 34+56.7
  11. const [realResult, setRealResult] = useState(0); // real result of calculation
  12. const [carryArr, setCarryArr] = useState([]); // array of carries ["1","0"]
  13. const [commaIdx, setCommaIdx] = useState(0); // int: comma position
  14. const [numbers, setNumbers] = useState(0); // array of arrays with input numbers
  15. const [numbersGrid, setNumbersGrid] = useState([{id: 1, number: ""}]);
  16. const [resultsGrid, setResultsGrid] = useState([{id: 1, number: ""}]);
  17. const [stepsGrid, setStepsGrid] = useState([{id: 1, step: ""}]);
  18. const [showSteps, setShowSteps] = useState(true);
  19. // focus the input field
  20. let calculationInput = useRef(null);
  21. useEffect(()=>{
  22. if(calculationInput.current && calculationInput.current.value === ""){
  23. calculationInput.current.focus();
  24. }
  25. },[])
  26. const handleInput = (e) => {
  27. setInput(e.target.value);
  28. }
  29. const handleResChange = (e) => {
  30. if(typeof e === "string"){
  31. imdtRes = e;
  32. }else{
  33. imdtRes = e.target.value;
  34. }
  35. // remove deleted input
  36. if(imdtRes === ""){
  37. resArr.shift();
  38. }else{
  39. // add input number to result array
  40. resArr.unshift(imdtRes);
  41. }
  42. }
  43. const handleCarryChange = (e, nosLeft) => {
  44. if(e.keyCode === 13){
  45. let carryArrCopy = [...carryArr];
  46. carryArrCopy.unshift(e.target.value);
  47. setCarryArr(carryArrCopy);
  48. let noCarry = carryArrCopy[0] === "" || carryArrCopy[0] === "0" || carryArrCopy[0] === undefined
  49. if((nosLeft === 1 && noCarry) ||
  50. // if stop after first iteration, numbers left is undefined at first
  51. (nosLeft !== nosLeft && realResult.toString().length === 1 && noCarry)){
  52. nosLeft = 0;
  53. handleResChange(" ")
  54. }
  55. if(nosLeft === 0){
  56. if(showSteps){
  57. showIdmtResults(carryArrCopy);
  58. addButtonsToImdtSteps();
  59. }else{
  60. finishCalculation();
  61. }
  62. }
  63. }
  64. }
  65. const handleSubmit = (e) => {
  66. startCalculation();
  67. e.preventDefault(); // avoid page reload
  68. }
  69. const startCalculation = () => {
  70. if (input.includes("+")){
  71. let numbers = input.split("+").map(x => parseFloat(x.replace(",",".")));
  72. let realRes = numbers.reduce((x,y) => x+y, 0);
  73. let afterComma = Math.max(...numbers.map(x => afterCommaLen(x)));
  74. realRes = parseFloat(realRes.toFixed(afterComma));
  75. setRealResult(realRes);
  76. console.log("real result:", realRes);
  77. let [numbersArr, commaIdx] = numbersToArrOfArr(numbers);
  78. setNumbers(numbersArr);
  79. setCommaIdx(commaIdx);
  80. setNumbersGrid(addNumbersToGrid(numbersArr, "+"));
  81. }
  82. }
  83. const ResultCarryForm = ({handleResChange, handleCarryChange}) => {
  84. let resInputField = useRef(null);
  85. let carryInputField = useRef(null);
  86. let labelText = "";
  87. let nosLeft = imdtResIdx + 1;
  88. let carryText = "Übertrag = ";
  89. if(typeof numbers === "object" && imdtResIdx !== -1){
  90. // set the digit index, start with the last digit
  91. if(typeof imdtResIdx === "undefined" || imdtResIdx === null){
  92. noOfDigits = Math.min(...numbers.map(n => n.length));
  93. imdtResIdx = noOfDigits - 1;
  94. }
  95. // skip over comma
  96. if(imdtResIdx === commaIdx){
  97. if(!resArr.includes(".")){
  98. handleResChange(".");
  99. }
  100. imdtResIdx = imdtResIdx - 1;
  101. nosLeft = nosLeft - 1;
  102. }
  103. // iterate numbers for this digit index
  104. for (let n in numbers){
  105. // skip last empty number if no carry
  106. if(nosLeft === 1 && carryArr[0] === "0"){
  107. nosLeft = 0;
  108. imdtResIdx = -1;
  109. return <></>
  110. }
  111. // create label text like: d + d + .. = input
  112. let digit = numbers[parseInt(n)][imdtResIdx];
  113. if(digit === "" || digit === "&nbsp;"){
  114. digit = 0;
  115. }
  116. labelText += digit;
  117. if (parseInt(n) === numbers.length - 1){
  118. if(carryArr[0] !== undefined && carryArr[0] > 0){
  119. labelText += " + " + carryArr[0].toString();
  120. }
  121. labelText += " = ";
  122. }else{
  123. labelText += " + ";
  124. }
  125. }
  126. imdtResIdx -= 1;
  127. nosLeft -= 1;
  128. return (
  129. <form>
  130. <label htmlFor="input_result" id="input_result_label">
  131. {labelText}
  132. </label>
  133. <input
  134. onKeyDown={(e) => handleKeyDown(e)}
  135. onChange={(e) => handleResChange(e)}
  136. type="text" id="input_result" size="2" maxLength="1"
  137. aria-labelledby="input_result_label" aria-required="true"
  138. ref={resInputField} autoFocus/>
  139. <br/>
  140. <label htmlFor="input_carry" id="input_carry_label">
  141. {carryText}
  142. </label>
  143. <input
  144. onKeyDown={(e) => handleCarryChange(e, nosLeft)}
  145. type="text" id="input_carry" size="2" maxLength="1"
  146. aria-labelledby="input_carry_label" aria-required="true"
  147. ref={carryInputField}/>
  148. </form>
  149. );
  150. }else{
  151. return (
  152. <>
  153. </>
  154. );
  155. }
  156. }
  157. const addButtonsToImdtSteps = () => {
  158. // workaround to access DOM td after grid values are set
  159. setTimeout(() => {
  160. let table = document.getElementById("idmtResultSteps").getElementsByTagName("table")[0]
  161. let trs = table.getElementsByTagName("tr");
  162. for(let trIdx in trs){
  163. let tr = trs[trIdx];
  164. if(tr instanceof HTMLElement && trIdx < trs.length-1){
  165. let td = tr.getElementsByTagName("td")[0];
  166. let btn = document.createElement("button");
  167. btn.innerHTML = "hier starten";
  168. btn.classList = "btn btn-secondary btn-sm";
  169. btn.id = "btn-" + trIdx;
  170. btn.addEventListener("click", () => startOver(trIdx));
  171. td.appendChild(btn);
  172. }
  173. }
  174. }, 300);
  175. }
  176. const showIdmtResults = (carryArrCopy) => {
  177. let realResArr = realResult.toString().split("");
  178. while(realResArr.length < resArr.length){
  179. realResArr.unshift("&nbsp;"); // add " " before
  180. }
  181. let carries = carryArrCopy.map(x => x || "0")
  182. carries.push("0");
  183. while(carries.length < resArr.length){
  184. carries.unshift("0"); // add "0" before
  185. }
  186. let foundComma = false;
  187. let stepsGridCopy = [];
  188. for (let i=0; i<resArr.length; i++) {
  189. let text = "";
  190. let trueNumbers = false;
  191. let idxNumbers = resArr.length - i - 1;
  192. let idxCarry = carries.length - i - 1;
  193. let realSum = 0;
  194. for (let j=0; j<numbers.length; j++) {
  195. let no = numbers[j][idxNumbers];
  196. if (no!=="." && no!=="&nbsp;"){
  197. trueNumbers = true;
  198. realSum += parseInt(no);
  199. }
  200. text += no
  201. if (j<numbers.length-1){
  202. text += " + "
  203. }
  204. }
  205. // ignore indexes without real digits
  206. if (resArr[idxNumbers]!=="&nbsp;" || (carries[idxCarry]!=="&nbsp;" & carries[idxCarry]!=="0")){
  207. trueNumbers = true;
  208. }
  209. // carry array has no "."
  210. if (resArr[idxNumbers] === "."){
  211. foundComma = true;
  212. trueNumbers = false;
  213. }
  214. if (foundComma){
  215. idxCarry += 1;
  216. }
  217. // add carry only if > 0
  218. if (carries[idxCarry]!=="0"){
  219. realSum += parseInt(carries[idxCarry]);
  220. text += " + " + carries[idxCarry] // + " Übertrag"
  221. }
  222. let realCarry = parseInt(realSum/10).toString()
  223. realSum = (realSum % 10).toString()
  224. text += " = "
  225. if (trueNumbers){
  226. text += resArr[idxNumbers]
  227. text += " mit Übertrag " + carries[idxCarry-1]
  228. text = text.replace(/&nbsp;/g, "0")
  229. text += resArr[idxNumbers]===realSum && carries[idxCarry-1]===realCarry ? ": Richtig " : ": Falsch ";
  230. stepsGridCopy.push({step: text});
  231. }
  232. }
  233. let paragraph = document.getElementById("stepsParagraph")
  234. paragraph.innerHTML = "Rechenschritte: ";
  235. let btnSubmit = document.createElement("button");
  236. btnSubmit.innerHTML = "Ergebnis abgeben";
  237. btnSubmit.addEventListener("click", finishCalculation);
  238. btnSubmit.classList = "btn btn-secondary btn-sm";
  239. btnSubmit.id = "btnSubmitSteps1";
  240. document.getElementById("idmtResultSteps").insertBefore(btnSubmit, paragraph);
  241. setStepsGrid(stepsGridCopy);
  242. let btnSubmit2 = document.createElement("button");
  243. btnSubmit2.innerHTML = "Ergebnis abgeben";
  244. btnSubmit2.addEventListener("click", finishCalculation);
  245. btnSubmit2.classList = "btn btn-secondary btn-sm";
  246. btnSubmit2.id = "btnSubmitSteps2";
  247. document.getElementById("idmtResultSteps").appendChild(btnSubmit2);
  248. document.getElementById("idmtResultSteps").style.display = "inline-block";
  249. document.getElementById("idmtResultSteps").tabIndex = 0;
  250. document.getElementById("idmtResultSteps").focus();
  251. }
  252. const startOver = (idx) => {
  253. let invertedIdx = numbers[0].length - idx - 1;
  254. imdtResIdx = invertedIdx;
  255. let carryArrCopy = [...carryArr];
  256. let slicer = invertedIdx;
  257. if(resArr[0] !== "&nbsp;" && !resArr.includes(".")){
  258. slicer += 1;
  259. }else if(resArr[0] === "&nbsp;" && resArr.includes(".")){
  260. slicer -= 1;
  261. }
  262. carryArrCopy = carryArrCopy.slice(slicer);
  263. // reset result and carries until the chosen step
  264. let commaPos = resArr.indexOf(".");
  265. if(resArr.includes(".") && commaPos >= invertedIdx){
  266. imdtResIdx -= 1;
  267. resArr = resArr.slice(invertedIdx);
  268. }else{
  269. resArr = resArr.slice(invertedIdx + 1);
  270. }
  271. setCarryArr(carryArrCopy);
  272. document.getElementById("btnSubmitSteps1").remove();
  273. document.getElementById("btnSubmitSteps2").remove();
  274. document.getElementById("idmtResultSteps").style.display = "none";
  275. }
  276. const finishCalculation = () => {
  277. document.getElementById("idmtResultSteps").innerHTML = "";
  278. let resCalc = resArr.filter(n => n !== "&nbsp;").join("");
  279. setResultsGrid([{number: resCalc}]);
  280. resCalc = parseFloat(resCalc);
  281. let message = ""
  282. if(resCalc === realResult){
  283. message = "Richtig!"
  284. }else{
  285. message = "Das ist leider falsch."
  286. }
  287. message = "<p>" + message + "</p>"
  288. document.getElementById("finishCalculation").innerHTML = message;
  289. document.getElementById("finishCalculation").tabIndex = "0";
  290. document.getElementById("finishCalculation").focus();
  291. }
  292. return (
  293. <main>
  294. <h1>Addition</h1>
  295. <form onSubmit={(e) => handleSubmit(e)}>
  296. <label htmlFor="calculationInput">
  297. Rechnung:
  298. </label>
  299. <input
  300. type="text"
  301. id="calculationInput"
  302. onChange={(e) => handleInput(e)}
  303. aria-label="Rechnung"
  304. aria-required="true"
  305. ref={calculationInput}/>
  306. <input type="submit" value="berechnen"/>
  307. </form>
  308. <div id="finishCalculation"></div>
  309. <div id="overview">
  310. <DataGrid
  311. dataSource={numbersGrid}
  312. keyExpr="id"
  313. focusedRowEnabled={true}
  314. showBorders={true}
  315. showColumnHeaders={false}
  316. >
  317. <Column dataField="number" />
  318. </DataGrid>
  319. <hr></hr>
  320. <DataGrid
  321. dataSource={resultsGrid}
  322. focusedRowEnabled={true}
  323. showBorders={true}
  324. showColumnHeaders={false}
  325. >
  326. <Column dataField="number" />
  327. </DataGrid>
  328. </div>
  329. <div id="calculation">
  330. <ResultCarryForm
  331. handleResChange={handleResChange}
  332. handleCarryChange={handleCarryChange}/>
  333. </div>
  334. <div id="idmtResultSteps">
  335. <p id="stepsParagraph"></p>
  336. <DataGrid
  337. dataSource={stepsGrid}
  338. focusedRowEnabled={true}
  339. showBorders={true}
  340. showColumnHeaders={false}
  341. noDataText=""
  342. >
  343. <Column dataField="step" />
  344. </DataGrid>
  345. </div>
  346. </main>
  347. );
  348. }
  349. export default Addition;