programming4us
programming4us
MOBILE

jQuery 1.3 : Modifying table appearance (part 4) - Filtering

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
2/24/2011 3:53:24 PM

Filtering

Earlier we examined sorting and paging as techniques for helping users focus on relevant portions of a table's data. We saw that both could be implemented either with server-side technology, or with JavaScript. Filtering completes this arsenal of data arrangement strategies. By displaying to the user only the table rows that match a given criterion, we can strip away needless distractions.

We have already seen how to perform one type of filter: highlighting a set of rows. Now we will extend this idea to actually hiding rows that don't match the filter.

We can begin by creating a place to put our filtering links. In a typical progressive enhancement strategy, we insert these controls using JavaScript so that people without scripting available do not see the options:

$(document).ready(function() {
$('table.filterable').each(function() {
var $table = $(this);
$table.find('th').each(function(column) {
if ($(this).is('.filter-column')) {
var $filters = $('<div class="filters"></div>');
$('<h3></h3>')
.text('Filter by ' + $(this).text() + ':')
.appendTo($filters);
$filters.insertBefore($table);
}
});
});
});

We get the label for the filter box from the column headers so that this code can be reused for other tables quite easily. Now we have a heading awaiting some buttons:

Filter options

Now, we can move on to actually implementing a filter. To start with, we will add filters for a couple of known topics. The code for this is quite similar to the author highlighting example from before:

$(document).ready(function() {
$('table.filterable').each(function() {
var $table = $(this);
$table.find('th').each(function(column) {
if ($(this).is('.filter-column')) {
var $filters = $('<div class="filters"></div>');
$('<h3></h3>')
.text('Filter by ' + $(this).text() + ':')
.appendTo($filters);
var keywords = ['conference', 'release'];
$.each(keywords, function(index, keyword) {
$('<div class="filter"></div>').text(keyword)
.bind('click', {key: keyword}, function(event) {
$('tr:not(:has(th))', $table).each(function() {
var value = $('td', this).eq(column).text();
if (value == event.data['key']) {
$(this).show();
}
else {
$(this).hide();
}
});
$(this).addClass('active')
.siblings().removeClass('active');
}).addClass('clickable').appendTo($filters);
});
$filters.insertBefore($table);
}
});
});
});


Starting with a static array of keywords to filter by, we loop through and create a filtering link for each. Just as in the paging example, we need to use the data .bind() to avoid accidental problems due to the properties of closures. Then, in the click handler, we compare each cell's contents against the keyword and hide the row if there is no match. Since our row selector excludes rows containing a<th> element, we don't need to worry about subheadings being hidden. parameter of

Both of the links now work as advertised:

Collecting filter options from content

Now we need to expand the filter options to cover the range of available topics in the table. Rather than hard-coding all of the topics, we can gather them from the text that has been entered in the table. We can change the definition of keywords to read:

var keywords = {};
filtering, table appearancesfilter options, gathering from content$table.find('td:nth-child(' + (column + 1) + ')')
.each(function() {
keywords[$(this).text()] = $(this).text();
});


This code relies on two tricks:

  1. 1. By using a map rather than an array to hold the keywords as they are found, we eliminate duplicates automatically; each key can have only one value, and keys are always unique.

  2. 2. jQuery's $.each() function lets us operate on arrays and maps identically, so no subsequent code has to change.

Now we have a full complement of filter options:

Reversing the filters

For completeness, we need a way to get back to the full list after we have filtered it. Adding an option for all topics is pretty straightforward:

$('<div class="filter">all</div>').click(function() {
filtering, table appearancesfilters, reversing$table.find('tbody tr').show();
$(this).addClass('active')
.siblings().removeClass('active');
}).addClass('clickable active').appendTo($filters);


This gives us an all link that simply shows all rows of the table. For good measure, this new link is marked active to begin with.

Interacting with other code

We learned with our sorting and paging code that we can't treat the various features we write as islands. The behaviors we build can interact in sometimes surprising ways; for this reason, it is worth revisiting our earlier efforts to examine how they coexist with the new filtering capabilities we have added.

Row striping

The advanced row striping we put in place earlier is confused by our new filters. Since the tables are not re-striped after a filter is performed, rows retain their coloring as if the filtered rows were still present.

To account for the filtered rows, the row-striping code needs to be able to find them. The jQuery pseudo-class :visible can assist us in collecting the correct set of rows to stripe. While we're making this change, we can prepare our row-striping code to be invoked from other places by creating a custom event type for it, as we did when making sorting and paging work together.

$(document).ready(function() {
$('table.striped').bind('stripe', function() {
$('tbody', this).each(function() {
$(this).find('tr:visible:not(:has(th))')
.removeClass('odd').addClass('even')
.filter(function(index) {
return (index % 6) < 3;
}).removeClass('even').addClass('odd');
});
}).trigger('stripe');
});

In our filtering code, we can now call $table.trigger('stripe') each time a filtering operation occurs. With both the new event handler and its triggers in place, the filtering operation respects row striping:

Expanding and collapsing

The expanding and collapsing behavior added earlier also conflicts with our filters. If a section is collapsed and a new filter is chosen, then the matching items are displayed, even if in the collapsed section. Conversely, if the table is filtered and a section is expanded, then all items in the expanded section are displayed regardless of whether they match the filter.

One way to address the latter situation is to change the way we show and hide rows. If we use a class to indicate a row should be hidden, we don't need to explicitly call .hide() and .show(). By replacing .hide() with .addClass('filtered') and .show() with .removeClass('filtered'), along with a CSS rule for the class, we can accomplish the hiding and showing but play more nicely with the collapsing code. If the class is removed and the row is collapsed, the row will not be inadvertently displayed.

Introducing this new filtered class also helps us with the converse issue. We can test for the presence of filtered when performing a section expansion, skipping these rows instead of showing them. Testing for this class is a simple matter of adding :not(.filtered) to the selector expression used during expansion.

Now our features play nicely, each able to hide and show the rows independently.

The finished code

Our second example page has demonstrated table row striping, highlighting, tooltips, collapsing/expanding, and filtering. Taken together, the JavaScript code for this page is:

$(document).ready(function() {
table appearances, modifyingJavaScript code$('table.striped').bind('stripe', function() {
$('tbody', this).each(function() {
$(this).find('tr:visible:not(:has(th))')
.removeClass('odd').addClass('even')
.filter(function(index) {
return (index % 6) < 3;
}).removeClass('even').addClass('odd');
});
}).trigger('stripe');
});
$(document).ready(function() {
var $authorCells = $('table.striped td:nth-child(3)');
var $tooltip = $('<div id="tooltip"></div>').appendTo('body');
var positionTooltip = function(event) {
var tPosX = event.pageX;
var tPosY = event.pageY + 20;
$tooltip.css({top: tPosY, left: tPosX});
};
var showTooltip = function(event) {
var authorName = $(this).text();
var action = 'Highlight';
if ($(this).parent().is('.highlight')) {
action = 'Unhighlight';
}
$tooltip
.text(action + ' all articles by ' + authorName)
.show();
positionTooltip(event);
};
var hideTooltip = function() {
$tooltip.hide();
};
$authorCells
.addClass('clickable')
.hover(showTooltip, hideTooltip)
.mousemove(positionTooltip)
.click(function(event) {
var authorName = $(this).text();
$authorCells.each(function(index) {
if (authorName == $(this).text()) {
$(this).parent().toggleClass('highlight');
}
else {
$(this).parent().removeClass('highlight');
}
});
showTooltip.call(this, event);
});
});
$(document).ready(function() {
table appearances, modifyingJavaScript codevar collapseIcon = '../images/bullet_toggle_minus.png';
var collapseText = 'Collapse this section';
var expandIcon = '../images/bullet_toggle_plus.png';
var expandText = 'Expand this section';
$('table.collapsible tbody').each(function() {
var $section = $(this);
$('<img />').attr('src', collapseIcon)
.attr('alt', collapseText)
.prependTo($section.find('th'))
.addClass('clickable')
.click(function() {
if ($section.is('.collapsed')) {
$section.removeClass('collapsed')
.find('tr:not(:has(th)):not(.filtered)')
.fadeIn('fast');
$(this).attr('src', collapseIcon)
.attr('alt', collapseText);
}
else {
$section.addClass('collapsed')
.find('tr:not(:has(th))')
.fadeOut('fast', function() {
$(this).css('display', 'none');
});
$(this).attr('src', expandIcon)
.attr('alt', expandText);
}
$section.parent().trigger('stripe');
});
});
});
$(document).ready(function() {
$('table.filterable').each(function() {
var $table = $(this);
$table.find('th').each(function(column) {
if ($(this).is('.filter-column')) {
var $filters = $('<div class="filters"></div>');
$('<h3></h3>')
.text('Filter by ' + $(this).text() + ':')
.appendTo($filters);
$('<div class="filter">all</div>').click(function() {
$table.find('tbody tr').removeClass('filtered');
$(this).addClass('active')
.siblings().removeClass('active');
$table.trigger('stripe');
}).addClass('clickable active').appendTo($filters);
var keywords = {};
$table.find('td:nth-child(' + (column + 1) + ')')
.each(function() {
keywords[$(this).text()] = $(this).text();
});
table appearances, modifyingJavaScript code$.each(keywords, function(index, keyword) {
$('<div class="filter"></div>').text(keyword)
.bind('click', {key: keyword}, function(event) {
$('tr:not(:has(th))', $table).each(function() {
var value = $('td', this).eq(column).text();
if (value == event.data['key']) {
$(this).removeClass('filtered');
}
else {
$(this).addClass('filtered');
}
});
$(this).addClass('active')
.siblings().removeClass('active');
$table.trigger('stripe');
}).addClass('clickable').appendTo($filters);
});
$filters.insertBefore($table);
}
});
});
});

Other  
  •  Windows Phone 7 Development : Using Culture Settings with ToString to Display Dates, Times, and Text
  •  Mobile Application Security : SymbianOS Security - Persistent Data Storage
  •  Mobile Application Security : SymbianOS Security - Interprocess Communication
  •  Mobile Application Security : SymbianOS Security - Permissions and User Controls
  •  Windows Phone 7 Development : Building a Trial Application (part 3) - Verifying Trial and Full Mode & Adding Finishing Touches
  •  Windows Phone 7 Development : Building a Trial Application (part 2) - Connecting to a Web Service & Adding Page-to-Page Navigation
  •  Windows Phone 7 Development : Building a Trial Application (part 1) - Building the User Interface
  •  jQuery 1.3 : Table Manipulation - Sorting and paging (part 2) : Server-side pagination & JavaScript pagination
  •  jQuery 1.3 : Table Manipulation - Sorting and paging (part 1) : Server-side sorting & JavaScript sorting
  •  Windows Phone 7 Development : Understanding Trial and Full Modes (part 3) - Simulating Application Trial and Full Modes
  •  
    Top 10
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
    - Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
    - Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
    REVIEW
    - First look: Apple Watch

    - 3 Tips for Maintaining Your Cell Phone Battery (part 1)

    - 3 Tips for Maintaining Your Cell Phone Battery (part 2)
    programming4us programming4us
    programming4us
     
     
    programming4us