Cross-document communication with iframes

Using iframes (inline frames) is often considered bad practice since it can hurt you from a SEO point view (contents of the iframes will not be indexed by search engines). But whenever you have an application which doesn’t require indexing of contents (e.g. because the content is only visible after the user has been authenticated and authorized) or you need to embed content from other web sites/apps, iframes provide a nice mechanism to include content in your app and ensure that this doesn’t cause any major security issues.

Please refer to the MDN which contains a good description of iframes and a few examples.

Accessing an iframe and its content

The first step in building using iframes is of course to define an iframe tag in your HTML code which will define where in the DOM, the external resources will be taken over:

<iframe id="iframe1"></iframe>

Now you have added this tag in your HTML code, you will most probably want to access it with JavaScript to set a URL to be loaded, define how the iframe contents should be displayed (e.g. the width and height of the iframe) and maybe access some of the DOM elements in the iframe. This section will show you how this can be done.

Please keep in mind that things are relatively easy when working with iframes which contents are loaded from the same host/domain. If you work with contents from other hosts/domains, you’ll need to have a look at the next sections as well.

Setting the URL and styles of an iframe

Setting the URL which contents need to be loaded in the iframe, just means setting the src property of the iframe object. And styling it can be done by using the style property. Here’s a short example:

var iframe1 = document.getElementById('iframe1');
iframe1.style.height = '200px';
iframe1.style.width = '400px';
iframe1.src = 'iframe.html';

In this case the source for the iframe contents is an HTML page on the same host/domain but you could also define a complete URL pointing to another location.

Detecting when the iframe’s content has been loaded

Before you can access the contents of the iframe, you will have to wait for the iframe contents to be loaded (just like you should wait for the contents of your page to be fully loaded before accessing and manipulating them). This is done by defining an onload callback on the iframe:

iframe1.onload = function() {
    // your code here
}

Accessing the contents of the iframe

Once you’ve made sure that the iframe contents have been loaded, you can access it’s document using either the contentDocument property of the iframe object or by using the document property of the contentWindow property of the iframe. Of course, it’s just easier to use contentDocument. Unfortunately, it’s not supported by older versions of Internet Explorer so to make sure that it works in all browsers, you should check whether the contentDocument property exists and if not, revert to contentWindow.document:

var frameDocument = iframe1.contentDocument ? iframe1.contentDocument : iframe1.contentWindow.document;
var title = frameDocument.getElementsByTagName("h1")[0];
alert(title.textContent);

Interactions between iframe and parent

Now that you can load content in the iframe, define how it should be displayed and access its content, you might also need to go one step further and access the parent document (or the iframes properties) from the iframe itself.

Accessing the parent document

Just like we accessed the contents of the iframe from a script in the parent page, we can do the opposite (currently ignoring cross-domain issues) by using the document property of the parent object:

var title = parent.document.getElementsByTagName("h1")[0];
alert(title.textContent);

Accessing the iframe properties from the iframe

If you have some logic based on the styles of the iframe tag in the parent page (e.g. its width or height), you can use window.frameElement which will point you to the containing iframe object:

var iframe = window.frameElement;
var width = iframe.style.width;
alert(width);

Calling a JavaScript function defined in the iframe

You can call JavaScript functions defined in the iframe (and bound to its window) by using the contentWindow property of the iframe object e.g.:

iframe1.contentWindow.showDialog();

Calling a JavaScript function defined in the parent

Similarly, you can call a JavaScript function defined in the parent window by using the window property of the parent object e.g.:

parent.window.showDialog2();

Same Origin Policy

The Same Origin Policy is an important concept when using JavaScript to interact with iframes. This is basically a security policy enforced by your browser and preventing documents originating from different domains to access each other’s properties and methods.

What’s the same origin?

Two documents have the same origin, if they have the same URI scheme/protocol (e.g. http, https…), the same host/domain (e.g. google.com) and the same port number (e.g. 80 or 443).

So documents loaded from:

  • http://google.com and https://google.com do not have the same origin since they have different URI schemes (http vs https)
  • http://benohead.com and http://benohead.com:8080 do not have the same origin since they have port numbers (80 vs 8080)
  • http://benohead.com and http://www.benohead.com do not have the same origin since they have different hostnames (even if the document loaded from www.benohead.com would be the same if loaded from benohead.com)
  • http://kanban.benohead.com and http://benohead.com do not have the same origin since sub-domains also count as different domains/hosts

But documents loaded from URIs where other parts of the URI are different share the same origin e.g.:

  • http://benohead.com and http://benohead.com/path: folders are not part of the tuple identifying origins
  • http://benohead.com and http://user:password@benohead.com: username and password are not part of the tuple identifying origins
  • http://benohead.com and http://benohead.com/path?query: query parameters are not part of the tuple identifying origins
  • http://benohead.com and http://benohead.com/path#fragment: fragments are not part of the tuple identifying origins

Note that depending on your browser http://benohead.com and http://benohead.com:80 (explicitly stating the port number) might or might not be considered the same origin.

Limitations when working with different origins

A page inside an iframe is not allowed to access or modify the DOM of its parent and vice-versa unless both have the same origin. So putting it in a different way: document or script loaded from one origin is prevented from getting or setting properties of a document from another origin.

Interacting cross-domain

Of course, in most cases using iframes makes sense when you want to include contents from other domains and not only when you want to include contents from the same domain. Fortunately, there are a few options for handling this depending on the exact level of cross-domain interaction which is required.

URL fragment hack

What you would have done 5 to 10 years ago is workaround the limitation by using the fact that any window/iframe can set the URL of another one and that if you only change the fragment part of a URL (e.g. what’s after the hash sign #), the page doesn’t reload. So basically, this hack involves sending some data to another iframe/window, by getting a reference to this iframe/window (which is always possible), adding a fragment (or changing it) in order to pass some data (effectively using the fragment as a data container and setting the URL as a trigger event).

Using this hack comes with two main limitations:

  • This hack doesn’t seem to work anymore in some browsers (e.g. Safari and Opera) which will not allow child frame to change a parent frame’s location.
  • You’re limited to the possible size of fragment identifiers which depends on the browser limitation and on the size of the URL without fragment. So sending multiple kilobytes of data between iframes using this technique might prove difficult.
  • It may cause issues with the back button. But this is only a problem if you send a message to your parent window. If the communication only goes from your parent window to iframes or between iframes, then you won’t see the URL and bookmarking and the back button will not be a problem.

So I won’t go into more details as how to implement this hack since there are much better ways to handle it nowadays.

window.name

Another hack often used in the past in order to pass data from an iframe to the parent. Why window.name ? Because window.name persists accross page reloads and pages in other domains can read or change it.

Another advantage of window.name is that it’s very easy to use for storage:

window.name = '{ "id": 1, "name": "My name" }';

In order to use it for communicating with the parent window, you need to introduce some polling mechanism in the parent e.g.:

var oldName = iframe1.contentWindow.name;
var checkName = function() {
  if(iframe1.contentWindow.name != oldName) {
    alert("window name changed to "+iframe1.contentWindow.name);
    oldName = iframe1.contentWindow.name;
  }
}
setInterval(checkName, 1000);

This code will check every second whether the window.name on the iframe has changed and display it when it has.

You seem to be able to store up to 2MB of data in window.name. But keep in mind that window.name was never actually not designed for storing or exchanging data . So browser vendor support could be dropped at any time.

Server side proxy

Since the single origin policy is enforced by the browser a natural solution to work around it is to access the remote site from your server and not from the browser. In order to implement it, you’ll need a proxy service on your site which forwards requests to the remote site. Of course, you’ll have to limit the use of the server side proxy in order not to introduce an exploitable security hole.

A cheap implementation of such a mechanism, could be to use the modules mod_rewrite or mod_proxy for the Apache web server to pass requests from your server to some other server.

document.domain

If all you’re trying to do is have documents coming from different subdomains interact, you can set the domain which will be used by the browser to check the origin in both document using the following JavaScript code:

document.domain = "benohead.com";

You can only set the domain property of your documents to a suffix (i.e. parent domain) of the actual domain. So if you loaded your document from “kanban.benohead.com” you can set it to “benohead.com” but not to “google.com” or “hello.benohead.com” (although you wouldn’t need to set it to “hello.benohead.com” since you can set the domain to “benohead.com” for both windows/frames loaded from “kanban.benohead.com” and “hello.benohead.com”).

JSON with Padding (JSONP)

Although not directly related to the inter-domain and inter-frame communication, JSONP allows you to call a remote server and have it execute some JavaScript function define on your side.

The basic idea behind JSONP is that the script tag bypasses the same-origin policy. So you can call a server using JSONP and provice a callback method and the server will perform some logic and return a script which will call this callback method with some parameters. So basically, this doesn’t allow you to implement a push mechanism from the iframe (loaded from a different domain) but allows you to implement a pull mechanism with callbacks.

One of the main restrictions when using JSONP is that you are restricted to using GET requests.

Cross-Origin Resource Sharing (CORS)

CORS is a mechanism implemented as an extension of HTTP using additional headers in the HTTP requests and responses.

Except for simple scenarios where no extra step is required, in most cases enabling CORS means that an extra HTTP request is sent from the browser to the server:

  1. A preflight request is sent to query the CORS restrictions imposed by the server. The preflight request is required unless the request matches the following:
    • the request method is a simple method (i.e. GET, HEAD, or POST)
    • the only headers manually set are headers set automatically by the user agent (e.g. Connection and User-Agent) or one of the following: Accept, Accept-Language, Content-Language, Content-Type.
    • the Content-Type header is application/x-www-form-urlencoded, multipart/form-data or text/plain.
  2. The actual request is sent.

The preflight request is an OPTIONS request with an Origin HTTP header set to the domain that served the parent page. The response from the server is either an error page or an HTTP response containing an Access-Control-Allow-Origin header. The value of this header is either indicating which origin sites are allowed or a wildcard (i.e. “*”) that allows all domains.

Additional Request and Response Headers

The CORS specification defines 3 additional request headers and 6 additional response headers.

Request headers:

  • Origin defines where the CORS request comes from
  • Access-Control-Request-Method defines in the preflight request which request method will later be used in the actual request
  • Access-Control-Request-Headers defines in the preflight request which request headers will later be used in the actual request

Response headers:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Credentials
  • Access-Control-Expose-Headers
  • Access-Control-Max-Age
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

How does it work?

The basic CORS workflow with preflight requests looks like this:

CORS

The browser send an HTTP OPTIONS request to the remote server with the origin of the page and the request method to be used. The remote server responds with an allowed origin and allowed methods headers. The browser then proceeds with the actual HTTP request. If you want to use some additional headers, an Access-Control-Request-Headers will also be sent in the OPTIONS request and an Access-Control-Allow-Headers will be returned in the response. You can then use this additional header in the actual request.

CORS vs. JSONP

Although CORS is supported by most modern web browsers, JSONP works better with older browsers. JSONP only supports the GET request method, while CORS also supports other types of HTTP requests. CORS makes it easier to create a secure cross-domain environment (e.g. by allowing parsing of responses) while using JSONP can cause cross-site scripting (XSS) issues, in case the remote site is compromised. And using CORS makes it easier to provide good error handling on top of XMLHttpRequest.

Setting up CORS on the server

In order to allow CORS requests, you only have to configure the server to add the following header to its response:

Access-Control-Allow-Origin: *

Of course, instead of a star, you can also return a single origin (e.g. http://benohead.com). The specification states that it could also be a space separated list of origins but in practice you’ll either have a start or a single origin. If you want to support a specific list of origins, you’ll have to have the web server check whether the provided origin is in a given list of allowed origins and return this one origin in the response to the HTTP call.

And if the requests to the web servers will also contain credentials, you need to configure the web server to also return the following header:

Access-Control-Allow-Credentials: true

If you are expecting not only simple requests but also preflight requests (HTTP OPTIONS), you will also need to set the Access-Control-Allow-Methods header in the response to the browser. It only needs to contains the method requested in the Access-Control-Request-Method header of the request. But usually, the complete list of allowed methods is sent back e.g.:

Access-Control-Allow-Methods: POST, GET, OPTIONS

Security and CORS

CORS in itself is not providing with the means to secure your site. It just helps you defining how the browsers should be handling access to cross-domain resource (i.e. cross-domain access). But since it relies on having the browser enforce the CORS policies, you need to have an additional security layer taking care of authentication and authorization.

In order to work with credential, you have set the withCredentials property to true in your XMLHttpRequest and the server needs put an additional header in the response:

Access-Control-Allow-Credentials: true

HTML5 postMessage

Nowadays, the best solution for direct communication between a parent page and an iframe is using the postMessage method available with HTML5. Using postMessage, you can send a message from one side to the other. The message contains some data and an origin. The receiver can then implement different behaviors based on the origin (also note that the browser will also check that the provided origin makes sense).

parent to iframe

In order to send from the parent to the iframe, the parent only has to call the postMessage function on the contentWindow of the iframe object:

iframe1.contentWindow.postMessage("hello", "http://127.0.0.1");

On the iframe side, you have a little bit more work. You need to define a handler function which will receive the message and register it as an event listener on the window object e.g.:

function displayMessage (evt) {
	alert("I got " + evt.data + " from " + evt.origin);
}

if (window.addEventListener) {
	window.addEventListener("message", displayMessage, false);
}
else {
	window.attachEvent("onmessage", displayMessage);
}

The parameter to the handler function is an event containing both the origin of the call and the data. Typically, you’d check whether you’re expecting a message from this origin and log or display an error if not.

iframe to parent

Sending messages in the other direction works in the same way. The only difference is that you call postMessage on parent.window:

parent.window.postMessage("hello", "http://127.0.0.1");

 

CSS: centering an elemenent vertically and horizontally

I had already written an article about this quite some time ago. The problem with this approach is that it only works with elements having a fix size. So I’ve written an article specific to the horizontal centering of variable width DIVs last year. The problem with this approach is that although it works I have no clue why and feel it might break some time in the future. Also this approach only works for centering horizontally. Luckily, since then, I’ve able to use another approach which seems cleaner and also doesn’t require my components to have a fix size and works both vertically and horizontally, without using any strange hacks.

First let’s start with a non-centered element:

<html>
	<body>
		<div style="background-color:green;width:35%;height:35%">
	</body>
</html>

It’s green and it has a height and width of 35% (so no fixed size). As expected, it’s displayed in the upper left corner:

not centered

Obviously, the first thing you’d try and is giving it a top and left margin of 50% to center it:

<html>
	<head>
		<style>
		.centered {
			margin-left: 50%;
			margin-top: 50%;
		}
		</style>
	</head>
	<body>
		<div class="centered" style="background-color:green;width:35%;height:35%">
	</body>
</html>

Unfortunately, this will move the element past the center. You’ll also notice that it’s off by a lot vertically but less horizontally:

margins 50 percent

This is because margin-top doesn’t use the height of the container to compute percentages to pixels but just like margin-left, it uses the width. So if your container is square, you won’t see a difference but otherwise, it’s a problem. So using margin-top is no option. Instead, we’ll set the position to relative and set top to 50%. Ok, we’d implement it without hacks but this one not really a bad one…

<html>
	<head>
		<style>
		.centered {
			margin-left: 50%;
			top: 50%;
			position: relative;
		}
		</style>
	</head>
	<body>
		<div class="centered" style="background-color:green;width:35%;height:35%">
	</body>
</html>

This now looks better but it’s still not properly centered:

better 50 percent

As you can probably guess now, the problem is that the upper left corner of the element is centered both vertically and horizontally. Not the center of the element. In order to correct this, we need some CSS properties which unlike the margin-xxx or top do not take into account the dimensions of the container but the dimensions of the element. Actually there aren’t so many properties like this. Luckily CSS transformations do work with the dimensions of the element. So a translation of -50% both vertically and horizontally will center the element:

<html>
	<head>
		<style>
		.centered {
			margin-left: 50%;
			top: 50%;
			position: relative;
			-webkit-transform: translate(-50%, -50%);
			-moz-transform: translate(-50%, -50%);
			-o-transform: translate(-50%, -50%);
			-ms-transform: translate(-50%, -50%);
			transform: translate(-50%, -50%);
		}
		</style>
	</head>
	<body>
		<div class="centered" style="background-color:green;width:35%;height:35%">
	</body>
</html>

Now it looks exactly the way it’s supposed to:

centered

HTML5: Displaying the battery level

Note that this currently only works with Firefox. Support for Chrome and Internet Explorer is not yet available and it’s not yet known when it will be available.

The new Battery Status API allows you from a web page to access information about the battery status of the device rendering it. It is not only useful in order to show the current status but to implement battery status dependent logic e.g. increasing polling interval using Ajax or disabling animations in order to preserve the battery, or auto-saving in case the device goes out of power, prevent the user from performing long running operation which might not be able to finish because of the low remaining battery level.

First we’ll create the HTML and CSS in order to get an empty battery display looking like this:

The HTML code couldn’t be simpler. We just need a battery div:

<div id="battery"></div>

Now we need to display a border around the div and a smaller rectangle after it:

.battery:after {
    background-color: #fff;
    border: 2px solid #000;
    content: "";
    display: block;
    height: 16px;
    position: absolute;
    right: -6px;
    top: 6px;
    width: 6px;
}
.battery {
    background-color: #fff;
    border: 2px solid #000;
    height: 32px;
    margin-left: auto;
    margin-right: auto;
    position: relative;
    width: 100px;
}

Then we need to display a battery level within the battery display:

<div id="battery"><div id="battery-level"></div></div>

And we just need to add some CSS:

.battery .battery-level {
    background-color: #666;
    height: 100%;
}

By setting the width of .battery-level in pixels (or in percent which is the same since the element is 100 pixels wide) to the battery level in percents, we’ll get the right display:

Of course, one would expect our battery level display to show the level in a different color depending whether the battery is almost empty or not. So let’s introduce three CSS classes which will show the level in green, yellow or red:

Using the following CSS code:

.battery .battery-level.high {
    background-color: #66CD00;
}
.battery .battery-level.medium {
    background-color: #FCD116;
}
.battery .battery-level.low {
    background-color: #FF3333;
}

Before we move to the HTML5 part, let’s also display the battery level as text, by setting it as text on the battery-level element and by adding the following css rule:

.battery .battery-level {
    text-align: center;
}

It then looks like this:

10%
75%
95%

Now we’re almost done… All we need is what’s actually the main topic of this post: to get the battery level data using HTML5. For this we need to use the new Battery Status API.

The Battery Status API provides access the battery information through navigator.battery which implement the BatteryManager interface. This interface provides the following information:

  • whether the battery is currently charging
  • the charging time
  • the discharging time
  • the battery level

Additionally, it also provides events you can listen to:

  • onchargingchange
  • onchargingtimechange
  • ondischargingtimechange
  • onlevelchange

We will not use the events for now and just show the battery level when the page was loaded:

The JavaScript code to work with this API is pretty straight forward:

var battery = navigator.battery;
var level    = battery.level * 100;
var batteryLevel = jQuery('.battery .battery-level');
batteryLevel.css('width', level + '%');
batteryLevel.text(level + '%');
if (battery.charging) {
    batteryLevel.addClass('charging');
} else if (level > 50) {  
    batteryLevel.addClass('high');  
} else if (level >= 25 ) {  
    batteryLevel.addClass('medium');  
} else {  
    batteryLevel.addClass('low');  
}

If you want to update the battery level display as the battery charges and discharges:

Here’s the JavaScript code:

function updateBatteryDisplay(battery) {
	var level = battery.level * 100;
	var batteryLevel = jQuery('.battery .battery-level');
	batteryLevel.css('width', level + '%');
	batteryLevel.text(level + '%');
	if (battery.charging) {
	    batteryLevel.addClass('charging');
	    batteryLevel.removeClass('high');  
	    batteryLevel.removeClass('medium');  
	    batteryLevel.removeClass('low');  
	} else if (level > 50) {  
	    batteryLevel.addClass('high');  
	    batteryLevel.removeClass('charging');
	    batteryLevel.removeClass('medium');  
	    batteryLevel.removeClass('low');  
	} else if (level >= 25 ) {  
	    batteryLevel.addClass('medium');  
	    batteryLevel.removeClass('charging');
	    batteryLevel.removeClass('high');  
	    batteryLevel.removeClass('low');  
	} else {  
	    batteryLevel.addClass('low');  
	    batteryLevel.removeClass('charging');
	    batteryLevel.removeClass('high');  
	    batteryLevel.removeClass('medium');  
	}
}

var battery = navigator.battery;
updateBatteryDisplay(battery);
battery.onchargingchange = function () {
    updateBatteryDisplay(battery);
};
battery.onlevelchange = function () {
    updateBatteryDisplay(battery);
};

So the Battery Status API is a very powerful and useful API which is very easy to use. Now let’s hope it will soon be available in all major browsers !

CSS: Center variable width divs horizontally

When centering divs, if your div has a fixed width you can either absolutely position it and set negative margins as shown in this post. Another way to center such a fixed width div horizontally is to set both its left and right margin to auto. The browser then has to set both margins to the same value.

Unfortunately known of these two techniques can be used if your div doesn’t have a fixed width. In this case, you’ll need a different solution.

Let’s assume you have a container div with another div inside:

<html>
	<body>
		<div id="container" style="width:100%; height:100%; background-color:red;">
			<div id="contained" style="width:50%; height:50%; background-color:green;">
				<p>I want to be centered</p>
			</div>
		</div>
	</body>
</html>

The container div will fill the whole window while the contained div will fill the upper left corner:

div not centered

The trick is to set a centered text alignment on the container and set an inline-block display on the contained div. Of course the text-align CSS property will be inherited by the contained div so you’ll need to set it back to left:

<html>
	<head>
		<style>
			#container {
				text-align: center;
			}
			#contained {
				text-align: left;
				display: inline-block;
			}
		</style>
	</head>
	<body>
		<div id="container" style="width:100%; height:100%; background-color:red;">
			<div id="contained" style="width:50%; height:50%; background-color:green;">
				<p>I want to be centered</p>
			</div>
		</div>
	</body>
</html>

It then looks like this:

div centered

I actually found it while changing all possible properties on the container and contained div. Just by chance. It still doesn’t make much sense to me but it does work. Before figuring this out, I was always using some JavaScript to find out the actual width of the contained div and set the margins accordingly and recomputing it on any resize. In case you are still interested in a jQuery solution, please check Manos Malihutsakis Blog.

HTML5: removed elements (tags)

Because of the semantical orientation of HTML5 many HTML tags have been declared a non-conformant (deprecated) in HTML5. These tags are tags which are either redundant or do not transport meaning but only display properties which should rather be done setting CSS properties than HTML tags.

Here’s the list of tags currently removed in HTML5:

Removed tag Usage Alternative HTML tags Alternative CSS properties
<acronym> acronym or abbreviation for a word <abbr>
<applet> Java applet <object>
<basefont> default font size for a document font, font-family, font-size, font-size-adjust, font-stretch, font-style, font-variant, font-weight, @font-face
<big> text font size one size bigger font-size
<center> center content horizontally within the containing element text-align
<dir> directory <ul>
<font> font size, color and face font, font-family, font-size, font-size-adjust, font-stretch, font-style, font-variant, font-weight, @font-face
<frame> area in which another HTML document can be displayed <iframe>
<frameset> container for <frame> elements
<noframes> element used when <frame> elements are not supported
<strike> strike-through <del> or <span> text-decoration
<tt> display text with monotype font <code>

Note that <u> was on the list of deprecated tags for some time but currently has found its way back into the HTML5 specification. <u> makes the contained text underlined. It's never been a good idea to use it since underlined text generally has a different meaning for users of HTML pages: it usually shows that this text is a link which is not the case with the <u> tag. Of course the same can be achieved with CSS, so this tag is not really required and using it for only display purposes doesn't fit the semantical objective of HTML5. This element only got out of the HTML5 non-conformity because there are scenarios in which it does transport a meaning (Chinese proper name mark, text marked incorrect by a spell-checker, indicate family names).

Online HTML Escape Tool

When writing a new post, I usually use the text view in TinyMCE. Whenever I need to paste some code example, I thus need to escape the pasted text to HTML entities so that it’s not interpreted as HTML code but to have it displayed properly. When just pasting a line of HTML it’s not a big deal but when I paste many lines it’s quite some work.

A workaround is to switch to the Visual view past the code and then switch back to the text view to add the <pre> tag. But it’s not very practical. Ideally, I would just need to open a web page in a separate browser window where I could have the code escaped.

Well, it’s exactly what I’ve implemented tonight. I’ve created a web page with two textareas. You can paste some code in the first textarea. The tool will then use JavaScript to escape the text and show it in the second readonly textarea. It escapes the following:

  • ", ', &, < and > to respectively &quot;, &apos;, &amp;, &lt; and &gt;
  • all codes between 160 and 255 with the corresponding HTML entity
  • all codes above 127 which haven’t yet been escaped to an hexadecimal notation e.g. &#x83;

Additionally to save some more interactions, I’ve added a button to copy the escaped text to the clipboard using the ZeroClipboard library. Since the latest version of the library seems to not work properly on my site, I ended up using the zclip jQuery plugin which in turn uses ZeroClipboard. In case, you want to do the same, note that the link to the swf file on the zclip library refers to google code where ZeroClipboard is not available anymore because it moved to the excellent GitHub. Luckily, I had an older version of zclip with the swf file in another project and could thus reuse it.

Since the whole code is running on the client using JavaScript, you can have a look at the source code by using the corresponding function of your favorite browser.

In the future, I plan to improve the page to make it look nicer and to add some options as to what you want to escape. For instance, you do not need to escape apostrophes and quotes if you just paste the code in an HTML tag (but you do need it if you paste it to an attribute). Also, you may want to also escape spaces and new lines which is currently not supported.

The tool can be found online at Online HTML Escape Tool.

CSS: Display checkboxes as switches

Checkboxes are kind of the most boring elements on a web page since the browsers always display them the same. Another problem is that the checkbox itself is pretty small. Of course it’s not a real problem for such checkboxes:

You can click on the checkbox itself or on the label to toggle it. So the small size of the checkbox itself is not an issue. But when you have something like this, it’s more of a problem:

Identification code:
Display name:
Activated:

Even if you wrap the checkbox in a label for it, you can still click anywhere near the checkbox to toggle it but it’s not visible to the user that it’s possible.

A better solution would be to have some alternative representation of the checkbox e.g. as a switch. It’s also important to be able to adapt the size of the switch. Here’s how it should look like in the end (the first one is the normal size and the second one is twice as large):

switches

Since you cannot style the checkbox itself, we’ll have to add a new element and hide the checkbox. Additionally, since some scripts will read the state of the checkbox, we need to keep the checkbox in there (even if not displayed) and we need to make sure that the state of the switch and the state of the check are in sync.

The easiest way to do it is to use a label. As can be see above if a label is created for a checkbox, clicking on the label toggles the checkbox. So the basic idea is to:

  • add a label for the checkbox
  • hide the checkbox
  • style the label to look like a switch
  • have a styling of the label based on the state of the checkbox

For the last point, we’ll leverage a special kind of CSS selector which selects elements that are placed immediately after a given element. This is done with a plus sign between the two elements:

input[type="checkbox"] + label

This would select the labels immediately following a checkbox. Using this we can style the labels depending on the state of the checkbox.

So first we need some HTML to have a checkbox:

<input class="switch" id="check1" type="checkbox" />

Note that I’ve used the class “switch” in case you do not want to turn all checkboxes into switches but only some of them. If you do want to turn them all into switches, just remove this class from all selector in the CSS and jQuery code in the rest of this post.

Your checkbox should also need to have an ID to be referenced by the label. Now let’s add the label dynamically using jQuery:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
    $( document ).ready(function() {
        $('input[type="checkbox"].switch').each(function() {
            if ($(this).attr("id")) {
                $('<label for="'+$(this).attr("id")+'"></label>').insertAfter($(this));
            }
        });
    });
</script>

It does the following:

  • Search for all checkboxes to be turned into switches
  • Only change the ones with an ID
  • Add a label immediately after the checkbox referencing the ID of the checkbox

Note that we do not need a special class for the label as we need to make the appearance dependent on the checkbox state anyway so no extra selector for the label is required.

If you want to achieve a solution without JavaScript, you’ll have to add the label after the checkbox manually in the HTML code e.g.:

<input id="check1" class="switch" type="checkbox">
<label for="check1"></label>

I’d rather use JavaScript and CSS pseudo-classes than polute my HTML code with elements which are only needed for styling. But I guess others may see it in a different way and rather use pure CSS and HTML than introduce some JavaScript.

Now we have this all in place, we just need to style the checkboxes and labels. An easier way to do it would be to use a background image. But I tend to avoid using images except for icons. And also if the element has to have a variable size, I definitely try to avoid images. The goal in this post is anyway no do show you how to make nice looking backgrounds with CSS only so we’ll stick to a plain two color background.

The first step is to hide the checkboxes we’ll turn into switches:

input[type="checkbox"].switch {
	display: none;
}

Now we want the label to look like a switch, so have a border, be rounded on the left and right and have a background color depending on its state (e.g. red for not checked and green for checked):

input[type="checkbox"].switch + label {
	background-color: #EE9090;
	border: 1px solid black;
	border-radius: 999px 999px 999px 999px;
	display: inline-block;
	width: 1.6em;
}

For the border radius, you’ll need to add the following to make it work in older browsers (i.e. Firefox < 4.0, Chrome < 5.0, Safari < 5.0, iOS Safari < 4.0 and Android Browser < 2.2):

-moz-border-radius: 999px 999px 999px 999px;
-webkit-border-radius: 999px 999px 999px 999px;

Please also note that IE only supports it starting with version 9.0.

The inline-block is a great invention. For those who still don’t know it, it’s a kind of float on steroids. It means it handles width and height just like a block element. But is displayed inlined with the surrounding elements just like inline elements. So this means that the label will have a width and height although it doesn’t have a text and that the round element we’ll add afterwards will be displayed in it.

Note that we’ve defined the width in ems. This allows us to make the element scale when we increase the font size.

Now, we’ll use pseudo-classes to dynamically add a round element which will be moved from left to right when the checkbox is checked:

input[type="checkbox"].switch + label:before {
	background-color: silver;
	border: 1px solid black;
	border-radius: inherit;
	box-sizing: border-box;
	content: "";
	display: block;
	height: 0.8em;
	width: 0.8em;
}

So the content is empty because we do not want to display some text but only have a background and borders displayed.

box-sizing is used so that the width and height defined do already contain the borders. We want this element to be half the width of the containing label and to be round (so same height and width). Since the width of the label is defined in ems, the height and width of this element also have to be defined in ems. But since we need the border to always be 1 pixel (defining it also in ems makes it look terrible when the size is increased), we should define the width and height to be 0.8em – 1px. This is not possible. So we use box-sizing to say that the 0.8em already contains the border.

Note that box-sizing, requires a prefix for all versions of Firefox (both mobile and desktop) and required a prefix for Safari until version 5.0, for iOS Safari until version 4.3, for Blackberry Browser version 7.0, Android Browser until version 3.0. Also not that Internet Explorer version 7.0.

So you may want to add the following:

-moz-border-radius: inherit;
-web-kit-border-radius: inherit;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;

When the checkbox is checked, we want to change the background color and move the round element to the right. Changing the background color is easy. The trick to move it to the right is to add a padding to the label. But adding the padding will make the label larger so at the same time we have to reduce the width of the label:

input[type="checkbox"].switch:checked + label {
	background-color: #90EE90;
	padding-left: 0.8em;
	width: 0.8em;
}

So now the switch is working ! But we want the transition to be smoother when the checkbox is checked or unchecked. This is done with the transition-duration and transition-property CSS properties:

input[type="checkbox"].switch + label {
	transition-duration: 0.3s;
	transition-property: padding, width;
}

This means that when changing the padding and width the transition will be distributed over 300 milliseconds. You could also add the change of background color but I felt it looks kind of strange because it looks like it changing to some kind of brownish color between red and green. Also, for this CSS property there are also browser specific prefixes:

-webkit-transition-duration: 0.3s;
-moz-transition-duration: 0.3s;
-o-transition-duration: 0.3s;
-webkit-transition-property: padding, width;
-moz-transition-property: padding, width;
-o-transition-property: padding, width;

Now we’re done !

To get a larger switch use the following:

<div style="font-size:200%"><input id="check2" type="checkbox" /></div>

You can find the whole code below:

<html>
        <head>
                <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
                <script type="text/javascript">
                        $( document ).ready(function() {
                                $('input[type="checkbox"].switch').each(function() {
                                        if ($(this).attr("id")) {
                                                $('<label for="'+$(this).attr("id")+'"></label>').insertAfter($(this));
                                        }
                                });
                        });
                </script>
                <style type="text/css">
                        input[type="checkbox"].switch {
                                display: none;
                        }

                        input[type="checkbox"].switch + label:before {
                            background-color: silver;
                            border: 1px solid black;
                            border-radius: inherit;
                            -moz-border-radius: inherit;
                            -web-kit-border-radius: inherit;
                            box-sizing: border-box;
                            -moz-box-sizing: border-box;
                            -webkit-box-sizing: border-box;
                            content: "";
                            display: block;
                            height: 0.8em;
                            width: 0.8em;
                        }

                        input[type="checkbox"].switch:checked + label {
                            background-color: #90EE90;
                            padding-left: 0.8em;
                            width: 0.8em;
                        }

                        input[type="checkbox"].switch + label {
                            background-color: #EE9090;
                            border: 1px solid black;
                            border-radius: 999px 999px 999px 999px;
                            -moz-border-radius: 999px 999px 999px 999px;
                            -webkit-border-radius: 999px 999px 999px 999px;
                            display: inline-block;
                            transition-duration: 0.3s;
                            -webkit-transition-duration: 0.3s;
                            -moz-transition-duration: 0.3s;
                            -o-transition-duration: 0.3s;
                            transition-property: padding, width;
                            -webkit-transition-property: padding, width;
                            -moz-transition-property: padding, width;
                            -o-transition-property: padding, width;
                            width: 1.6em;
                        }
                </style>
        </head>
        <body>
                <input class="switch" id="check1" type="checkbox" />
                <br/><br/>
                <div style="font-size:200%"><input class="switch" id="check2" type="checkbox" /></div>
        </body>
</html>

A simple login dialog with HTML, CSS and jQuery

I’m currently working on a new software and I am working on both an administration page and a home page for the software. I also made a login page. Currently the login page looks kind of strange because it basically only has a few elements and takes up almost the whole screen (except a header, a navigation bar and a footer). So the screen is much too big for the few elements I have in there. So I decided to move the login page to a floating login dialog which is much smaller and thus looks better. Here is a short tutorial how to build such a dialog with HTML, CSS and jQuery.

First, you need to understand what dialogs in this context are. Dialogs are usually implemented in such a way that you have an HTML element floating on top of the page. In order to have it not displayed in the page or below the page, you need to define a z-index which defines where the element is displayed on the Z axis. This is done in CSS with the following:

        .dialog-popup {
            display: none;
            left: 50%;
            position: fixed;
            top: 50%;
            z-index: 99999; 
        }

Here a short explanation about the different CSS properties set above:

  • display: none => the dialog is not displayed when you open the page containing it but only when you click on some login link.
  • left: 50% / top: 50% => we want to have the dialog centered on the screen. This defines that the top left corner of the dialog will be in the middle of the screen. This means we’ll have to add some negative left and top margins when we display the dialog in order to have the middle of the dialog centered (the exact offset of course depends on the height and width of the displayed dialog.
  • position: fixed => this means that the position of the dialog doesn’t depend on the position of other elements but has a fixed position on the screen. Also note that the default value for position is static and with this set, the z-index will be ignored. So it’s important to set it to fixed.
  • z-index: 99999 => All elements with a z-index smaller than 99999 will be displayed below the dialog. If elements have no z-index defined, they inherit the z-index of their parent and if none is defined for any element, they all have a z-index of 0.

To make it look nicer, we’ll also had the following to the CSS for .dialog_popup:

            background: none repeat scroll 0 0 #CCCCCC;
            border: 2px solid #222222;
            border-radius: 3px 3px 3px 3px;
            font-family: Verdana,Geneva,sans-serif;
            padding: 10px;

Note that you may want to add the corresponding browser specific properties for border-radius.

Now let’s first move to the HTML code before we get back to the CSS. We need a link which will display the dialog:

<a href="#" data-selector="#login-dialog" class="dialog-link">Login</a>

The data-selector attribute is a custom attribute which is used to indicate which div ID is to be opened in the dialog when the link is clicked.

Now we’ll define the div for the dialog itself:

<div id="login-dialog" class="dialog-popup">
	<form method="post" action="#">
		<fieldset>
		</fieldset>
		<button class="submit" type="button">Login</button>
	</form>
</div>

To actually open the dialog, we’ll use jQuery. First we’ll load jQuery from Google:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>

Then we’ll define that the dialog with the id defined in the data-selector attribute will be made visible with a fade in effect and its margins will be adapted so that it’s properly centered:

    <script type="text/javascript">
        $(document).ready(function() {
            $('a.dialog-link').click(function() {
                var dialog_id = $(this).attr('data-selector');
                $(dialog_id).fadeIn(200);
                $(dialog_id).css({ 
                    'margin-top' : -($(dialog_id).height() + 4) / 2,
                    'margin-left' : -($(dialog_id).width() + 4) / 2
                });
                return false;
            });
        });
    </script>

To go from the top left corner to the middle of the dialog, we’re defining a negative top and left margin corresponding to half the height and width of the dialog. We also add 4 pixels to the height and width because of the 2 pixels border on all 4 sides of the dialog.

If you want the dialog to be displayed faster, you should reduce the 200 parameter in the fadeIn call. If it’s too fast for you, increase it.

Now we’ll want to add some content to our login dialog:

  • A close button.
  • A username field.
  • A password field.
  • A link for users who forgot their password.

For the close button, I’ll this nice little icon:

close

With these additions, the HTML code for the dialog looks like this:

<div id="login-dialog" class="dialog-popup">
	<a href="#" class="close"><img src="close.png" class="close-button" title="Close this dalog" alt="Close" /></a>
	<form method="post" action="#">
		<fieldset>
			<label for="username">
				<span>Username</span>
				<input type="text" id="username" name="username" placeholder="Username" value=""></input>
			</label>
			<label for="password">
				<span>Password</span>
				<input type="password" id="password" name="password" placeholder="Password" value=""></input>
			</label>
			</fieldset>
		<button class="submit" type="button">Login</button>
		<a href="#" id="forgot-password-link">Forgot your password? Click here.</a>
	</form>
</div>

We’ll need to style it a little bit. First place the close button on the top right corner:

.dialog-popup .close-button {
	float: right;
	margin: -26px -26px 0 0;
}

Then we’ll remove the border and padding from the fieldset:

.dialog-popup form fieldset {
	border: medium none;
	padding: 0;
}

Then we’ll make the labels and input elements look nicer:

.dialog-popup form fieldset label {
	display: block;
	font-size: 1.2em;
}

.dialog-popup form fieldset label span,
.dialog-popup form fieldset label input,
.dialog-popup form button,
.dialog-popup form a {
	display: block;
	margin-bottom: 10px;
}

.dialog-popup form fieldset label input {
	background: none repeat scroll 0 0 #EEEEEE;
	border-color: #FFFFFF #DDDDDD #DDDDDD #FFFFFF;
	border-radius: 3px 3px 3px 3px;
	border-style: solid;
	border-width: 1px;
	color: #000000;
	padding: 6px 6px 4px;
	width: 200px;
}

Style the submit button:

.dialog-popup form button.submit {
	border-color: #000000;
	border-radius: 3px 3px 3px 3px;
	border-width: 1px;
	cursor: pointer;
	margin-top: 10px;
	padding: 6px 6px 4px;
	width: 200px;
}

.dialog-popup form button.submit:hover {
	background-color: #E0E0E0;
}

And the link for those who forgot their password:

#forgot-password-link {
	color: #00AE00;
	font-size: 0.7em;
	text-align: center;
	text-decoration: none;
	width: 200px;
}

Now that everything looks nice, we’ll also add an overlay below the dialog, so that we can make the page under the dialog darker. This way it is clear to the user, that he/she has to focus on the dialog and not the page in the background anymore. So first we need to add a div for the overlay e.g. after the definition of the dialog:

<div id="dialog-overlay" title="Click here to close the dialog">

This overlay is also hidden when the page is first displayed (just like the dialog itself) and will be displayed when the link is clicked. The overlay should cover the whole page, be dark and semi-transparent.

Being hidden by default means:

display: none;

Below the dialog but over of the page:

z-index: 99998;

Covering the whole page:

position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 100%;
width: 100%;
margin-left: auto;
margin-right: auto;

Being dark and semi-transparent:

background: #000;
opacity: 0.75;

This makes the page in the background still visible but not really readable. If it’s too dark for you, just reduce the opacity: 0 means transparent, 1 means opaque (i.e. the page is not visible anymore).

Now that we’ve defined the overlay below the dialog, we need to have it display at the same time as the dialog itself. This is done by adding the following line just above the similar line making the dialog appear:

$('#dialog-overlay').fadeIn(200);

And we want both to disappear when the close button is clicked. Actually we also want both to disappear when you click on the overlay (so outside of the dialog). It is easy to do it with jQuery:

$('.dialog-popup a.close, #dialog-overlay').click(function() { 
	$('.dialog-popup').fadeOut(200);
	$('#dialog-overlay').fadeOut(200);
	return false;
});

And we’re done. The dialog looks like this when it is displayed:

login

In case you do not like puzzle games and would rather see the whole code at once, here it is:

<html>
    <head>
    <style>
        #dialog-overlay {
            background: #000;
            bottom: 0;
            display: none;
            height: 100%;
            left: 0;
            margin-left: auto;
            margin-right: auto;
            opacity: 0.75;
            position: fixed;
            right: 0;
            top: 0;
            width: 100%;
            z-index: 99998;
        }

        .dialog-popup {
            background: none repeat scroll 0 0 #CCCCCC;
            border: 2px solid #222222;
            border-radius: 3px 3px 3px 3px;
            font-family: Verdana,Geneva,sans-serif;
            padding: 10px;
            display: none;
            left: 50%;
            position: fixed;
            top: 50%;
            z-index: 99999; 
        }

        .dialog-popup .close-button {
            float: right;
            margin: -26px -26px 0 0;
        }

        .dialog-popup form fieldset {
            border: medium none;
            padding: 0;
        }

        .dialog-popup form fieldset label {
            display: block;
            font-size: 1.2em;
        }

        .dialog-popup form fieldset label span,
        .dialog-popup form fieldset label input,
        .dialog-popup form button,
        .dialog-popup form a {
            display: block;
            margin-bottom: 10px;
        }

        .dialog-popup form fieldset label input {
            background: none repeat scroll 0 0 #EEEEEE;
            border-color: #FFFFFF #DDDDDD #DDDDDD #FFFFFF;
            border-radius: 3px 3px 3px 3px;
            border-style: solid;
            border-width: 1px;
            color: #000000;
            padding: 6px 6px 4px;
            width: 200px;
        }

        .dialog-popup form button.submit {
            border-color: #000000;
            border-radius: 3px 3px 3px 3px;
            border-width: 1px;
            cursor: pointer;
            margin-top: 10px;
            padding: 6px 6px 4px;
            width: 200px;
        }

        .dialog-popup form button.submit:hover {
            background-color: #E0E0E0;
        }    

        #forgot-password-link {
            color: #00AE00;
            font-size: 0.7em;
            text-align: center;
            text-decoration: none;
            width: 200px;
        }
    </style>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $('a.dialog-link').click(function() {
                var dialog_id = $(this).attr('data-selector');
                $('#dialog-overlay').fadeIn(200);
                $(dialog_id).fadeIn(200);
                $(dialog_id).css({ 
                    'margin-top' : -($(dialog_id).height() + 4) / 2,
                    'margin-left' : -($(dialog_id).width() + 4) / 2
                });
                return false;
            });

            $('.dialog-popup a.close, #dialog-overlay').click(function() { 
                $('.dialog-popup').fadeOut(200);
                $('#dialog-overlay').fadeOut(200);
                return false;
            });
        });
    </script>
    </head>
    <body>
        <a href="#" data-selector="#login-dialog" class="dialog-link">Login</a>
        <div id="login-dialog" class="dialog-popup">
            <a href="#" class="close"><img src="close.png" class="close-button" title="Close this dalog" alt="Close" /></a>
            <form method="post" action="#">
                <fieldset>
                    <label for="username">
                        <span>Username</span>
                        <input type="text" id="username" name="username" placeholder="Username" value=""></input>
                    </label>
                    <label for="password">
                        <span>Password</span>
                        <input type="password" id="password" name="password" placeholder="Password" value=""></input>
                    </label>
                    </fieldset>
                <button class="submit" type="button">Login</button>
                <a href="#" id="forgot-password-link">Forgot your password? Click here.</a>
            </form>
        </div>
        <div id="dialog-overlay" title="Click here to close the dialog">
    </body>
</html>

jQuery: style current page link in navigation

I’m currently working on a web page which has a navigation area in the header and looks like this:

<nav>
    <ol class="main_menu">
        <li><a href="http://localhost/demo/index.php/home" class="active">home</a></li>
        <li><a href="http://localhost/demo/index.php/features">Features</a></li>
        <li><a href="http://localhost/demo/index.php/pricing">Pricing</a></li>
        <li><a href="http://localhost/demo/index.php/support">Support</a></li>
        <li><a href="http://localhost/demo/index.php/blog">Blog</a></li>
        <li><a href="http://localhost/demo/index.php/register">Sign up</a></li>
        <li><a href="http://localhost/demo/index.php/login">Login</a></li>
    </ol>
</nav>

Now I wanted to have the active page somehow highlighted so that the user can clearly see where he is. So first I defined some styles for this highlighting (basically just making it bold and underlined):

<style type="text/css">
    nav .active {
        font-weight: bold;
        text-decoration: underline;
    }
</style>

So what I needed now was some JavaScript to check the currently loaded URL and check which of the navigation links should be highlighted. All URLs start with http://localhost/demo/index.php. So I just needed to check the 4th part of the location path, find the navigation link related to that (so basically the link which ends with the same 4th part). Additionally if you go to http://localhost/demo/index.php you are shown the Home page even though the URL doesn’t end with /home. So I also needed to handle this special case.

So first I needed a JavaScript function to get the last part of the URL. Here I just needed to split the path using a slash as a separator and get the last one. There is just one exception: if the URL ends with a slash, I do not get the last one but the second to last (so that I do not get an empty result):

function getLastPart(url) {
    var parts = url.split("/");
    return (url.lastIndexOf('/') !== url.length - 1 ? parts[parts.length - 1] : parts[parts.length - 2]);
}

Now that we have it, we’ll get the last part of the URL and activate the link which ends with this last part. Additionally if the last part is index.php, we’ll highlight the Home link:

$(function() {
    var lastpart = getLastPart(window.location.href);
    $('nav a[href$="/' + lastpart + '"]').addClass('active');
    if (lastpart == "index.php") {
        $('nav a[href$="/home"]').addClass('active');
}

About the jQuery selector for the link selection:

nav a[href$="/home"]

It basically says to find a link in the navigation (i.e. nav a) having a href attribute (i.e. nav a[href…]) ending with (i.e. href$=) “/home”.

Of course if your URLs look different e.g. if you have query parameters or it is not the last part of the URL which is relevant for the link selection, you’ll need to adapt the getLastPart function. If it is not the last part of the URL which is relevant, you’ll also need to use a different selector.

Here the complete JavaScript:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" type="text/javascript">
<script type="text/javascript">
<!--
$(function() {
    var lastpart = getLastPart(window.location.href);
    $('nav a[href$="/' + lastpart + '"]').addClass('active');
    if (lastpart == "index.php") {
        $('nav a[href$="/home"]').addClass('active');
}
  });
function getLastPart(url) {
    var parts = url.split("/");
    return (url.lastIndexOf('/') !== url.length - 1 ? parts[parts.length - 1] : parts[parts.length - 2]);
}
//-->
</script>

How to build a simple table filter with jQuery

I needed to have a very lightweight and easy to use javascript table filter. The idea was to add a second row to the table header (below the column names) and have there a text input in each cell of this new row. When you write something in there and press Return, it will filter out all rows not containing this string in this particular column. You should also be able to combine filters for different columns.

Let’s implement it as a jQuery plugin. First we need a name… We’ll call the file jquery.filtertable.js and the function we define in the plugin will be filtertable:

(function($) {
    $.fn.filterTable = function(filter, columnname) {
        return this;
    };
})(jQuery);

We have two parameters:

  1. First, the string used to filter the table
  2. Second, the name of the column where the filter string will be searched for

Of course using the column name is not always a good idea. It’s be better to use an ID or an index. An index is kind of difficult to use especially if columns are moved around. And since the column might not have an ID but will most probably have a name/header, I went for the name.

So the first thing we need to do is to map the column name to an internal index:

var index = null;
this.find("thead > tr:first > th").each(function(i) {
    if ($.trim($(this).text()) == columnname) {
        index = i;
        return false;
    }
});

So here I make a few assumptions:

  • The table has a theader
  • The first row in the this header is the one we’re interested in

If we do not find the column, we just throw an exception:

if (index == null)
    throw ("filter columnname: " + columnname + " not found");

Then we loop through the rows, get the text of the corresponding cell and compare it with the filter. If it contains the filter, the row is shown otherwise hidden:

this.find("tbody:first > tr").each(function() {
    var row = $(this);
    var cellText = $(row.find(("td:eq(" + index + ")"))).text();
    if (cellText.indexOf(filter) == -1) {
        row.hide();
    }
    else {
        row.show();
    }
});

Note that I here also assume you have a <tbody> tag. If not, you’ll have to modify the plugin.

There are 2 additional pieces of functionality we’ll want to add:

  • If the filter string is empty, we show everything
  • If the cell contains a select box, we want to use the text of the selected entry and cell text

First the empty filter showing all rows:

this.find("tbody:first > tr").each(function() {
    var row = $(this);
    if (filter == "") {
        row.show();
    }
    else {
        var cellText = $(row.find(("td:eq(" + index + ")"))).text();
        if (cellText.indexOf(filter) == -1) {
            row.hide();
        }
        else {
            row.show();
        }
    }
});

Now handle select box:

this.find("tbody:first > tr").each(function() {
    var row = $(this);
    if (filter == "") {
        row.show();
    }
    else {
        var cellText = row.find("td:eq(" + index + ")").find('option:selected').text();
        if (cellText == "") {
            cellText = $(row.find(("td:eq(" + index + ")"))).text();
        }
        if (cellText.indexOf(filter) == -1) {
            row.hide();
        }
        else {
            row.show();
        }
    }
});

Now the whole code:

(function($) {
	$.fn.filterTable = function(filter, columnname) {
		var index = null;
		this.find("thead > tr:first > th").each(function(i) {
			if ($.trim($(this).text()) == columnname) {
				index = i;
				return false;
			}
		});
		if (index == null)
			throw ("filter columnname: " + columnname + " not found");

		this.find("tbody:first > tr").each(function() {
		    var row = $(this);
		    if (filter == "") {
		        row.show();
		    }
		    else {
		        var cellText = row.find("td:eq(" + index + ")").find('option:selected').text();
		        if (cellText == "") {
		            cellText = $(row.find(("td:eq(" + index + ")"))).text();
		        }
		        if (cellText.indexOf(filter) == -1) {
		            row.hide();
		        }
		        else {
		            row.show();
		        }
		    }
		});
		return this;
	};
})(jQuery);

You can download the file here.

To use it, simply add the following to your <head> section:

<script type='text/javascript' src='jquery.tablefilter.js'></script>

Then when you want to filter the table, just do the following:

$("#your-table-id").filterTable('filter_text', 'column_name');