Article
0 comment

this.domElement is your new document object in SPFx web parts development

You are familiar with the JavaScript method like ‘document.getElementByID’. Since HTML5 the Document Object Model has more to offer than return an element by its ID on a document.

Use this.document instead of document

Use this.document instead of document

I case of web part development you should forget that the document object inside and HTML exists instead you should request HTML elements differently.

document vs this.domElement

When you use any method on the document JavaScript object, you always get the complete document or page. Raising a query against the document can affect the overall experience.

Instead, in web parts or extension, you have a domElement object, and it contains only the relevant content.

If you query, for example, all elements with the class name ‘.myWebPart’. The behaviour is different depending on what you use.

The query document.querySelectorAll('.myWebPart') will return all HTML elements on a page. In case you have the web part more than once on a page you get all — even elements in other web part instances. Besides, bulk modification of objects has an impact on all elements.

Instead a query using this.domElement.querySelectorAll('.myWebPart') will return only all classes in your web part. So it is safer to use this method in all cases.

To show you this behaviour, I set up a simple web party only including a container element. The web part itself exists two times on the page.

Base web part setup for our test

Base web part setup for our test

Now let’s add some queries using both methods.

// query document
console.debug(
  "document.querySelectorAll('.' + styles.container)",
   document.querySelectorAll('.' + styles.container).length);

// query this.domElement
console.debug(
      "this.domElement.querySelectorAll('.' + styles.container)",
      this.domElement.querySelectorAll('.' + styles.container).length);

This code inside the render method of a web part produces the following output.

Query results of queries agains document vs this.domElement

Query results of queries agains document vs this.domElement

The first line query all container and finds only one instance of the container, because it is the first web part that queries against the complete DOM. One container gets found on the second line of the output. There is only one container element in the web part, so that was expectable too.

The third line again uses a query on the document level. Now that the second web part executed this code, it finds two elements using the same class, which is an unwanted behaviour.
The fourth line again, executed by the second web part, only uses ‘this.domElement’. This query again was executed against the web parts HTML only, and only the container of the second instance got returned.

What if I use jQuery?

Mainly if you use jQuery, you shouldn’t use ‘document’ under any circumstances for three reasons. The first reason again you query against the complete page, where you like to query only against your web part. Second, especially every time you register for example $(document).ready you register an event listener on the document, and there is already a way to many registered there. You don’t like to interfere with the one Microsoft provided.
The third reason is that $ or jQuery mostly represent the document object too.

Yeah but I need to run $(document).ready!

Long answer short you don’t need to use $(document).ready in web parts. In the web part, you have a render method, which is the equivalent of $(document).ready in jQuery.
Lets put this to a simple test again with our two instances of the same web part from before.

public constructor() {

  super();

  $(document).ready(()=>{
    console.debug(Date.now(), '$(document).ready executed');
  })

}

So we added a constructor to the web part and ran document ready there. In the debug message we output the exact time as ticks the code got executed.

Besides, let us add the same code, just using this.domElement to the render method. It always needed to be in the render method because on time the constructor gets called this.domElement do not exist.

public render(): void {

  this.domElement.innerHTML = `
    <div class="${ styles.helloWorld}">
      <div class="${ styles.container}">
      I am a container
      </div>
    </div>`;

  $(this.domElement).ready(()=>{
    console.debug(Date.now(), '$(this.domElement).ready executed');
  });

}

The debug information in the browser console looks like this.

jQuery document ready vs. this.document ready

jQuery document ready vs. this.document ready

There is one exciting thing to see here. Both methods get executed at the same time (ticks), but why is this? In general, every web part is a React component, so when it is already on the page, the document is already ready. So there is no need to execute the code this way at all. Both methods get executed twice.

Query elements using jQuery

To keep this short, let us query the container elements in the render method using the following code to output some debug information.

// unsafe way to query elements
console.debug(
  "$('.'+styles.container).length",
  $('.' + styles.container).length
);
// safe way to query jquery
console.debug(
  "$(this.domElement).find('.' + styles.container).length",
  $(this.domElement).find('.' + styles.container).length
)

The critical elements in this code are how you query elements. In the case of jQuery, you execute this.

$('.' + styles.container)

So $(...) is just an abstracted way of executing ‘document.querySelectorAll’. So with jQuery, you always query for the document, and you don’t want to do this. You like to stay in the boundary of your web part. To do this properly, you should run the following query against the selector.

$(this.domElement).find('.' + styles.container)

Brocken into pieces the $(this.domElement) creates a new jQuery object using the HTML from your current web part only. The second part ‘find’ only specifically search for the ‘styles.container’ classes inside your web part only.

The result, in this case, is like before in the native implementation of ‘document.querySelectorAll’ vs ‘this.domElement’.

Queries against the document vs. this.domElement

Queries against the document vs. this.domElement

Again line one and two are coming from the first web part instance, and in the case of jQuery document using only one instance was found.
Line three and four is the output of the second instance of the web part. Again $ is equal to document, which means you find all class instance on a page instead of only those in the web part.
The fourth line shows the safe way to use jQuery and limit the code only to the web part instead of accessing the overall page.

Forget document and just use ‘this.domElement’

When you write your web parts using ReactJS, don’t use jQuery. You honestly don’t need it. ReactJS gives you all you need.

For all other web, parts make sure you never query the whole document and make your code work only in your web part instead.

Avoid queries against the page DOM and stay in your web part. The advantages are clear. It is safer and better performant, especially when you have many web parts on the same page.

Leave a Reply

Required fields are marked *.


This site uses Akismet to reduce spam. Learn how your comment data is processed.