Joe
Joe

Reputation: 720

DRY up htmlCollection to Array calls

I have a function that is currently using the .getElementBy... DOM calls in JavaScript.

var $ = function (selector) {
  var elements = [];

  var lastSelector  = selector.substring(selector.search(/[^#.]+$/), selector.length);

  if(selector.includes('#') !== true || selector.includes('.') !== true) {
    elements.push(document.getElementsByTagName(lastSelector));
    elements = Array.prototype.slice.call(elements[0]);
  }

return elements;
};

There are a number of other if statements in the function using the code:

elements.push(document.getElementsByTagName(lastSelector));
elements = Array.prototype.slice.call(elements[0]);

or

elements.push(document.getElementsByClassName(lastSelector));
elements = Array.prototype.slice.call(elements[0]);

Ideally i'd like to DRY up the repeated:

elements = Array.prototype.slice.call(elements[0]);

but I cannot define it before the if statements because elements has not yet been populated. It therefore tries to run the code on an empty array and errors.

Any suggestions?

Upvotes: 0

Views: 420

Answers (2)

Joe
Joe

Reputation: 720

Certainly consider @marat-tanalin answer. In the case where using querySelectorAll() is not an option, the following worked for me, thanks @master565 for the help:

To start, wrapping the lines:

elements.push(document.getElementsByTagName(lastSelector));
elements = Array.prototype.slice.call(elements[0]);

in a function:

function pushByTag(selector) {
  elements.push(document.getElementsByTagName(selector));
  elements = Array.prototype.slice.call(elements[0]);
}

Dried things up considerably. Then setting a variable for the if argument helped a lot:

if(selector.includes('#') !== true || selector.includes('.') !== true)

became:

var noClassOrId = selector.includes('#') !== true || selector.includes('.') !== true;

Both these refactors allowed me to single line my if statement in to something I'd argue was fairly readable:

if (noClassOrId) pushByTag(lastSelector);

Upvotes: 0

Marat Tanalin
Marat Tanalin

Reputation: 14123

Instead of using a home-brew limited function for selecting elements by a selector, you could just use the standard querySelectorAll() available in all browsers including IE8+.

As for converting an array-like object (e. g. a DOM collection) to a real Array (what Array.prototype.slice.call() is used for in your code), I use the following function:

var arrayFrom = function(arrayLike) {
    if (Array.from) {
        return Array.from(arrayLike);
    }

    var items;

    try {
        items = Array.prototype.slice.call(arrayLike, 0);
    }
    catch(e) {
        items = [];

        var count = arrayLike.length;

        for (var i = 0; i < count; i++) {
            items.push(arrayLike[i]);
        }
    }

    return items;
};

or its following simplified version if browsers not supporting passing a non-Array argument to Array.prototype.slice.call() (IE8- if I recall correctly) don’t matter:

var arrayFrom = function(arrayLike) {
    return Array.from
         ? Array.from(arrayLike);
         : Array.prototype.slice.call(arrayLike, 0);
};

Upvotes: 1

Related Questions