Home > Net >  Iterate through html table and calculates value
Iterate through html table and calculates value

Time:10-16

The goal is that people should enter a value in the input field and JavaScript should multiply with a fixed value.

The code should do this for every row and do this automatically without refreshing.

JavaScript works for the first row but if I add the loop (first 3 lines of code) then it doesn't work anymore.

The Table looks like this

<table class="u-table-entity" id="table_gebäck">
  <script src="calculation.js"></script>
  <colgroup>
    <col width="20%">
    <col width="2.1%">
    <col width="22%">
    <col width="21.7%">
    <col width="34.2%">
  </colgroup>
  <tbody >
    <tr style="height: 55px;">
      <b>
      <td ><b>Produkt</b><span style="font-weight: 700;"></span>
      </td>
      <td ></td>
      <td ><b>Einzelpreis</b></td>
      <td ><b>Menge</b></td>
      <td ><b>Gesamtpreis</b></td>
      </b>
    </tr>
    <tr style="height: 55px;">
      <td >Kornspitz</td>
      <td ></td>
      <td >
        <p value="1.39" id="basePrice">1,39 €</p>
      </td>
      <td >
        <form id="Menge">
          <input type="number" min="0" id="quantity" value="0" step="1.0">
        </form>
      </td>
      <td >
        <form id="sum">
          <p><output id="field_sum" for="quantity">0</output> €</p>
        </form>
      </td>
    </tr>
    <tr style="height: 55px;">
      <td >Row 2</td>
      <td ></td>
      <td >
        <p value="5.39" id="basePrice">5,39 €</p>
      </td>
      <td >
        <form id="Menge">
          <input type="number" min="0" id="quantity" value="0" step="1.0">
        </form>
      </td>
      <td >
        <form id="sum">
          <p><output id="field_sum" for="quantity">0</output> €</p>
        </form>
      </td>
    </tr>
  </tbody>
</table>

and the JavaScript code I have created so far

var table = document.getElementById("table_gebäck");

for (var i = 0, row; row = table.rows[i]; i  ) {
  let row = table.rows[i]
  (function () {
    const basePrice = document.getElementsByClassName("basePrice");
    const quantityInput = document.getElementsByClassName("quantity");
    const resOutput = document.getElementsByClassName("field_sum");
    quantityInput.addEventListener("change", function () {
      let currentQuantity = parseFloat(quantityInput.value);
      let currentBasePrice = parseFloat(basePrice.getAttribute("value"));
      resOutput.textContent = currentQuantity * currentBasePrice;
    });
  }());
}

EDIT

This also helped me

Calculating total price by iterating in the table using JavaScript

CodePudding user response:

In addition to the markup cleanup/sanitize task(s) one could implement kind of a micro-component approach.

A viable solution which also is agnostic to (decoupled from) a certain DOM hierarchy can be based on custom data attributes and dataset access together with bound handler data ...

function jsonParse(value) {
  let result = null;
  try {
    result = JSON.parse(value);
  } catch(exception) {}
  return result;
}
function sanitizeFloatValue(value) {
  value = parseFloat(value);
  return Number.isFinite(value) ? value : null;
}

function updateTotalValueFromBoundContextData(/* evt */) {
  const { source, target, baseValue, toFixed } = this;
  const totalValue = baseValue * (sanitizeFloatValue(source.value) || 0);

  target.value = (typeof toFixed === 'number')
    ? totalValue.toFixed(toFixed)
    : totalValue;
}
function initializeUpdateTotalValue(componentRoot) {
  const source = componentRoot
    .querySelector('[data-base-value]');
  const target = componentRoot
    .querySelector('[data-total-value]');

  const baseValue = sanitizeFloatValue(source.dataset.baseValue) || 0;

  const config = jsonParse(componentRoot.dataset.updateTotalValue) ?? {};
  const toFixed = sanitizeFloatValue(config.toFixed);

  const context = { source, target, baseValue, toFixed };

  source
    .addEventListener('input',
      updateTotalValueFromBoundContextData.bind(context)
    );

  // initial update call
  updateTotalValueFromBoundContextData.call(context);
}

function main() {
  document
    .querySelectorAll('[data-update-total-value]')
    .forEach(initializeUpdateTotalValue);
}
main();
body { zoom: .8; }
tr, td { margin: 0!important; padding: 0!important; height: unset!important; }
<form name="product-orders">
  <!-- <script src="calculation.js"></script> //-->
  <table class="u-table-entity">
    <colgroup>
      <col width="20%">
      <col width="2.1%">
      <col width="22%">
      <col width="21.7%">
      <col width="34.2%">
    </colgroup>
    <tbody class="u-table-alt-grey-5 u-table-body">
      <tr style="height: 55px;">
        <td class="u-table-cell u-table-cell-1"><b>Produkt</b><span style="font-weight: 700;"></span>
        </td>
        <td class="u-table-cell"></td>
        <td class="u-table-cell u-table-cell-3"><b>Einzelpreis</b></td>
        <td class="u-table-cell u-table-cell-4"><b>Menge</b></td>
        <td class="u-table-cell u-table-cell-5"><b>Gesamtpreis</b></td>
      </tr>
      <tr style="height: 55px;" data-update-total-value='{"toFixed": 2}'>
        <td class="u-table-cell">Kornspitz</td>
        <td class="u-table-cell"></td>
        <td class="u-table-cell">
          <p value="1.39">1,39 €</p>
        </td>
        <td class="u-table-cell">
          <input type="number"
            min="0" value="0" step="1"
            name="quantity-Kornspitz"
            id="quantity-Kornspitz"
            data-base-value="1.39"
          />
        </td>
        <td class="u-table-cell">
          <p>
            <output for="quantity-Kornspitz" data-total-value>0</output></p>
        </td>
      </tr>
      <tr style="height: 55px;" data-update-total-value='{"toFixed": 2}'>
        <td class="u-table-cell">Row 2</td>
        <td class="u-table-cell"></td>
        <td class="u-table-cell">
          <p value="5.39">5,39 €</p>
        </td>
        <td class="u-table-cell">
          <input type="number"
            min="0" value="0" step="1"
            name="quantity-Row_2"
            id="quantity-Row_2"
            data-base-value="5.39"
          />
        </td>
        <td class="u-table-cell">
          <p>
            <output for="quantity-Row_2" data-total-value>0</output></p>
        </td>
      </tr>
      <tr style="height: 55px;" data-update-total-value>
        <td class="u-table-cell">float number calculation</td>
        <td class="u-table-cell"></td>
        <td class="u-table-cell">
          <p value="1.39">1,39 €</p>
        </td>
        <td class="u-table-cell">
          <input type="number"
            min="0" value="5" step="1"
            name="quantity-Row_3"
            id="quantity-Row_3"
            data-base-value="1.39"
          />
        </td>
        <td class="u-table-cell">
          <p>
            <output for="quantity-Row_3" data-total-value>0</output></p>
        </td>
      </tr>
      <tr style="height: 55px;" data-update-total-value='{"toFixed": 4}'>
        <td class="u-table-cell"><code>toFixed</code> set to 4</td>
        <td class="u-table-cell"></td>
        <td class="u-table-cell">
          <p value="5.39">5,39 €</p>
        </td>
        <td class="u-table-cell">
          <input type="number"
            min="0" value="3" step="1"
            name="quantity-Row_4"
            id="quantity-Row_4"
            data-base-value="5.39"
          />
        </td>
        <td class="u-table-cell">
          <p>
            <output for="quantity-Row_4" data-total-value>0</output></p>
        </td>
      </tr>
    </tbody>
  </table>
</form>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Edit

"Thank you very much for sharing this But i have no idea how to use it – Setzi138"

Ok, let me try then ...

Firstly, the OP needs to write valid markup, thus there are never duplicate values for any element's id property.

Secondly, this task should be solved as generic as possible. One e.g. should not implement any code which is tightly coupled to both a specific DOM hierarchy (or markup) or a too specific task like calculating a total price.

Thus one has to find a solution which easily adapts to other environments with equal tasks like here which actually is ... "Updating and displaying a total value calculated from a base value and a currently changed multiplier whenever this multiplier value changes".

The above carefully worded requirement gives the direction or even points to a solution's corner stones.

A main initialization task firstly needs to identify each structure before it can be initialized.

A viable approach is to utilize custom data-* attributes instead of class (coupled to layout and styling) and id (too specific, disqualifies for generic solutions) attributes. In addition one can provide any string based data to a custom attribute which can be accessed via an HTMLElement's dataset property.

Even though the approach claims to be as markup agnostic as can be, there is one precondition to it. The structure of such a "micro component" needs to have a root element and two other elements which both need to be nested under/within the component root. One element is the trigger or source of the quantity changes and the other serves as target for the updated total value.

Utilizing [data-*] attributes also has no disadvantage with accessing such elements within the DOM since a solutions makes use of modern element queries via <element >.querySelector and <element >.querySelectorAll.

The only things left now are a proper naming and the implementation itself.

...

to be continued

...

With more iterations over the first shown approach one finally could provide a module based implementation of a highly generic, customizable and self-initializing component which due to the customization ability also runs in DOM environments where one is not allowed of changing the markup on a large scale ...

// utility functions
function parseJSON(value) {
  let result = null;
  try {
    result = JSON.parse(value);
  } catch(exception) {}
  return result;
}
function sanitizeFloatValue(value) {
  value = parseFloat(value);
  return Number.isFinite(value) ? value : null;
}

function getCustomKeyFromAttributeSelector(selector) {
  return (/\[\s*data-(?<customKey>[^\s\]] )(?:\s|\])/)
    .exec(selector)
    ?.groups
    ?.customKey;
}
function getDatasetKeyFromCustomAttributeSelector(selector) {
  return getCustomKeyFromAttributeSelector(selector)
    ?.toLowerCase()
    ?.replace((/-(\p{Ll})/gu), (match, char) => char.toUpperCase());
}

// component specific module based code
const updateTotalValueModule = (function () {
  const DEFAULT_CONFIG = {
    calculation: {
      baseValue: 0,
      toFixed: 2,
    },
    selectors: {
      source: '[data-base-value]',
      target: '[data-total-value]',
    },
    eventType: 'input',
  }
  const DEFAULT_SELCTOR_COMPONENT = '[data-update-total-value]';

  function updateTotalValueFromBoundContextData(/* evt */) {
    const { source, target, baseValue, toFixed } = this;
    const totalValue = baseValue * (sanitizeFloatValue(source.value) || 0);

    target.value = (typeof toFixed === 'number')
      ? totalValue.toFixed(toFixed)
      : totalValue;
  }
  let currentConfigKey;

  function initializeUpdateTotalValue(componentRoot) {
    const { // config values.
      selectors = DEFAULT_CONFIG.selectors,
      eventType = DEFAULT_CONFIG.eventType,
      calculation = DEFAULT_CONFIG.calculation
    } = (parseJSON(
      componentRoot.dataset[currentConfigKey]
    ) ?? DEFAULT_CONFIG);

    const source = componentRoot.querySelector(
      selectors.source || DEFAULT_CONFIG.selectors.source
    );
    const target = source && componentRoot.querySelector(
      selectors.target || DEFAULT_CONFIG.selectors.target
    );
    if (source && target) {

      const baseValue = (
        calculation.baseValue ||
        sanitizeFloatValue(source.dataset[
          getDatasetKeyFromCustomAttributeSelector(
            selectors.source || DEFAULT_CONFIG.selectors.source
          )
        ]) || 0
      );
      const toFixed = sanitizeFloatValue(
        calculation.toFixed || DEFAULT_CONFIG.calculation.toFixed
      );
      const context = { source, target, baseValue, toFixed };

      source.addEventListener(
        eventType,
        updateTotalValueFromBoundContextData.bind(context)
      );

      // initial update call
      updateTotalValueFromBoundContextData.call(context);
    }
  }
  function initialize(customAttributeSelector) {
    const selector = (typeof customAttributeSelector === 'string')
      && customAttributeSelector
      || DEFAULT_SELCTOR_COMPONENT;

    currentConfigKey = getDatasetKeyFromCustomAttributeSelector(selector);

    document
      .querySelectorAll(selector)
      .forEach(initializeUpdateTotalValue);
  }

  // (module) export(s)
  return {
    initialize
  };

}());

// running the component ...

// ... does target any default-config conform component.
updateTotalValueModule.initialize();

// ... does target any custom specific component.
updateTotalValueModule.initialize('[data-auto-total-price]');
body { zoom: .8; }
tr, td { margin: 0!important; padding: 0!important; height: unset!important; }
<!-- <script src="calculation.js"></script> //-->
<table class="u-table-entity" id="table_gebäck">
  <colgroup>
    <col width="20%">
    <col width="2.1%">
    <col width="22%">
    <col width="21.7%">
    <col width="34.2%">
  </colgroup>
  <tbody class="u-table-alt-grey-5 u-table-body">
    <tr style="height: 55px;">
      <td class="u-table-cell u-table-cell-1"><b>Produkt</b><span style="font-weight: 700;"></span>
      </td>
      <td class="u-table-cell"></td>
      <td class="u-table-cell u-table-cell-3"><b>Einzelpreis</b></td>
      <td class="u-table-cell u-table-cell-4"><b>Menge</b></td>
      <td class="u-table-cell u-table-cell-5"><b>Gesamtpreis</b></td>
    </tr>
    <tr style="height: 55px;" data-update-total-value ='{"calculation":{"baseValue":1.39,"toFixed":2},"selectors":{"source":"form input[type=\"number\"]","target":"form output"},"eventType":"input"}'>
      <td class="u-table-cell">Kornspitz</td>
      <td class="u-table-cell"></td>
      <td class="u-table-cell">
        <p>1,39 €</p>
      </td>
      <td class="u-table-cell">
        <form name="amount_Kornspitz">
          <input type="number" value="0" min="0" step="1" id="amount_Kornspitz">
        </form>
      </td>
      <td class="u-table-cell">
        <form name="price_total_Kornspitz">
          <p>
            <output for="amount_Kornspitz">0</output></p>
        </form>
      </td>
    </tr>
    <tr style="height: 55px;" data-auto-total-price='{"calculation":{"toFixed":4},"selectors":{"target":"form output"}}'>
      <td class="u-table-cell">Row 2</td>
      <td class="u-table-cell"></td>
      <td class="u-table-cell">
        <p>5,39 €</p>
      </td>
      <td class="u-table-cell">
        <form name="amount_Row_2">
          <input type="number" value="5" min="0" step="1" id="amount_Row_2" data-base-value="5.39">
        </form>
      </td>
      <td class="u-table-cell">
        <form name="price_total_Row_2">
          <p>
            <output for="amount_Row_2">0</output></p>
        </form>
      </td>
    </tr>
  </tbody>
</table>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related