• Skip to main content
  • Skip to secondary navigation
  • Skip to footer

recoveryArea

Content Management for Everyone!

  • About me
  • Blog
  • AB Testing
  • WordPress
  • Portfolio

Archives for June 2021

Adding DOM Elements LIKE A BOSS

3 June 2021 By iPasqualito

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.

inspect element shows the sheet where we expect it.

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 🙂

Filed Under: AB Testing, JavaScript

My Secret to Super Fast AB Test Loading

2 June 2021 By iPasqualito

When I am testing elements that take some time to load, the last thing I want is a flash of un-styled content, or see the unchanged element jump into its changed state. Since a few years, browsers have a great API built in that I use to achieve super fast loading of my test code: Mutation Observer. (link opens new tab)

In this post I’ll explain how I use this API to my advantage.

Make sure your script is loaded as soon as possible. It’s OK if you load it asynchronously, but you want it to be available as the first piece of JS the page is loading.

Here’s the function I use to observe when an element gets added to the DOM. I basically wrap a querySelector in a MutationObserver. The latter will fire upon every DOM mutation. The querySelector will then test for the existence of my element. If that returns true, I disconnect the observer if I don’t need it anymore. Finally, I run the callback function that was passed as the second parameter.

const observeDOM = (config, callback) => {
    // use a regular function so `this` refers to the Mutation Observer
    new MutationObserver(function() {
        const element = document.querySelector(config.selector);
        if (element) {
            // disconnect if you don't need it again
            if(config.disconnect) this.disconnect();
            // test and run the callback function
            if(typeof callback === "function") callback(element);
        }
    }).observe(config.parent || document, {
        subtree: config.recursive,
        childList: true
    });
}

I use a ‘normal’ function keyword on the Mutation Observer function because if I don’t, I won’t be able to disconnect it if that is what I want. This will then refer to the Window object and not the MutationObserver instance.

const config = {
    selector: 'li.timeline-item', // what element are we looking for?
    parent: document.querySelector("ul.timeline"), // narrow down search scope if possible...
    recursive: true, // look at descendant elements too
    disconnect: false // set to true when one hit is enough
}

In the config file above you can see that I am observing an unordered list for additions of list items. Since disconnect is set to false, the observer will fire on every mutation and do the element test again. Note: You can prevent triggering on the same element over and over again by adding a class (.found) to the element as soon as it’s found, and change your selector accordingly: a li.timeline-item:not(.found) selector does that trick just fine.

// run this function when the element is found
const callback = console.log;

Here is a simple example of a callback function you can run when you have a hit. In your case you probably want to kick off your AB test code. See what I did there?

// kickoff mutation observer
observeDOM(config, callback);

Last but not least, you want to start observing by calling your function with config and callback parameters.

In the next post, I’ll show you how to create DOM elements – recoveryArea style!

Happy coding!

Filed Under: AB Testing, JavaScript

Footer

Office

recoveryArea
Nijverheidstraat 11-1
7511 JM Enschede

KvK: 65594940

Goals

Let's make a more elegant, easy to use internet, accessible for everyone. Let's move forward to an empathic, secular, sustainable future, enabled through science and technology.

Read my full profile

Get in touch

  • GitHub
  • LinkedIn
  • Twitter

Copyright © 2023 · From recoveryArea with

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Cookie settingsACCEPT
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Functional
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytics
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Others
Other uncategorized cookies are those that are being analyzed and have not been classified into a category as yet.
SAVE & ACCEPT