Showing posts with label DOM. Show all posts

jQuery: Add table row count column

In this post you will learn how to add a table column with a row count in it. If you are looking for "how to add a new table row", please refer to previous post.

Recently, a user on the jQuery mailing list asked how she could automatically add a table row counter into her table. She wanted to add a new column to her table and display the row number, so that her users could refer to row numbers while communicating. The user had the following HTML table markup:

<table class="transactions">
  <tr>
    <td>Order 1</td>
    <td>$10.99</td>
  </tr>
  <tr>
    <td>Order 2</td>
    <td>$4.50</td>
  </tr>
</table>

For cases like this, where you can be sure that there would be no table cell rowspans and there are only trs and tds. Then, jQuery code to add a counter is straightforward.

$('.transactions tr td:first-child').each(function(i){
  $(this).before('<td>'+(i+1)+'</td>');
});

Here, we are selecting first child td tags of all tr tags in a table with a transactions class. Then, we are iterating through them and passing a zero based index to the loop. While looping though the first row cells, we are adding new column just using .before('<td>'+(i+1)+'</td>') method. The resulting table would have the following markup:

<table class="transactions">
  <tr>
    <td>1</td>
    <td>Order 1</td>
    <td>$10.99</td>
  </tr>
  <tr>
    <td>2</td>
    <td>Order 2</td>
    <td>$4.50</td>
  </tr>
</table>

However, for cases when tables have header rows and footer, the above script may not behave as expected. That is why we have to take into the consideration table headers and footers. So the code snippet above will become:

$(".transactions").each(function(){
  $('th:first-child, thead td:first-child', this).each(function(){
    var tag = $(this).prop('tagName');
    $(this).before('<'+tag+'>#</'+tag+'>');
  });
  $('td:first-child', this).each(function(i){
    $(this).before('<td>'+(i+1)+'</td>');
  });
});

This code takes into the account the table headers and adds "#" header column for the counter. Still, this code is not as universal as it could have been. Let’s take into the account the possibility of row spans and multi line header rows and rewrite the code into the jQuery plugin with options.

So, here is the final code:

(function($){
  $.fn.extend({
    tableAddCounter: function(options) {
      
      // set up default options 
      var defaults = { 
        title:  '#',
        start:  1,
        id:     false, 
        class:  false,
      };

      // Overwrite default options with user provided
      var options = $.extend({}, defaults, options);

      return $(this).each(function(){
        // Make sure this is a table tag
        if($(this).is('table')){

          // Add column title unless set to 'false'
          if(!options.title) options.title = '';
          $('th:first-child, thead td:first-child', this).each(function(){
            var tagName = $(this).prop('tagName');
            $(this).before('<'+tagName+' rowspan="'+$('thead tr').length+'" class="'+options.class+'" id="'+options.id+'">'+options.title+'</'+tagName+'>');
          });
        
          // Add counter starting counter from 'start'
          $('tbody td:first-child', this).each(function(i){
            $(this).before('<td>' + (options.start + i) + '</td>');
          });
        
        }
      });
    }
  });
})(jQuery);

Plugin features

  • Can set custom settings as a hash parameter to the plugin:
    • title - counter column header text
    • start - the number to start the counter
    • id - CSS "id" to add to all the counter tds
    • class - CSS "class" to add to all the counter tds
  • It takes into consideration table header's row and column spans.
  • Gracefully degrade for browsers that do not support javascript.
  • Keeps your HTML mark up clean.
  • Make your tabular data more readable.

Example code

// Add row count with default settings
$('.myTables').tableAddCounter(); 

// Provide your settings for 
// column name, css class and start counter from 26
var options = { 
  name: "Count", 
  class: "counter",
  start: 26
};
$('.myTables').tableAddCounter(options);

Here is the original table (to the left) and new table (to the right) with row columns automatically added.

tableAddCounter jQuery plugin screenshot

jQuery .end() explanation & examples

jQuery's .end() method is one of the most rarely used methods that I know of. In this post I would like share remind you what it does and how we can use it to make our jQuery code more readable.

Firstly, what does it do?

Put simply .end() cancels the last filtering action and returns a set like it was before the filtering was applied. It's best explained with an example. Consider we have this HTML code:

<ul>
  <li>One</li>
  <li>Two</li>
  <li class="special">>Three</li>
</ul>
$('li')  // would have all 3 elements

$('li').filter('.special')  // would have 1 (third) element only

$('li').filter('.special').end()  // would have all 3 elements again

Now, the main question: Why on earth would you need it?! It is actually very useful when you have to apply different methods on several sub-selections in your DOM. The game development fits this case perfectly. I have seen beautiful and easy to understand game code that was made possible by a combination of custom jQuery selectors, extending jQuery and .end() method.

Couple of examples

To give you an idea of what a code of some hypothetical game might look like:

check_mines = function(from, to_coord){  // don't pay attention to this building block
     $("#game-canvas")

          .find("div:ships")
               .moveElements(from, to_coord)
          .end()

          .find("div:mines")
               .killIfClose(to_coord)
          .end()

          .find("div:healthPack")
               .addHealthIfClose(to_coord)
               .markHealthPackUsed(to_coord)
          .end();

};
SomeGame.register(check_mines);  // don't pay attention to this building block

As you can see, .end() helps us group related actions and make the code more readable.

Let's consider more realistic case. Let's say we have an article with images, links etc. (eg. Wikipedia articles). We need to add .content-img class to all images, than add .external-link to all external links, and finally apply a tooltip plugin to all the links that have .tooltip class within the article.

<div class="article">
     <p>Text ... </p>
     ...
</div>
$(".article")

    .find("img")
        .addClass("content-image")
    .end()  // back to .article set

    .find("a:external")
        .addClass("external-link")
    .end() // back to .article set

    .find("a.tooltip")
        .tooltip()
    .end(); // don't really need it here, but nice to have for symmetry

The above case is better written in 3 separate lines of codes of course, but it here to help explain the purpose of the .end().

NOTE:
If you do several filtering, you must call .end() several times as well.

$(".article").find("a").filter(".tooltip").end().end();

Hope you will find useful cases for .end() in your projects.

NS_ERROR_NOT_AVAILABLE: Component returned failure code: 0x80040111 (NS_ERROR_NOT_AVAILABLE) [nsIDOMWindow.alert]

This is a common error in Firefox. It is logged in console when you try to call on non existent DOM API method. Seeing this error message is very common with methods call to alert(), confirm(), drawImage() (canvas) and window.open(). You may think "how those methods could be 'non-existent'?", since they are standard APIs of the window object. Well, that's true, they can't, but consider the case when browser blocks your alert or pop-up windows. Firefox pop-up blocker will make those methods not available in that context and should silently carry on with the next statement, but instead it throws an error.

To solve this problem:

  • make sure you are calling methods on non-null objects;
  • remove alert(), confirm() or open() methods in unload event handler. FF pop-up blocker ignores all window.open() methods in the unload handler.

The flavours of the error message:

  • NS_ERROR_NOT_AVAILABLE: Component returned failure code: 0x80040111 (NS_ERROR_NOT_AVAILABLE) [nsIDOMWindow.alert]
  • NS_ERROR_NOT_AVAILABLE: Component returned failure code: 0x80040111 (NS_ERROR_NOT_AVAILABLE) [nsIDOMWindow.confirm]
  • NS_ERROR_NOT_AVAILABLE: Component returned failure code: 0x80040111 (NS_ERROR_NOT_AVAILABLE) [nsIDOMCanvasRenderingContext2D.drawImage]

At the time of writing the problem has not been fixed.

HTML: The difference between attribute and property

In this short post I will explain the difference between attributes and properties in HTML. The .prop() function introduced in jQuery 1.6 raised a lot of questions about the difference and I hope this post will help you to understand it.

What is an attribute?

Attributes carry additional information about an HTML element and come in name=”value” pairs. Example: <div class=”my-class”></div>. Here we have a div tag and it has a class attribute with a value of my-class.

What is a property?

Property is a representation of an attribute in the HTML DOM tree. So the attribute in the example above would have a property named className with a value of my-class.

Our DIV node
 |- nodeName = "DIV"
 |- className = "my-class"
 |- style
   |- ...
 |- ...

What is the difference between attribute and property?

Attributes are in your HTML text document/file, whereas properties are in HTML DOM tree. This means that attributes do not change and always carry initial (default) values. However, HTML properties can change, for example when user checks a checkbox, inputs text to textarea or uses JavaScript to change the property value.

Here is a visual representation:
HTML                          DOM
<input value="default" />     value = default



----------------
Current values:
Attribute: "default"          Property:  "default"

Assume user inputs his name "Joe" into the inputbox. Here are what attribute and property values of an element will be.

HTML                          DOM
<input value="default" />     value = Joe



----------------
Current values:
Attribute: "default"          Property:  "Joe"

As you can see, only element’s property is changed, because it is in the DOM and dynamic. But element’s attribute is in HTML text and can not be changed!

I hope this helps to understand the difference between attributes and properties. If you have any questions please leave them on jQueryHowto Facebook page.

Also, stay tuned for the next post about the difference between jQuery.attr() and jQuery.prop() and when to use one over another.

Dynamically create iframe with jQuery

This article explains and shows how to dynamically create an iframe in your HTML document’s DOM using jQuery and/or JavaScript. It also gives you an example code. This post is not extensive explanation of how to manage and work with iframes using jQuery. This post simply shows you how to dynamically create iframes using jQuery and JavaScript and serves as a note.

Creating iframe is similar to creating any DOM element. All you need to do is to use jQuery factory like this:

// Create an iframe element
$(‘<iframe />’);

// You can also create it with attributes set
$('<iframe id="myFrame" name="myFrame">');

// Finnaly attach it into the DOM
$('<iframe id="myFrame" name="myFrame">').appendTo('body');

Don’t forget that the iframe source is just an iframe’s attribute. So you can set that just like any other attribute.

// Setting iframe's source
$('<iframe />').attr('src', 'http://www.google.com'); 

UPDATE:

As BinaryKitten point’s out below, with jQuery 1.4 you can do it using the following syntax:

// Set attributes as a second parameter
$('<iframe />', {
    name: 'myFrame',
    id:   'myFrame',
    ...
}).appendTo('body');

diggthis.js + jQuery .wrap() is not working, causing Firefox to freeze and Safari to crush

I came across a forum post where user was experiencing problems while using Digg this (diggthis.js) button code with jQuery’s .wrap() method. According to him Mozilla Firefox (FF) is freezing and Apple Safari is crushing when he tries to wrap the div that contains diggthis.js file with another new div like so:

<div class="rounded"> 
  <script type="text/javascript"> 
    digg_url = "myURL.com"; 
  </script> 
  <script src="http://digg.com/tools/diggthis.js" type="text/javascript"></script>
</div>

And the jQuery code is:

$(document).ready(function(){ 
    $("div.rounded").wrap('<div></div>'); 
});

This code seems to crush those browsers. Another way to do this is to remove the div that contains the script (.rounded), then create a new <div> and then insert the removed div (.rounded) back into the newly created div like this:

$(document).ready(function(){ 
    $("div.rounded").remove().wrap('<div></div>').appendTo("#somewhere"); 
});

Remove the bottom table row using jQuery

I have posted a small jQuery code a while ago which adds a table row at the bottom of any given table. The code takes into the consideration tbody tag and also can be used as a jQuery plugin.

Recently I was asked to write a jQuery or JavaScript code that removes the last/bottom row from the given table. The jQuery code I wrote was surprisingly small.

jQuery code to remove bottom/last table row

// Simple bottom row removal
$('#myTable tr:last').remove();

// Removing n'th (ex. 3rd) row from the table
$('#myTable tr:eq(2)').remove();

Improved jQuery code

We can improve our simple bottom row removal code to take into the consideration the possible <tbody> and <tfoot> HTML tags that can be found in tables.

// Improved code that takes into the consideration
// the <tbody> tag
$('#myTable').each(function(){
    if($('tbody', this).length > 0){
        $('tbody tr:last', this).remove();
    }else {
        $('tr:last', this).remove();
    }
});

// Improved code that for n'th row removal
// In this example we are removing 3rd row
$('#myTable').each(function(){
    if($('tbody', this).length > 0){
        $('tbody tr:eq(2)', this).remove();
    }else {
        $('tr:eq(2)', this).remove();
    }
});

Bonus JavaScript function

You can also turn the code above into the jQuery plugin or JavaScript function. Here is a JavaScript function to remove the bottom table row (you can amend the code to make it remove n’th row).

/*
    Remove the last/bottom table row
*/
function removeTableRow(jQtable){
    jQtable.each(function(){
        if($('tbody', this).length > 0){
            $('tbody tr:last', this).remove();
        }else {
            $('tr:last', this).remove();
        }
    });
}

// Here is how to use it
removeTableRow($('table'));
Also the above javascript function can easily be rewritten as a jQuery plugin. Read this post to learn how to create jQuery plugins.

Dynamically creating input box/checkbox/radio button... does not work in Internet Explorer (IE)

While working on some project, trying to create a checkbox and radio button dynamically using jQuery I came across a problem. Mozilla Firefox, Opera and Safari were creating and rendering my new checkboxes just fine, but Internet Explorer (IE6, IE7) did not create them. I spend half a day trying to figure out what is wrong with my jQuery or JavaScript code. Some hours later, I remember, I came across a post saying that IE can not create a general DOM input form element and then assign it a type (checkbox, radio, text, password, etc) attribute.

What you need to do when you are creating a new checkbox or radio button set with jQuery is to define the type attribute while creating like so:

$('<input type="checkbox" />');
// Create and then set any other attributes
$('<input type="checkbox" />').attr('id', 'newBox');

Problem:

Can not create a new input form fields using jQuery or newly created checkboxes and radio buttons are not displayed/created.

Solution:

To solve the problem you need to create an input field with type attribute already defined.

Add table row using jQuery and JavaScript

I noticed that there are a lot of developers who need to dynamically add a new table row to the bottom of their tables. I wrote a little javascript function that takes your jQuery selection of tables and dynamically adds a table row at the bottom of them.

jQuery add table row function definition:

/*
    Add a new table row to the bottom of the table
*/

function addTableRow(jQtable){
    jQtable.each(function(){
        var $table = $(this);
        // Number of td's in the last table row
        var n = $('tr:last td', this).length;
        var tds = '<tr>';
        for(var i = 0; i < n; i++){
            tds += '<td>&nbsp;</td>';
        }
        tds += '</tr>';
        if($('tbody', this).length > 0){
            $('tbody', this).append(tds);
        }else {
            $(this).append(tds);
        }
    });
}

jQuery add table row function usage example:

addTableRow($('#myTable'));
addTableRow($('.myTables'));

5 easy tips on how to improve code performance with huge data sets in jQuery

Sitting on jQuery's support mailing list I noticed that developers use jQuery with huge data sets and their code becomes very slow. Examples would be generating very long tables with a lot of rows using AJAX to get JSON data. Or iterating through a long (very long) list of data, etc.

So I compiled a list of 5 easy tips on how to improve your code performance while working with huge data sets in jQuery.

  1. Use JavaScript native for() loop instead of jQuery's $.each() helper function.

    Native browser functions are always faster then any other helper functions that were built to add an abstraction layer. In case you are looping through an object that you have received as JSON, I highly recommend you rewrite your JSON to contain an array rather than an object.

  2. Do NOT append an element to the DOM in your loop.

    This one is probably one of the most important tips that will significantly improve your code performance. It is so important that I will repeat myself. Do not append a new element to the DOM in your loop statement. Instead store it in a variable as text and append it to the DOM after your loop finishes like this:

    // DO NOT DO THIS 
    for (var i=0; i<=rows.length; i++)  
    { 
        $('#myTable').append('<tr><td>'+rows[i]+'</td></tr>'); 
    } 
    
    // INSTEAD DO THIS 
    var tmp = ''; 
    for (var i=0; i<=rows.length; i++)  
    { 
        tmp += '<tr><td>'+rows[i]+'</td></tr>'; 
    } 
    $('#myTable').append(tmp);

  3. If you have a lot of elements to be inserted into the DOM, surround them with a parent element for better performance.

    When you have a lot of elements to insert into the DOM tree it takes time to add them all. Somehow adding one element with 1000 children is faster than adding 1000 children separately. You can search this site for performance tests that prove it.
    So, to improve our previous example's performance let's cover <tr>'s with <tbody> tag like this:

    var tmp = '<tbody>';
    for (var i=0; i<=rows.length; i++)
    {
        tmp += '<tr><td>'+rows[i]+'</td></tr>';
    }
    tmp += '</tbody>';
    $('#myTable').append(tmp);

  4. Don't use string concatenation, instead use array's join() method for a very long strings.

    var tmp = [];
    tmp[0] = '<tbody>';
    for (var i=1; i<=rows.length; i++)
    {
        tmp[i] = '<tr><td>'+rows[i-1]+'</td></tr>';
    }
    tmp[tmp.length] = '</tbody>';
    $('#myTable').append(tmp.join(''));

  5. And the last but not least use setTimeout() function for your long list looping and concatenation functions.

    This will make sure that page does not freeze while it loops through the long list of data and lets users to work with your page meanwhile.

    It was well mentioned in comments that setTimeout() function should be used to split your code processing into little chunks so your browser does not freeze up like this:

    function myFunk(data){ 
         
        // do processing 
         
        if(!has_finished) 
            setTimeout("myFunk()", 100); 
    }

How to get full html string including the selected element itself with jQuery's $.html()

Sometimes you need to get the selected element's html as well when using .html() function. To make it more clear what I mean, consider you have this HTML markup:

<div id="top">
  <div id="inner">
    Some content
  </div>
  More content
</div>

And you need to get not only <div id="inner">Some con... but <div id="top"><div id="inner">Some con...

Here is the code to get jQuery selector's HTML including its own:

var html = $('<div>').append($('#top').clone()).remove().html();

Here we are:

  1. Cloning selected div
  2. Creating a new div DOM object and appending created clone of the div
  3. Then getting the contents of wrapped div (which would give us all HTML)
  4. Finally removing the created DOM object, so it does not clutter our DOM tree.

This is a little trick you can use to select self HTML with jQuery's .html() function. But if you can you should surround your markup with some dummy div and avoid this workaround all together. This would be much faster since jQuery would not need to do all the DOM manipulations.