My favourite bits of JS.
You know when you sidle up to someone in the street or accost them at a party, and ask: "What's your favourite bit of JavaScript?"
Well... the reaction you get is why I restrict this sort of behaviour to LinkedIn.
querySelectorAll
Here's the scenario - the data layer is sparse / non existent. You need to get some data off a page. Sure you've got your classics like getElementById() and so on, but what if the elements on the site don't have a an ID? I like to use querySelectorAll, as I find it super flexible.
Let's go to loophorizon.com and look at this image-of-a-person-I-don't-know half way down the page:
The image does't have an ID (and let's pretend it doesn't have a clearly defined class for the purpose of this example); what if I want to pull the image source?
querySelectorAll() to the rescue!
document.querySelectorAll('[id="w-node-_6f5fdede-377f-4852-6bd7-dfa14675aae4-ee8628a5"]');
But wait - that's the div element that contains the image! Never fear...
document.querySelectorAll('[id="w-node-_9eb7db44-e590-4e8e-7625-bb378dfdb91b-ee8628a5"]')[0].getElementsByTagName('img')[0].src;
Ok, so you could use querySelector() instead of querySelectorAll() (and avoid all that [0] shenanigans) if you know the parent element has a unique ID. And OK, you could use getElementByID() and then perform the same drill down, but here's the thing - you can use querySelectorAll() on any element attribute!
Let's look at the Loop Horizon logo (and again, let's pretend it does't have a clearly defined class for the purpose of this example):
I could use any of the following to grab the image source:
document.querySelectorAll('[data-w-id="cece7bb6-e93f-1599-2f86-22ac2b9f0a7d"]')[0].getElementsByTagName('img')[0].src;
//OR
document.querySelectorAll('[aria-current="page"]')[0].getElementsByTagName('img')[0].src;
//OR
document.querySelectorAll('[style="will-change: opacity; opacity: 1;"]')[0].getElementsByTagName('img')[0].src;
And here's the real key as to why I love querySelectorAll() - it returns a NodeList (all the [0] shenanigans), and that means you can do stuff like this:
document.querySelectorAll('[class^="container-"]');
Yep, that's regex. Some of those IDs are a bit long winded, or maybe you just want to pull all elements within a certain section of the page and see what's in there; the fact that querySelectorAll() can use regex and the fact that it returns a NodeList, gives that flexibility I talked about; return as little or as much as you like, and then do what you want with it!
Array functions
That brings be nicely onto my next favourite bit of JS. Array functions.
You've got your node list, but how do you get the information you want from it? It's all well and good using document.querySelectorAll('[data-w-id="cece7bb6-e93f-1599-2f86-22ac2b9f0a7d"]')[0].getElementsByTagName('img')[0].src; (for example), but what if the 'img' isn't in position [0]? Or what if there's more than one 'img' being used?
Let's turn our NodeList into an array, so we can start to get funky with it!
Array.from(document.querySelectorAll('[class^="container-"]'));
Now it's an array, we can filter down to the bits of that array that contain images!
领英推荐
Array.from(document.querySelectorAll('[class^="container-"]')).map(function(key){return key.getElementsByTagName('img')});
//OR
Array.from(document.querySelectorAll('[class^="container-"]')).map(key => key.getElementsByTagName('img'));
//I prefer the verbose first version just because it makes more sense in my head... plus it works in ES5. I know... I'm a dinosaur.
Ah the map() function - it creates a new output array from the input array (and you have to input an array[]), based on the conditions stipulated.
I love it... a lot. Maybe too much, but I'm only human.
But oh no though! We get a bunch of HTML objects...
Well wrap your eyes around this...
Array.from(document.querySelectorAll('[class^="container-"]')).map(function(key){return Array.from(key.getElementsByTagName('img'))});
//OR
Array.from(document.querySelectorAll('[class^="container-"]')).map(key => Array.from(key.getElementsByTagName('img')));
That's right - just use the Array.from() function again, nested within the map(), to return nested arrays instead.
Now we can use another array function to filter out any empty arrays returned.
Array.from(document.querySelectorAll('[class^="container-"]')).map(function(key){return Array.from(key.getElementsByTagName('img'))}).filter(function(key){return key.length > 0});
//OR
Array.from(document.querySelectorAll('[class^="container-"]')).map(key => Array.from(key.getElementsByTagName('img'))).filter(key => key.length > 0);
So that's sorted that out.
Now to return the image source - for this, we need to tweak the getElementsByTagName('img') code to return the source rather than the full image object - using another array function of course, given there may be more than one image per element.
Array.from(document.querySelectorAll('[class^="container-"]')).map(function(key){return Array.from(key.getElementsByTagName('img')).map(function(key){return key.src})}).filter(function(key){return key.length > 0});
//OR
Array.from(document.querySelectorAll('[class^="container-"]')).map(key => Array.from(key.getElementsByTagName('img')).map(key => key.src)).filter(key => key.length > 0);
But alas there are some nested arrays. We can sort that using the concat function though.
[].concat.apply([],Array.from(document.querySelectorAll('[class^="container-"]')).map(function(key){return Array.from(key.getElementsByTagName('img')).map(function(key){return key.src})}).filter(function(key){return key.length > 0}));
//OR
[].concat.apply([],Array.from(document.querySelectorAll('[class^="container-"]')).map(key => Array.from(key.getElementsByTagName('img')).map(key => key.src)).filter(key => key.length > 0));
And now we have all the image sources associated with the "class" beginning with "container-".
Amazing... but kind of useless, as it stands. What shall we do with it?
Let's pretend that every single one of the images relates to a promotion, and wrap this whole ordeal up by pushing each image source onto the dataLayer as part of a view_promotion event to get hypothetically collected by GA4 - https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtm#view_promotion.
[].concat.apply([],Array.from(document.querySelectorAll('[class^="container-"]')).map(function(key){return Array.from(key.getElementsByTagName('img')).map(function(key){return key.src})}).filter(function(key){return key.length > 0})).forEach(function(key){dataLayer.push({event: "view_promotion",
ecommerce: {
creative_name: key,
creative_slot: "example_position",
promotion_id: "fake_id",
promotion_name: "fake_name",
items: []
}
})});
Obviously, this is over-simplified; in this hypothetical scenario it's likely that unique promotion detail (for each image / group of images) would be available somewhere within the various elements returned by the original "class"-beginning-with-"container-" querySelectorAll() function. And you could therefore (of course) tweak the various map functions to pull this data out at the correct stage... and maybe use one of my other favourite bits of JavaScript - Object.assign() - by first creating a core promotion level object per element returned, and then iterating through the list of image sources within each element to Object.assign() it back to the core object.
But this example goes to show how another simple array function - forEach - can be used to iterate through the array of image sources generated and do something usefuly with them.
END.
We all have JQuery to thank for querySelectorAll
Principal App Developer at Interactive Investor
1 年As a developer, I promise to keep inserting random data attributes onto the DOM, only to have them randomise on each deploy. We can't make it too easy for you ?? (Love a good .map)
Founder, SaaS Pimp and Automation Expert, Intercontinental Speaker. Not a Data Analyst, not a Web Analyst, not a Web Developer, not a Front-end Developer, not a Back-end Developer.
1 年Array functions chaining, closely followed by ternary operators. They have revolutionised how I code in Javascript. You might be able to replace this: [].concat.apply([],Array.from(document.querySelectorAll('[class^="container-"]')).map(someFunction) ... with: [].map.call(document.querySelectorAll('[class^="container-"]'), someFunction I know Array.from, it's a fairly recent addition to Javascript, but I almost never used it. I use [].map.call instead.