Home > other >  How to count the occurrences of specified character sequences while editing a textarea?
How to count the occurrences of specified character sequences while editing a textarea?

Time:03-24

I'm looking for a way to automatically count the specified words in the textarea without having to click a button to do so, and in that case, auto update it if there are changes..

Here's my chaotic code so far:

function findWord1() {
  var outputDiv = $('#opening-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "[";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){ }\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a>');
}

function findWord2() {
  var outputDiv = $('#closing-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "]";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}

function findWord3() {
  var outputDiv = $('#opening-added-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "[A>";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}

function findWord4() {
  var outputDiv = $('#closing-added-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "lt;A]";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}

function findWord5() {
  var outputDiv = $('#opening-deleted-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "[D>";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}

function findWord6() {
  var outputDiv = $('#closing-deleted-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "lt;D]";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<button onclick="findWord1();findWord2();findWord3();findWord4();findWord5();findWord6();">Count</button>

<table>
<thead>
    <tr>
        <th scope="col">Items</th>
        <th scope="col">Count</th>
    </tr>
</thead>
<tbody>
    <tr>
        <td>[A&gt;</td>
        <td><a id="opening-added-bracket"></a></td>
    </tr>
    <tr>
        <td>&lt;A]</td>
        <td><a id="closing-added-bracket"></a></td>
    </tr>
    <tr>
        <td>[D&gt;</td>
        <td><a id="opening-deleted-bracket"></a></td>
    </tr>
    <tr>
        <td>&lt;D]</td>
        <td><a id="closing-deleted-bracket"></a></td>
    </tr>
    <tr>
        <td>[</td>
        <td><a id="opening-bracket"></a></td>
    </tr>
    <tr>
        <td>]</td>
        <td><a id="closing-bracket"></a></td>
    </tr>
</tbody>
</table>

<textarea id="textarea3" rows="5">
[A>
lt;A]
[D>
lt;D]
</textarea>

Also if possible, I'd like to retain the "Count" button so I can still manually do it in case the other parts of the code fails..

Apologies if my code looks so bad, but thank you in advance for any help.

CodePudding user response:

Use keyup Simply do this way

function countWordFunc() { 

  let myText = document.getElementById("myText").value;

  let wordsArr = myText.trim().split(" ")
  let countWords = wordsArr.filter(word => word !== "").length
  
  let counter = document.getElementById("counter");
  counter.innerHTML = `Total Words: ${countWords}`;

}
<textarea onkeyup="countWordFunc()" id="myText">Hello, World</textarea>

<div id="counter"></div>

CodePudding user response:

Create a function to call all your others. This is a step toward simplifying your code. Then, create an event listener for the textarea's input event with the new function as its callback.

This question is essentially a duplicate of many other questions involving events. See https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events for some good information, and search for more answers here if you have further questions.

function findAllWords() {
  findWord1();
  findWord2();
  findWord3();
  findWord4();
  findWord5();
  findWord6();
}

document.getElementById('textarea3').addEventListener('input', findAllWords);

function findWord1() {
  var outputDiv = $('#opening-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "[";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){ }\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a>');
}

function findWord2() {
  var outputDiv = $('#closing-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "]";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}

function findWord3() {
  var outputDiv = $('#opening-added-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "[A>";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}

function findWord4() {
  var outputDiv = $('#closing-added-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "lt;A]";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}

function findWord5() {
  var outputDiv = $('#opening-deleted-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "[D>";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}

function findWord6() {
  var outputDiv = $('#closing-deleted-bracket');
  var searchText = $('#textarea3').val();
  var wordMatch = "lt;D]";

  outputDiv.empty();

  var m = searchText.match(new RegExp(wordMatch.toString().replace(/(?=[.\\ *?[^\]$(){}\|])/g, "\\"), "ig"));
  outputDiv.append('<a>'   (m ? m.length : 0)   '</a');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<table>
  <thead>
    <tr>
      <th scope="col">Items</th>
      <th scope="col">Count</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>[A&gt;</td>
      <td>
        <a id="opening-added-bracket"></a>
      </td>
    </tr>
    <tr>
      <td>&lt;A]</td>
      <td>
        <a id="closing-added-bracket"></a>
      </td>
    </tr>
    <tr>
      <td>[D&gt;</td>
      <td>
        <a id="opening-deleted-bracket"></a>
      </td>
    </tr>
    <tr>
      <td>&lt;D]</td>
      <td>
        <a id="closing-deleted-bracket"></a>
      </td>
    </tr>
    <tr>
      <td>[</td>
      <td>
        <a id="opening-bracket"></a>
      </td>
    </tr>
    <tr>
      <td>]</td>
      <td>
        <a id="closing-bracket"></a>
      </td>
    </tr>
  </tbody>
</table>

<textarea id="textarea3" rows="5">
[A>
lt;A]
[D>
lt;D]
</textarea>

CodePudding user response:

Try this, combined functions into one:

    let textarea = $('#textarea3');
textarea.on('keyup', _ => counting());
function counting() {
    var searchText = $('#textarea3').val();

    let words = [];
    words['[A>'] = '#opening-added-bracket';
    words['<A]'] = '#closing-added-bracket';
    words['[D>'] = '#opening-deleted-bracket';
    words['<D]'] = '#closing-deleted-bracket';
    words['['] = '#opening-bracket';
    words[']'] = '#closing-bracket';

    for (const word in words) {
        var outputDiv = $(words[word]);
        outputDiv.empty();
        let count = searchText.split(word).length - 1;
        searchText = searchText.replaceAll(word,'');
        outputDiv.append('<a>'   count   '</a>');
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<button onclick="counting();">Count</button>

<table>
    <thead>
        <tr>
            <th scope="col">Items</th>
            <th scope="col">Count</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>[A&gt;</td>
            <td><a id="opening-added-bracket"></a></td>
        </tr>
        <tr>
            <td>&lt;A]</td>
            <td><a id="closing-added-bracket"></a></td>
        </tr>
        <tr>
            <td>[D&gt;</td>
            <td><a id="opening-deleted-bracket"></a></td>
        </tr>
        <tr>
            <td>&lt;D]</td>
            <td><a id="closing-deleted-bracket"></a></td>
        </tr>
        <tr>
            <td>[</td>
            <td><a id="opening-bracket"></a></td>
        </tr>
        <tr>
            <td>]</td>
            <td><a id="closing-bracket"></a></td>
        </tr>
    </tbody>
</table>

<textarea id="textarea3" rows="5">
[A>
<A]
[D>
<D]
</textarea>

CodePudding user response:

There are following issues with the OP's code ...

  • code based on specific element ids can never be converted into something generic enough in terms of re-usable code.

  • counting single brackets is hazardous especially since they are part of other to be matched character sequences (which the OP refers to as words).

Thus, one might consider a configurable and generic as possible, poor-mans, component-like approach similar to the next provided example code, where ...

  • one e.g. can configure the to be matched items via regex patterns.

  • one furthermore can configure the component's UI/UX behavior like auto-count.

  • every configuration is based on data-* attributes and its HTMLElement.dataset counterpart.

  • due to the above 3rd bullet-point a component is agnostic to it's markup as long as the necessary data-* attributes are getting provided correctly.

The approach also could be summarized like with one of my comments ...

"Initialize a component like structure by reading and computing the config settings, and upon the latter register the count handler with the correct events. The count handler itself then is a straightforward regex based pattern matching (which also works correctly unlike all the other solutions) and count rendering task. Element IDs are not needed, markup is freely selectable, configurations are applied via markup and data attributes. One does not need to touch JS code in order to introduce more to be matched items/words."

function displayItemCount(itemRoot, target) {
  const elmPattern =
    target && itemRoot?.querySelector('[data-reg-pattern]');

  const elmCount =
    elmPattern && itemRoot?.querySelector('[data-match-count]');

  const regXItem =
    elmCount && RegExp(elmPattern.dataset.regPattern, 'g');

  if (regXItem) {
    elmCount.textContent = (target.value.match(regXItem) ?? []).length;
  }
}
function displayItemCountsOfBoundComponentData() {
  const { rootNode, countsTarget } = this;

  rootNode
    .querySelectorAll('[data-item-match]')
    .forEach(itemRoot =>

      displayItemCount(itemRoot, countsTarget)
    );
}

function initializeItemCountsComponent(rootNode) {
  const { dataset } = rootNode;
  let {
    countsTargetSelector = '',
    triggerCountsSelector = '',
    autoCount = false,
  } = dataset;
  
  countsTargetSelector = countsTargetSelector.trim();
  triggerCountsSelector = triggerCountsSelector.trim();

  const countsTarget = (
    rootNode.querySelector(countsTargetSelector || null) ||
    document.querySelector(countsTargetSelector || null)
  );
  if (countsTarget) {

    let countsTriggers = rootNode
      .querySelectorAll(triggerCountsSelector || null);

    if (countsTriggers.length === 0) {
      countsTriggers = document
        .querySelectorAll(triggerCountsSelector || null);
    }
    const isAutoCount = (
      (countsTriggers.length === 0) || (

        (autoCount !== false) && (

          (autoCount.trim() === '') ||
          (autoCount.trim().toLowerCase() === 'true')
        )
      )
    );
    const displayItemCounts =
      displayItemCountsOfBoundComponentData.bind({
        rootNode,
        countsTarget,
      });

    countsTriggers.forEach(elmNode =>
      elmNode
        .addEventListener('click', displayItemCounts)
    );
    if (isAutoCount) {

      countsTarget
        .addEventListener('input', displayItemCounts);

      // trigger component's initial item counts.
      displayItemCounts();
    }
  }
}

function main() {
  document
    .querySelectorAll('[data-item-counts]')
    .forEach(initializeItemCountsComponent);
}
main();
body { margin: 0; }
article { position: relative; display: inline-block; margin: 0 20px 0 0; }
[data-item-counts] { float: left; margin: 0 10px 0 0; }
textarea { margin: 0; }
button { position: absolute; left: 0; top: 175px; }
<article>
  <table
    data-item-counts
    data-counts-target-selector="#textarea3"
    data-trigger-counts-selector="#count-textarea3-items"
  >
    <thead>
      <tr>
        <th scope="col">Items</th>
        <th scope="col">Count</th>
      </tr>
    </thead>
    <tbody>
      <tr data-item-match>
        <td data-reg-pattern="\[A>" title="Opening Added Bracket">[A&gt;</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="<A]" title="Closing Added Bracket">&lt;A]</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="\[D>" title="Opening Deleted Bracket">[D&gt;</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="<D]" title="Closing Deleted Bracket">&lt;D]</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="\[(?!A|D)" title="Opening Bracket">[</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="(?<!<A|D)]" title="Closing Bracket">]</td>
        <td data-match-count>.?.</td>
      </tr>
    </tbody>
  </table>

  <textarea
    id="textarea3"
    cols="16"
    rows="12">
[A&gt;&lt;A][D&gt;&lt;D][[]][]&lt;A][D&gt;

[A&gt;&lt;A][D&gt;&lt;D][[]][]&lt;A][D&gt;

... explicitly triggered count.</textarea>

  <button id="count-textarea3-items">Count Items</button>
</article>

<article>
  <table
    data-item-counts
    data-counts-target-selector="#textarea54"
    data-auto-count
  >
    <thead>
      <tr>
        <th scope="col">Items</th>
        <th scope="col">Count</th>
      </tr>
    </thead>
    <tbody>
      <tr data-item-match>
        <td data-reg-pattern="\[A>" title="Opening Added Bracket">[A&gt;</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="<A]" title="Closing Added Bracket">&lt;A]</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="\[D>" title="Opening Deleted Bracket">[D&gt;</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="<D]" title="Closing Deleted Bracket">&lt;D]</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="\[(?!A|D)" title="Opening Bracket">[</td>
        <td data-match-count>.?.</td>
      </tr>
      <tr data-item-match>
        <td data-reg-pattern="(?<!<A|D)]" title="Closing Bracket">]</td>
        <td data-match-count>.?.</td>
      </tr>
    </tbody>
  </table>

  <textarea
    id="textarea54"
    cols="16"
    rows="12"
  >[[]][]&lt;A][D&gt;[A&gt;&lt;A][D&gt;&lt;D]

... auto-count while editing.</textarea>

</article>

  • Related