1

Hey guys need a bit of help here.

I'm trying to find all class names that start with the same naming convention in the DOM and put them all in to an array.

so for example.

<div class="userName_342">John</div>
<div class="userName_366">Doe</div>
<div class="userName_234">Bob</div>
<div class="userName_873">David</div>

I need help making the above code with a little bit of JavaScript to the array below.

 var classArr = ["userName_342","userName_366","userName_234","userName_873"];

Any help on how to even get started would be greatly appreciated.

Thank you.

BaconJuice
  • 3,739
  • 13
  • 56
  • 88
  • 1
    Will the `div` elements ever hav *other* classes on them as well? E.g., `class="userName_342 foo"` or `class="bar userName_342"`? – T.J. Crowder Sep 29 '14 at 15:31
  • You could try something like this http://stackoverflow.com/questions/2178416/jquery-using-starts-with-selector-on-individual-class-names assuming you're using jQuery, $("div[class^='userName-']") to get the elements, then if you just need the class names you could loop over the results from there. It might be helpful if we knew what you were going to do with the array of class names. – Shriike Sep 29 '14 at 15:33
  • possible duplicate of [css selector by class prefix](http://stackoverflow.com/questions/3338680/css-selector-by-class-prefix) –  Sep 29 '14 at 15:33
  • 3
    Don't use class names like that. What is the objective? If you really want to do this, then it's just `document.querySelectorAll('div[class^="userName_"], div[class*=" userName_"]')`, then gather the classes from those elements. –  Sep 29 '14 at 15:34
  • 3
    What are you actually trying to do here? How is the HTML generated in the first place? Because it seems like a really silly design. If those are supposed to be unique (i.e. there is only one element with a class of `userName_342`) then they ought to probably be `id`s rather than classes. If you want to keep them as classes, then it would probably be easier to add another class that you can select, so `
    ` which you might want to do if using ids anyway. If you can't control the HTML, then the other suggestions using `class^='username'` might be the only way.
    – Matt Burland Sep 29 '14 at 15:37

2 Answers2

7

Assuming the relevant class is always the only class on those elements, you can do it with an "attribute starts with" selector combined with Array#map:

var list = document.querySelectorAll("div[class^=userName_]");
var classArr = Array.prototype.map.call(list, function(div) {
    return div.className;
});

Matt Burland points out that that will return an array with duplicate entries if there are multiple elements with the same class. Two ways to address that:

Array#reduce, but this use of it isn't very efficient:

var list = document.querySelectorAll("div[class^=userName_]");
var classArr = Array.prototype.reduce.call(list, function(array, div) {
    if (array.indexOf(div.className) === -1) {
        array.push(div.className);
    };
    return array;
}, []);

...or using a temporary map:

var list = document.querySelectorAll("div[class^=userName_]");
var map = {};
Array.prototype.forEach.call(list, function(div) {
    map[div.className] = true;
});
var classArr = Object.keys(map);

Array#map, Array#reduce, Array#forEach, and Object.keys are all ES5 features, but if you need to support older engines, they can all be shimmed.

querySelectorAll is available on all modern browsers, and IE8.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • The standard idiom for `unique` is simply `function unique(arr) { return arr.filter(function(elt, idx, arr) { return arr.indexOf(elt) === idx; }); }`. –  Sep 29 '14 at 15:44
  • @torazaburo: I spend a *lot* of time kicking around a lot of JavaScript, and have seen that perhaps once. It's not an efficient design, nor would it be efficient to build the entire array first and then apply that `unique` to it. – T.J. Crowder Sep 29 '14 at 15:47
  • @T.J.Crowder It's of exactly the same `O(n^2)` as your reduce algorithm, which is essentially also the underscore algorithm. The array is already built, so applying filter to it is no more work than applying reduce. However, the hashing approach is of order `O(n)` and is certainly optimal at least for larger collections. –  Sep 29 '14 at 16:25
  • @torazaburo: Well, not *quite* the same, because my `reduce` approach is searching through a smaller array (at least initially because I'm building it up, and possibly throughout). E.g., if there are 100 elements but 10 unique names, at worst I'm searching through a 10-element array, not a 100-element array. But then again, unless there's a specific performance problem, it's not like it matters anyway. :-) – T.J. Crowder Sep 29 '14 at 16:32
  • @T.J.Crowder: Yes, it doesn't (matter). So let's not waste too much time on this. What I like about this solution is that it is elegant and readable. But it is wrong to say that my solution searches through all 100 elements; it searches (via `indexOf`) only until the first match, which except in degenerate cases is unlikely to be much further into the original array than the match in the resultant array you are searching. By my computation, your algorithm is `O((n-1) x n / 2)`, and mine is `O(n x (n+1) / 2)`, for a difference of `N`, or 2% in the case of 100 elements. –  Sep 29 '14 at 16:43
  • @torazaburo: Good point! It's really only the "miss" scenario where it matters (if it even does). – T.J. Crowder Sep 29 '14 at 16:48
0

Here is an example of a function to find based on class name.

http://codepen.io/justindunham/pen/nhJsD

document['getElementsByRegex'] = function(pattern){
   var arrElements = [];   // to accumulate matching elements
   var re = new RegExp(pattern);   // the regex to match with

   function findRecursively(aNode) { // recursive function to traverse DOM
     //console.log(aNode);
      if (!aNode) 
          return;
      if (aNode.className !== undefined && aNode.className.search(re) != -1)
          arrElements.push(aNode);  // FOUND ONE!
      for (var idx in aNode.childNodes) // search children...
          findRecursively(aNode.childNodes[idx]);
   };

   findRecursively(document); // initiate recursive matching
   return arrElements; // return matching elements
};

Based fully on this answer Select div using wildcard ID

Community
  • 1
  • 1
jdunham
  • 439
  • 3
  • 6
  • 1
    This was a really good solution about five years ago, before `querySelectorAll` and starts-with attribute selectors. –  Sep 29 '14 at 15:46
  • This seems highly inefficient, because you search *every* element for a child matching a pattern. It's better to use the selector APIs, such as `querySelectorAll` – Alessandro Vendruscolo Sep 29 '14 at 15:47
  • It's not a great solution, but a solution none the less. Depends on your level of browser support I suppose. – jdunham Sep 29 '14 at 15:51