5

I have a few spans:

<span class="first" data-id="1" />
<span class="second" data-id="4" />
<span class="second" data-id="2" />
<span class="third" data-id="5" />

And operations on them:

const spans = document.querySelectorAll('span');

const list = [];

spans.forEach(function(span) {
    if (typeof list[span.getAttribute('class')] === 'undefined') {
    list[span.getAttribute('class')] = [];
  }

  list[span.getAttribute('class')].push(span.getAttribute('data-id'));
});

console.log(list);
console.log(JSON.stringify(list));

But JSON.stringify return empty array.

How can I count the number of occurrences of data-id at a given SPAN the easiest way and next get it to string? I would like to send this data to API.

Fiddle: https://jsfiddle.net/x7thc59v/

1
  • try const list = {} JSON.stringify will return empty array for any not number indexes. And you can use const obj = {} instead of list since you don't want list you want object. Commented Aug 24, 2019 at 9:09

4 Answers 4

2

here is a code that's working: use object instead of array to have key. use var instead of const to modify your variable;

const spans = document.querySelectorAll('span');

var list = {};

spans.forEach(function(span) {
    if (typeof list[span.getAttribute('class')] === 'undefined') {
    list[span.getAttribute('class')] = [];
  }

  list[span.getAttribute('class')].push(span.getAttribute('data-id'));
});

console.log(list);
console.log(JSON.stringify(list));
Sign up to request clarification or add additional context in comments.

3 Comments

there is no need to use var instead of const - the contents of ` const` array or object are still modifiable.
yes but (it's my opinion, for my codes), I use const when I know this variable does not need a change. I use var if the variable will be changed in any time, and let in for loop. (I repeat it's my way of doing things, but you're right)
Do what you want in your own code, but please don't tell people to do it unnecessarily.
2

If you want output like this [{"first":["1"]},{"second":["4","2"]},{"third":["5"]}]

Then you can follow this appraoch

const spans = document.querySelectorAll('span');
const list = [];
spans.forEach(function(span) {
 const className = span.getAttribute('class');
 const valIndex = list.findIndex(val => val[className]);
 const hasVal = valIndex !== -1;
 if (className && hasVal) {
  const preVal = list[valIndex][className];
  list[valIndex][className] = preVal.concat(span.getAttribute('data-id'));
 } else if (className && !hasVal){
  list.push({[className]: [span.getAttribute('data-id')]});
 }
});
console.log(list);
console.log(JSON.stringify(list));

Here is working jsfiddle;

5 Comments

your logic is incorrect - the branch in the OP's code is there to ensure that an empty array exists the first time a particular class is observed so that they can then push the first element into the array associated with that class.
@Alnitak Updated the logic to check for the previously existing keys, it was not clear in the problem statement but i think logic will take only first key value in case there are multiple.
it's still wrong - the OP is trying to create a structure where the class name is used for the key, and where the value is an array of the data-id fields for all elements that have that class. Your code creates a separate list entry for each span.
@Alnitak thank you for your inputs, updated the solution as per the exepcted behaviour, perhaps i misunderstood the question on first two attempts.
@Alnitak please do not downvote an answer before seeing how clear the question is, in my case i did not knew the expected output and neither was it mentioned in the question, so instead of downvoting an answer, please downvote the question first or add the expected output in question so people can provide proper answers.
1

The JSON.stringify function will not serialise string keys added to an array, only the numeric ones.

The trivial fix then is to replace const list = [] with const list = {} and then update the code that uses the results to expect an Object instead of an Array.

More generally, you should reduce the repetition in your code, especially the repeated calls to span.getAttribute('class'):

const spans = document.querySelectorAll('span');
const list = {};

spans.forEach(function(span) {
    const cls = span.className;
    const id = span.getAttribute('data-id');

    list[cls] = list[cls] || [];   // ensure the array exists
    list[cls].push(id);
});

Comments

1

I think you wanted the list to be an object as the way you were trying to access the property of list by the class name.

Also rather than mutating an external object using forEach its better to use Array.prototype.reduce on the NodeList returned from document.querySelectorAll() call:

const spans = document.querySelectorAll('span');   

//With Array.prototype.reduce
const list = Array.prototype.reduce.call(spans, function(acc, span) {
    const attr = span.getAttribute('class');
    const dataId = span.getAttribute('data-id');
    acc[attr] ? acc[attr].push(dataId) : (acc[attr] = [dataId]);
    return acc;
}, {});

console.log(list);
console.log(JSON.stringify(list));
<span class="first" data-id="1" />
<span class="second" data-id="4" />
<span class="second" data-id="2" />
<span class="third" data-id="5" />

4 Comments

re-creating a new array of each pass with .concat is not good practise.
@Alnitak Yes you are right, I am just trying to reduce the mutation of the original array as much as possible.
Pushing an element onto an existing array is far cheaper than having the browser create a whole new array that now contains all the elements and then throwing away the original.
@Alnitak Thank you for the suggestion, I will keep it in my mind. I updated my code

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.