Reblog: Optimize jQuery selectors

I have always found jQuery selectors very complex and have never quite understood how to use them properly.   I just found this article from the website Optimize your jQuery selectors for best performance.  It is a fantastic article and full credit goes to them.  The only reason I am copying it here is because I want to make sure that the information stays in my toolbox.   Please check out their page to see more information.


• ID selector $(‘#elementID’)
• Tag selector $(‘p’)
• Class selector $(‘.CSSClass’)
• Attribute selector $(‘[type=”text”]‘)
• Pseudo selector $(‘:visible’)

Always use ID selector if possible

Accessing DOM is a very expensive operation, so it’s beneficial to minimize effort and time. As we all know, the ID attribute is unique for each HTML element on the page. in JavaScript document.getElementById() is the function that one would use to select the HTML element. It’s the fastest and the best way to select the element because it directly maps to the element. jQuery is a library written on top of JavaScript, which means that it internally calls the JavaScript functions to do the job. When you use ID as a selector in jQuery, it internally calls document.getElementById(). To select the HTML element with elm as ID, the jQuery code looks like this: $(“#elm”);
This method works well in all browsers, so it’s a good choice if you are using an older browser.

Cache your selector

Caching improves the performance of the application. You can cache data or objects for better performance. You can also cache your jQuery selectors for better performance using the ID as your selector (as mentioned in the previous tip). When you don’t cache the selector, jQuery must rescan the DOM to get the element. You may not feel the difference in small applications, but with large applications this becomes very critical. Let’s look at the process of caching objects in jQuery. The following simple line of code caches the selector and stores it in the $elm variable:

var $elm = $("#elm");

Now use the $elm variable instead of using the jQuery ID selector. Like this:

var $elm = $("#elm");
 $elm.addClass(‘dummy’);

Remember, the scope of a variable is limited to where it is defined. If it is defined as a global variable then you can use it anywhere in the jQuery code, but if it is inside a method the scope will be limited to that particular method only. Global caching is useful for elements which are frequently used in the code.

Define a context with jQuery selectors

By default, jQuery selectors perform their search within the DOM. But while defining the selector, you can pass the context, which limits the searching range for the jQuery selector. In other words, you are instructing jQuery to look inot the context rather than beginning the search from the document root. This helps in speeding up the searching process which will definitely improve the performance. Passing the context is optional, but you should use it whenever you can.
Enough theory! Let’s take a look at a practical example. Consider the following HTML code:

<div id="”parent1”">
<div class="”child”"></div>
<div class="”child”"></div>
<div class="”child”"></div>
</div>

If you want to select all the div element with child class you can use the following jQuery code:

var $elm = $(".child");

The above code will search for elements with child class starting from the document root. It can be optimized by passing via an alternate selector. Like,

var $parent = $("#parent1");
 var $elm = $(".child", $parent);

This limits the search range, so now searching is limited to the div with ID parent1. The context argument can be a DOM element, a document, or a jQuery object. If you pass context, then jQuery will internally use the find method to retrieve the elements. So the above code is internally converted to:

var $elm = $parent.find(".child");

jQuery first checks if the passed context is a jQuery object. If it is, it calls the find() method on the given context. If the given context is a DOM element, it first converts it to a jQuery object and then executes the find() method. As such, it is better to pass the object as context instead of the DOM element because it will reduce the conversion time.

Don’t repeat your selectors

As mentioned earlier, you can cache the jQuery selectors which prevents you from repeating your selector. jQuery also offers chaining, which allows you to chain multiple methods in a single call. Consider the following jQuery code:

$("div").css("color", "red");
 $("div").css("font-size", "14px");
 $("div").text("New Text!");

Below is the first optimized version using caching:

var $div = $("div");
 $div.css("color", "red");
 $div.css("font-size", "14px");
 $div.text("New text goes here!");

Next is the second optimized version using chaining:

$(“div”).css({“color”, “red”, “font-size”, “14px”}).text(“New text goes here!”);

Use class selector wisely

jQuery also provides a CSS class selector which allows you to select elements with a particular CSS class. This is again a very useful and popular selector as it allows you to select multiple elements at once. However, you have to be cautious while using class selector. The following jQuery code will select all the elements with “dummy” class applied to them:
$(“.dummy”)

In this case, jQuery has to scan the complete DOM to discover elements with dummy class. As mentioned earlier, traversing DOM is a very expensive process so it’s always better to minimize this effort. In this case, you can pass a tag name to reduce the searching scope. Like this:

$("div.dummy");

The above code tells jQuery to only give me the DIV elements with dummy CSS class applied. Though the class selector works quite well in modern browsers, older browsers have performance issues with class selector. That being said, it’s not always a good choice to add a tag name with class selector. Why?

In this example, jQuery will first search for all elements with class dummy and then filter records to only return those elements that are div elements. So when the dummy class is only meant for div elements, you need to specify the tag name in order to add an extra step of filtering. Keep in mind that this is only useful when the CSS class is applied to different HTML elements like span, paragraph and div.

Be very specific about selectors

Did you know that jQuery selectors are executed from right to left? In other words, the last selector will be executed first when there are multiple selectors. Consider the following example:

$("div.parent .child");

In this example, jQuery will first search for all elements with class child (last selector executed first) and then apply a filter to return only those elements which are div elements with a parent class applied. This is the optimized version:

$(".parent div.child");

Here we are more specific on the last selector, which helps to speed up performance!

Look for an alternative for the Pseudo selector

Pseudo class selectors are CSS selectors with a colon preceding them. They are also available with jQuery – :visible, :checked or :first. Pseudo selectors are useful for selecting elements with different states, or a particular element from a set of elements, but they are slower compared to other jQuery selectors. You should either find a way to replace the pseudo selector or be very specific in using it. Let’s take a look at examples of both. The following jQuery code selects all elements which are visible:

$(":visible");

You can be more specific here. Like,

$("input:visible");

or even better,

$("form").find("input:visible");

Pseudo selectors like :first, :last and :eq allow you to select a particular element from a set of elements. As an example, the following jQuery code will select the first table row using the :first pseudo selector.

$("tr:first")

The above code can be replaced with better performing code, but first you need to cache the selector (table row in this case).

var tRows=$('tr');

Since jQuery stores this as an array, we can take advantage of it. To get the first table row:

var firstRow=$(tRows[0]);

To get the last element (:last),

var lastRow = $(tRows[tRows.length - 1]);

Or to get any nth row (:eq(n)),

var nRow=$(tRows[n]);

 

Conditional JavaScript in MVC Razor Statement

I had an odd case where the Model for a MVC Razor page may or may not be null.   If data exists we need to do some extra javascript on the page that doesn’t get run at any other time.   I had problems figuring out how to check for a “null” Model using javascript.   This StackOverflow post had the example that finally showed me a nice elegant way of writing my code.  Here is another SO post with additional information that uses the <text> option.

    var jsonData = null;
    @if(Model != null)
    {
      @:jsonData=@Html.Raw(Json.Encode(Model.ServerDataItemList))
    }

    koVM = new ScheduledEmailVM(jsonData);
    ko.applyBindings(koVM , document.getElementById("ko_div_id"));

Bootstrap Modal Scrolling issue

We have a complex knockout page where we have a Bootstrap Modal that opens up and then we open a simple BootstrapDialog modal on top of it.   When the BootstrapDialog modal is closed the initial Modal will no longer scroll with the page.   The solution that worked for me was as simple as this.  If this solution doesn’t work there were a few more complex solutions on the referenced page.

.modal { overflow: auto !important; }

Code Credit goes to jofftiquez on Github

#TINYMCE: PAGELOAD FLICKERING V2

So a while back I wrote this post: #TinyMCE: Fixing Pageload Flickering

Well now I have found a problem with it.  When you load the page with the textarea hidden the plugins don’t seem to bind properly.  I had to come up with a new way to do the same thing.  Here is my solution.  Not ideal but seems to work fine so far.

Notice that the ‘display:none’ is now gone and the size is now set to a size so that the textarea is not visible on the page. Although, there is a small remnant left there. Not sure I like it but I’m not sure how to hide it.

@Html.TextAreaFor(m => m.ContentHtml, 
                       new { @class = "tinymce", maxlength = 5000, 
                       @style = "height:1px; width:1px; " })

Now once TinyMCE is properly initialized I manually set the iframe size to be the height and width I want.

$('textarea.tinymce').tinymce({
  script_url: '/scripts/tinymce/4.0.25/tinymce.min.js',
  theme: "modern",
  toolbar: "bold italic ",
  paste_auto_cleanup_on_paste: true,
  init_instance_callback: function (editor) {
     $(editor.getContainer()).find("iframe").css("height", "300px");
     $(editor.getContainer()).find("iframe").css("width", "100%");
  }
});

#TinyMCE: Style and Script Elements

We have begun using TinyMCE as the primary editor in our CMS system.  Some of the functionality is to store pages that are later rendered as html on websites.  This means that often our designers are adding custom javascript and css styles to pages.  In our most recent release we found that when we switched to TinyMCE we lost the functionality to add custom scripts and styles to the pages.  After a mad dash we found that adding the following code to the TinyMCE editor allows the user to enter <style> and <script> elements via the Source Code window.   TinyMCE does add comments around the code that is added but they do not affect the functionality.

TinyMCE Initalization Settings

//Allow html script, style, etc tags to work in the editors
extended_valid_elements: 'pre[*],script[*],style[*]', 
valid_children: "+body[style|script],pre[script|div|p|br|span|img|style|h1|h2|h3|h4|h5],*[*]",
valid_elements : '*[*]', 

TinyMCE Example Input

<script type="text/javascript">
$(function () {
    Console.log('test');
  });
</script>



<style>
h2 {
        color: #FF0000;
    }
</style>


TinyMCE Example Output

<script type="text/javascript">// <![CDATA[ $(function () { Console.log('test'); }); // ]]></script>


<style><!-- h2 { color: #FF0000; } --></style>


Embedding a Google Font

When working on websites there are occasions when the designers give you a design with some crazy fonts.  These look awesome but designers usually just reference the Google Font api website.  This is great for them but can degrade the performance of your website.  We prefer to download the fonts and run them locally.  Because I don’t do this very often I actually had to try to figure it out again.  It took a half hour to figure out so I need to write it down for next time!

When you download a webfont from Google Fonts it only gives you the .tff file.  This is useful when running on my machine but not so useful when trying to embed it into a website.

To embed it first download the .tff file from Google.

Then go to fontsquirrel and generate all the files you need.  It will create all the different types as well as an awesome page that shows examples of the font in use.  It created: .eot, .svg, .ttf, .woff, .woff2 files.

Done.  Now you have all the files you need to properly embed a Google Font in your website.

Update (1/10/2017):  When we pulled in some new fonts and pushed it to our DEV testing system we found that the new fonts were throwing errors.  After a bit of testing I found that we needed to add the following lines to our web.config file.  Here is the SO question that described the fix.

<remove fileExtension=".woff2" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />

#tinymce and #xeditable

headlineI am working on a project with some fairly complex editable fields in popups requirements.  I started looking around for a library that would package up the popups so that I can use them without having to start from scratch.  I came across the x-editable library which works very smoothly and handles most of what I need it to.  One of the requirements is that the textarea in the popup need to be a tinymce editor.  Here is my solution to hooking them up.  As usual the code is fairly simple and elegant once it works, but getting it to work took far longer than I anticipated.

Html:

<div>
  <span>Headline:</span>
  <a href="#" id="puHeadline"><i class="fa fa-edit"></i></a>
  <div id="textGoesHere"></div>
</div>

JavaScript:

$(function () {
  //----------------------------------------------------------------
  // Demo popover edit with tinymce
  $.fn.editable.defaults.mode = 'popup'; //toggle `popup` / `inline` mode

  $('#puHeadline').editable({
    pk: 1,
    type: 'textarea',
    title: 'Headline:',
    placeholder: 'Enter your headline...',
    placement: 'right',
    showbuttons: 'bottom',
    inputclass: 'tinymce',
    validate: function(value) {
      if($.trim(value) == '') {
        return 'This field is required';
      }
    },
    display: function (value) {
      $('#textGoesHere').html(value);
    },
  });

  $('#puHeadline').click(function() {
    setupTinyMCE();
  });

  function setupTinyMCE() {
    // TinyMCE functionality
    $('.tinymce').tinymce({
      // Location of TinyMCE script
      script_url: '/scripts/tinymce/4.0.25/tinymce.min.js',
      // General options
      theme: "modern",
      plugins: "link code fullscreen fmgwordpaste",
      toolbar: "bold italic | alignleft aligncenter alignright | fullscreen",
      menubar: false,
      statusbar: false,
      content_css: "/areas/c2c/Content/css/editor.css",
      setup: function(editor) {
      },
      init_instance_callback: function(editor) {
        $('.mce-tinymce').show();
        $(editor.getContainer()).find(".mce-path").css("display", "none");
      },
    });
  }
});