| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 | import React, { useState, useEffect, useRef } from "react";import DataGrid, {Column} from "devextreme-react/data-grid";import {addNumbersToGrid, numbersToArrOfArr, afterCommaLen, handleKeyDown} from "./helpers.js";import "./App.css";let imdtRes;let imdtResIdx;let resArr = [];let noOfDigits = 0;function Addition() {  const [input, setInput] = useState(""); // initial user input like 34+56.7  const [realResult, setRealResult] = useState(0); // real result of calculation  const [carryArr, setCarryArr] = useState([]); // array of carries ["1","0"]  const [commaIdx, setCommaIdx] = useState(0); // int: comma position  const [numbers, setNumbers] = useState(0); // array of arrays with input numbers  const [numbersGrid, setNumbersGrid] = useState([{id: 1, number: ""}]);  const [resultsGrid, setResultsGrid] = useState([{id: 1, number: ""}]);  const [stepsGrid, setStepsGrid] = useState([{id: 1, step: ""}]);  const [showSteps, setShowSteps] = useState(true);  // focus the input field  let calculationInput = useRef(null);  useEffect(()=>{    if(calculationInput.current && calculationInput.current.value === ""){      calculationInput.current.focus();    }  },[])  const handleInput = (e) => {    setInput(e.target.value);  }  const handleResChange = (e) => {    if(typeof e === "string"){      imdtRes = e;    }else{      imdtRes = e.target.value;    }    // remove deleted input    if(imdtRes === ""){      resArr.shift();    }else{      // add input number to result array      resArr.unshift(imdtRes);    }  }  const handleCarryChange = (e, nosLeft) => {    if(e.keyCode === 13){      let carryArrCopy = [...carryArr];      carryArrCopy.unshift(e.target.value);      setCarryArr(carryArrCopy);      let noCarry = carryArrCopy[0] === "" || carryArrCopy[0] === "0" || carryArrCopy[0] === undefined      if((nosLeft === 1 && noCarry) ||         // if stop after first iteration, numbers left is undefined at first         (nosLeft !== nosLeft && realResult.toString().length === 1 && noCarry)){        nosLeft = 0;        handleResChange(" ")      }      if(nosLeft === 0){        if(showSteps){          showIdmtResults(carryArrCopy);          addButtonsToImdtSteps();        }else{          finishCalculation();        }      }    }  }  const handleSubmit = (e) => {    startCalculation();    e.preventDefault(); // avoid page reload  }  const startCalculation = () => {    if (input.includes("+")){      let numbers = input.split("+").map(x => parseFloat(x.replace(",",".")));      let realRes = numbers.reduce((x,y) => x+y, 0);      let afterComma = Math.max(...numbers.map(x => afterCommaLen(x)));      realRes = parseFloat(realRes.toFixed(afterComma));      setRealResult(realRes);      console.log("real result:", realRes);      let [numbersArr, commaIdx] = numbersToArrOfArr(numbers);      setNumbers(numbersArr);      setCommaIdx(commaIdx);      setNumbersGrid(addNumbersToGrid(numbersArr, "+"));    }  }  const ResultCarryForm = ({handleResChange, handleCarryChange}) => {    let resInputField = useRef(null);    let carryInputField = useRef(null);    let labelText = "";    let nosLeft = imdtResIdx + 1;    let carryText = "Übertrag = ";    if(typeof numbers === "object" && imdtResIdx !== -1){      // set the digit index, start with the last digit      if(typeof imdtResIdx === "undefined" || imdtResIdx === null){        noOfDigits = Math.min(...numbers.map(n => n.length));        imdtResIdx = noOfDigits - 1;      }      // skip over comma      if(imdtResIdx === commaIdx){        if(!resArr.includes(".")){          handleResChange(".");        }        imdtResIdx = imdtResIdx - 1;        nosLeft = nosLeft - 1;      }      // iterate numbers for this digit index      for (let n in numbers){        // skip last empty number if no carry        if(nosLeft === 1 && carryArr[0] === "0"){          nosLeft = 0;          imdtResIdx = -1;          return <></>        }        // create label text like: d + d + .. = input        let digit = numbers[parseInt(n)][imdtResIdx];        if(digit === "" || digit === " "){          digit = 0;        }        labelText += digit;        if (parseInt(n) === numbers.length - 1){          if(carryArr[0] !== undefined && carryArr[0] > 0){            labelText += " + " + carryArr[0].toString();          }          labelText += " = ";        }else{          labelText += " + ";        }      }      imdtResIdx -= 1;      nosLeft -= 1;      return (        <form>          <label htmlFor="input_result" id="input_result_label">            {labelText}          </label>          <input            onKeyDown={(e) => handleKeyDown(e)}            onChange={(e) => handleResChange(e)}            type="text" id="input_result" size="2" maxLength="1"            aria-labelledby="input_result_label" aria-required="true"            ref={resInputField} autoFocus/>          <br/>          <label htmlFor="input_carry" id="input_carry_label">            {carryText}          </label>          <input            onKeyDown={(e) => handleCarryChange(e, nosLeft)}            type="text" id="input_carry" size="2" maxLength="1"            aria-labelledby="input_carry_label" aria-required="true"            ref={carryInputField}/>        </form>      );    }else{      return (        <>        </>      );    }  }  const addButtonsToImdtSteps = () => {    // workaround to access DOM td after grid values are set    setTimeout(() => {      let table = document.getElementById("idmtResultSteps").getElementsByTagName("table")[0]      let trs = table.getElementsByTagName("tr");      for(let trIdx in trs){        let tr = trs[trIdx];        if(tr instanceof HTMLElement && trIdx < trs.length-1){          let td = tr.getElementsByTagName("td")[0];          let btn = document.createElement("button");          btn.innerHTML = "hier starten";          btn.classList = "btn btn-secondary btn-sm";          btn.id = "btn-" + trIdx;          btn.addEventListener("click", () => startOver(trIdx));          td.appendChild(btn);        }      }    }, 300);  }  const showIdmtResults = (carryArrCopy) => {    let realResArr = realResult.toString().split("");    while(realResArr.length < resArr.length){      realResArr.unshift(" "); // add " " before    }    let carries = carryArrCopy.map(x => x || "0")    carries.push("0");    while(carries.length < resArr.length){      carries.unshift("0"); // add "0" before    }    let foundComma = false;    let stepsGridCopy = [];    for (let i=0; i<resArr.length; i++) {      let text = "";      let trueNumbers = false;      let idxNumbers = resArr.length - i - 1;      let idxCarry = carries.length - i - 1;      let realSum = 0;      for (let j=0; j<numbers.length; j++) {        let no = numbers[j][idxNumbers];        if (no!=="." && no!==" "){          trueNumbers = true;          realSum += parseInt(no);        }        text += no        if (j<numbers.length-1){          text += " + "        }      }      // ignore indexes without real digits      if (resArr[idxNumbers]!==" " || (carries[idxCarry]!==" " & carries[idxCarry]!=="0")){        trueNumbers = true;      }      // carry array has no "."      if (resArr[idxNumbers] === "."){        foundComma = true;        trueNumbers = false;      }      if (foundComma){        idxCarry += 1;      }      // add carry only if > 0      if (carries[idxCarry]!=="0"){        realSum += parseInt(carries[idxCarry]);        text += " + " + carries[idxCarry] // + " Übertrag"      }      let realCarry = parseInt(realSum/10).toString()      realSum = (realSum % 10).toString()      text += " = "      if (trueNumbers){        text += resArr[idxNumbers]        text += " mit Übertrag " + carries[idxCarry-1]        text = text.replace(/ /g, "0")        text += resArr[idxNumbers]===realSum && carries[idxCarry-1]===realCarry ? ": Richtig " : ": Falsch ";        stepsGridCopy.push({step: text});      }    }    let paragraph = document.getElementById("stepsParagraph")    paragraph.innerHTML = "Rechenschritte: ";    let btnSubmit = document.createElement("button");    btnSubmit.innerHTML = "Ergebnis abgeben";    btnSubmit.addEventListener("click", finishCalculation);    btnSubmit.classList = "btn btn-secondary btn-sm";    btnSubmit.id = "btnSubmitSteps1";    document.getElementById("idmtResultSteps").insertBefore(btnSubmit, paragraph);    setStepsGrid(stepsGridCopy);    let btnSubmit2 = document.createElement("button");    btnSubmit2.innerHTML = "Ergebnis abgeben";    btnSubmit2.addEventListener("click", finishCalculation);    btnSubmit2.classList = "btn btn-secondary btn-sm";    btnSubmit2.id = "btnSubmitSteps2";    document.getElementById("idmtResultSteps").appendChild(btnSubmit2);    document.getElementById("idmtResultSteps").style.display = "inline-block";    document.getElementById("idmtResultSteps").tabIndex = 0;    document.getElementById("idmtResultSteps").focus();  }  const startOver = (idx) => {    let invertedIdx = numbers[0].length - idx - 1;    imdtResIdx = invertedIdx;    let carryArrCopy = [...carryArr];    let slicer = invertedIdx;    if(resArr[0] !== " " && !resArr.includes(".")){      slicer += 1;    }else if(resArr[0] === " " && resArr.includes(".")){      slicer -= 1;    }    carryArrCopy = carryArrCopy.slice(slicer);    // reset result and carries until the chosen step    let commaPos = resArr.indexOf(".");    if(resArr.includes(".") && commaPos >= invertedIdx){      imdtResIdx -= 1;      resArr = resArr.slice(invertedIdx);    }else{      resArr = resArr.slice(invertedIdx + 1);    }    setCarryArr(carryArrCopy);    document.getElementById("btnSubmitSteps1").remove();    document.getElementById("btnSubmitSteps2").remove();    document.getElementById("idmtResultSteps").style.display = "none";  }  const finishCalculation = () => {    document.getElementById("idmtResultSteps").innerHTML = "";    let resCalc = resArr.filter(n => n !== " ").join("");    setResultsGrid([{number: resCalc}]);    resCalc = parseFloat(resCalc);    let message = ""    if(resCalc === realResult){      message = "Richtig!"    }else{      message = "Das ist leider falsch."    }    message = "<p>" + message + "</p>"    document.getElementById("finishCalculation").innerHTML = message;    document.getElementById("finishCalculation").tabIndex = "0";    document.getElementById("finishCalculation").focus();  }  return (    <main>      <h1>Addition</h1>      <form onSubmit={(e) => handleSubmit(e)}>        <label htmlFor="calculationInput">          Rechnung:        </label>        <input          type="text"          id="calculationInput"          onChange={(e) => handleInput(e)}          aria-label="Rechnung"          aria-required="true"          ref={calculationInput}/>        <input type="submit" value="berechnen"/>      </form>      <div id="finishCalculation"></div>      <div id="overview">        <DataGrid          dataSource={numbersGrid}          keyExpr="id"          focusedRowEnabled={true}          showBorders={true}          showColumnHeaders={false}        >          <Column dataField="number" />        </DataGrid>        <hr></hr>        <DataGrid          dataSource={resultsGrid}          focusedRowEnabled={true}          showBorders={true}          showColumnHeaders={false}        >          <Column dataField="number" />        </DataGrid>      </div>      <div id="calculation">        <ResultCarryForm          handleResChange={handleResChange}          handleCarryChange={handleCarryChange}/>      </div>      <div id="idmtResultSteps">        <p id="stepsParagraph"></p>        <DataGrid          dataSource={stepsGrid}          focusedRowEnabled={true}          showBorders={true}          showColumnHeaders={false}          noDataText=""        >          <Column dataField="step" />        </DataGrid>      </div>    </main>  );}export default Addition;
 |