Yeah, I even have a blog on my website! This is the place where I drop short write-ups with tips and tricks, offer old hardware for sale, maybe even share a song with you, my dear audience and (future) clientele.
Blog
How To List User Defined Window Globals with Javascript
There might be occasions where you want to inspect what is defined on the Window object, for instance is jQuery or Google Analytics loaded, or are there third party scripts that pollute your global Window scope?
In that case this script might come in handy. If you run it in your browsers console, it will create an object containing all user defined globals, nicely subdivided by type.
It works as follows: first, we declare 2 arrays, one empty called ‘filteredPropKeys’ and one that hold all the property names from the globals on Window called ‘allWindowPropKeys’. What we also need is an empty object that will hold all the user defined globals, let’s call it ‘customProperties’.
// create an iframe and append to body to load a clean window object
let filteredPropKeys,
allWindowPropKeys = Object.getOwnPropertyNames(win),
customProperties = {
array: {},
object: {}
},
iframe = doc.createElement("iframe");
// hide it just to be sure
iframe.style.display = "none";
// and append to the body
doc.body.append(iframe);
It then creates an iFrame element so we have a clean reference Window object. The iFrame is appended to body, so we can compare the original Window object with the iFrame Window object. Everything that is not defined on the iFrame window object will be added to the ‘filteredPropKeys’ array.
// filter the list against the properties that exist in the iframe
filteredPropKeys = allWindowPropKeys.filter(windowPropKeys => !iframe.contentWindow.hasOwnProperty(windowPropKeys));
When that is done, the cool part happens: We loop through the ‘filteredPropKeys’ array, check the type of the property against Window, and then add it to a corresponding property type on our customProperty object.
// now loop through the keys and make one big object containing all the custom properties
filteredPropKeys.forEach(key => {
const customPropKey = win[key];
const type = typeof customPropKey;
if (type === "object") {
// arrays are objects, too... so a special test is required
if (Array.isArray(customPropKey)) customProperties.array[key] = customPropKey;
else customProperties.object[key] = customPropKey;
} else {
// make a property for the remaining types
if (customProperties[`${type}`] === undefined) customProperties[`${type}`] = {};
customProperties[`${type}`][key] = customPropKey;
}
});
Finally, we log out the result with some added extra’s, the total number of Window globals, the total number of user defined globals, and the user defined globals themselves.
// write the result to the console.
con.log({
numWindowProps: allWindowPropKeys.length,
numCustomProps: filteredPropKeys.length,
customProperties
});
On this website, this will log out something like you see below. Pretty nice, right?
Finally, to keep things clean, we remove the iFrame we just created. And that’s all there is to it!
// clean up
iframe.remove();
Happy coding 😘
How to simulate clicks on non-supported HTML elements
Working on an AB test, I had to write some code that involved using a clone for a clickable element. The original HTML element, an <svg> element, had to be hidden and replaced by a button. What this button should do is simple: hide a pop up on click.
My first thought was, I add a click event listener to that button and just pass that click to the original element like so:
// (this code is what my ab test framework expects to create an HTML element 😉 )
{
tagName: "a",
attributes: {
class: "a-button a-button--size-small a-button--mode-primary a-button--color-red ra-031-pin-weiter-button",
innerHTML: `<div class="a-button__text">Weiter mit gewählten Markt</div>`,
onclick: () => closeButton.click() // simple, right?
},
position: "beforeend",
target: messageContainer
}
Simple! Thank you all those years of experience, this should do the trick, right?
What I did not yet realize however was that <svg> elements do not support the HTMLElement.click() method so running into this error kind of stumped me…:
So then I started looking for a solution. The first thing I found was this, and it looked promising, but somehow did not cut the cake for me:
// click() is a function that's only defined on HTML elements.
// Fortunately it's a convenience function that we
// can implement it ourselves pretty easily like so:
const target = document.querySelector("svg");
target.dispatchEvent(new Event('click'));
Also, since the handler for the original element’s click event was out of reach for me, I could not approach it directly so I just had to find a way to make this simulated click work.
Then I found this piece of code, but when I started using it I discovered that parts of it were deprecated but, great news, now I was getting somewhere! The click actually worked!
const ev = document.createEvent("SVGEvents");
ev.initEvent("click",true,true); // deprecated.. 🙁
target.dispatchEvent(ev);
But.. using deprecated code is something I rather avoid. Looking for an even better solution should not be that hard anymore since I’m looking in the right direction. Then I found this, the final solution to my problem and come to think of it, quite predictable 😉
const dispatchClick = target => {
const ev = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
target.dispatchEvent(ev);
};
Guess when you’re confused by a totally unexpected error, finding the correct solution sometimes takes you on a slight detour…
Adding DOM Elements LIKE A BOSS
In my AB testing Framework I have a method that creates DOM nodes, sets their properties and attributes, and adds them to the DOM for me. Since in 90% of all the tests we run, we need one or more custom elements, I decided to create a function that does all that for me. The requirements were:
- create (one or more) DOM Element(s) by config
- add attributes to element (class, style, innerText/ -HTML, and even events like onclick)
- insert element in DOM relative to a target, or replace that target
- return a reference to the element for later use
OK, let’s write a function that can do all of that – it’s possible to do it in only a few lines of code!
const buildNodesFromConfigArray = nodes => nodes.map(({tag, attributes, position, target}) => {
// create the element
const node = document.createElement(tag);
// iterate through property list,
// match innerText, innerHTML or event attributes (event attributes should be wrapped functions!),
// else just set the attribute
Object.entries(attributes).map(([key, value]) => (/^(inner|on)\w+$/i.test(key)) ? node[key] = attributes[key] : node.setAttribute(key, value));
// [optional] place it in the DOM
if (position && target) (position === "replace") ? target.replaceWith(node) : target.insertAdjacentElement(position, node);
// return it for use in the caller function
return node;
});
As you can see, first we create a DOM element. Then comes a pretty magical line of code if I may say so, we map over the attributes object so we can check these as key-value pair, one by one. If the regex matches on the key, we have to set either innerText or innerHTML, or an event like ‘onclick’ or ‘onmousesomething’ or whatever event you fancy. If it does not, we set an attribute with name ‘key’ and value ‘value’. Finally, if a position and target are set in our config, we add the element to the DOM relative to a target, or replace that target. Now, let’s see this awesome code in action!
// let's create a new stylesheet
const [style] = buildNodesFromConfigArray([{
tag: 'style',
attributes: {
id: "ra-stylesheet",
rel: "stylesheet",
type: "text/css"
},
position: "beforeend",
target: document.head
}]);
We declare an array and use the destructure technique to have the variable(s) immediately available to us. That way we can use it later on in our code. Like so, for instance:
style.append(document.createTextNode(`
body {
background-color: #00ff88;
}
`))
Here you can see the stylesheet added to the DOM. All the properties are set like we specified.
What if we want to add some meta tags to the head of our site? That would look like this. (You could actually skip the variable declaration if all you want is to add these to the head).
const [meta1, meta2] = buildNodesFromConfigArray([{
tagName: "meta",
attributes: {
class: "ra-133-meta",
property: "og:type",
content: "website"
},
position: "beforeend",
target: document.head
}, {
tagName: "meta",
attributes: {
class: "ra-133-meta",
property: "og:site_name",
content: document.location.origin
},
position: "beforeend",
target: document.head
}])
Here’s a final example, where we won’t be needing the elements later in our code, we just want them added in the DOM:
buildNodesFromConfigArray([{
tagName: "div", //
attributes: {
class: "first",
innerText: "My Paragraph",
onclick: (event) => {
// make sure the value here is an actual function!
alert(event)
}
},
position: "beforebegin", // use insertAdjacentElement position parameter, or replace
target: document.querySelector("#someElement")
}, {
tagName: "div",
attributes: {
class: "second",
},
position: "replace",
target: document.querySelector("#someOtherElement")
}]);
OK, now you know how to create one or more DOM elements LIKE A BOSS. Contact me if you want to know more!
Next time, I’ll share a trick I posted on Twitter a while ago, how I exclude IE from my tests, the recoveryArea way!
Happy coding 🙂