jQuery UI Widget Factory Live Binding

I recently developed my first jQuery UI Custom Widget using the jQuery UI Widget Factory. I found the Widget Factory to be a nice framework for what I needed to do and took away a bit of the complexity normally needed for a UI plugin.

It did introduce a level of complexity for live event binding. For example, I want all dynamically generated anchor tags to run a method when clicked.

The Widget Factory wants us to use the “this._on” method to accomplish this. The Widget Factory is a little undocumented so it was hard to figure this one out.

At first I tried the following:

$.widget( "CFWT.myWidget", {

	_create: function() {
		this._on('.myClass', {
			click: function(event) {
				var $container = $(event.target);
				doSomething($container);
			}
		});
	}
	
	_doSomething: function(container) {
		console.log(container);
	}

});

However I quickly found out that the event was only bound with any classes created the first time. After some Q&A on Stack Overflow I finally figured out the correct way.

$.widget( "CFWT.myWidget", {

	_create: function() {
		this._on(this.element, {
			'click.myClass': function(event) {
				var $container = $(event.target);
				this._doSomething($container);
			}
		});
	}
	
	_doSomething: function($container) {
		console.log($container);
	}

});

This new code will bind any “myClass” class that is created inside the widget’s container. If you want it document wide then replace “this.element” with “document” or another container.

You may also notice that I use “$(event.target)” instead of “this”. That is because “this” doesn’t change to the event. Instead, I believe, it remains at the widget level, which can be pretty confusing.

A working example can be found at http://jsfiddle.net/vEAhq/8/

Advertisements

#bind, #event, #jquery, #live, #ui, #widget

jQuery Issue With Multiple Selectors and clone()/appendTo()

On a current project, the website was and many interactive jQuery “modules” where supplied by a 3rd party designer. We I pieced it together with ColdFusion everything worked great on IE8+, FF and Chrome. However it was soon discovered that a large customer of the client couldn’t upgrade past IE7 at the moment. After running the JavaScript through jsLint, that fixed the majority of the issues. Heck, just a couple of commas out of place will make a nightmare out of IE7.

However there was an issue that took me a whole day to figure out. (We’re talking thousands of lines of code to debug with no error being thrown).

As a general description, the site is a customized e-commerce site in its basic functionality. It has a category navigation tree to the left, with a product result grid to the right. When an item on the grid is clicked, the grid is hidden and replaced by a product detail section populated via AJAX.

First of all, there where around 1,500 categories that where populated into the DOM on page load via an un-ordered list. It was then converted into a cascading tree. The way you’re supposed to do it for accessibility and SEO purposes right? Well, IE7 didn’t take too kindly to that and took about a whole minute to display. IE8+ took probably around a second.

To resolve this issue I created a jQuery UI Widget via the Widget Factory. This required upgrading the version of jQuery being used, and I chose the latest one at 1.9.1 that still supported IE7.

After the tree was created and implemented, it became clear that only one product in the product grid was showing as opposed to the 15 for each page.

I determined that jQuery 1.7.2 loaded the grid as expected, but both 1.8 and 1.9 versions only loaded one. The method used to populate the grid was to clone a hidden div, populate it, append it to the parent container and make it visible using data from an AJAX JSON data set. It was looped via an $.each() method.

Because there where no errors being thrown, and I needed that newer version of jQuery I started the process of elimination. That process took me to the bitter end. I found that I couldn’t use multiple elements inside a selector. For example:

$('#myDIV1,#myDIV2').hide();

The above would inhibit all but one cloned container to be displayed. The workaround was to split up the selectors:

$('#myDIV2').hide();
$('#myDIV2').hide();

This would only happen in IE7. IE8+, FF, and Chrome worked just fine. It also only happened in jQuery 1.8+.

I could reproduce this any time, ripping out most of the app down to the essentials.

However at this time, creating a basic app in another virtual site works just fine. I may try to figure it out at a later time, but right now I can’t duplicate the issue.

But I’m writing this in case anyone runs into this edge case and needs a workaround. Hopefully I can find the cause and submit a bug to the jQuery team.

#appendto, #clone, #ie7, #internet-explorer, #jquery, #ui

img Element error Event

If you develop and push to production, you most likely have experienced broken images on your dev instance. You may have also run into broken images on production due to user/admin error, etc. Wouldn’t it be nice if you could display a default placeholder image without having to take a hit on your file system, checking to see if it exists, before your load each image?

Thanks to Ryan Stille’s recent blog post, I have been made aware of a (not so great) solution. Apparently the img tag, along with others such as object, script and style have error events. We can listen for the error event and load a placeholder image in place of the default browser error image.

The catch is the event handler must be attached before the browser fires the error event. Also, the error event may not be correctly fired when the page is served locally. The error event relies on HTTP status codes and will generally not be triggered if the URL uses the “file:” protocol.

In simpler terms, the only solution I’ve found is to either place the event handler inline with the img tag, assign the img src via JavaScript or recursively search each image’s complete state or naturalWidth once the window is done loading. I’ve tried using “$().on()” and “$(“img”).error()” both after the document loads and inline before the element is loaded. However neither solution works, which doesn’t make much sense to me.

I am including multiple examples because this is not a one-solution-fits-all scenario.

The first working example displays a placeholder image using the inline method if the error event is thrown. Notice the onerror handler is reset when it’s run so you don’t run into an infinite loop if your placeholder image also fails.

<img src="images/myImage.jpg" alt="My Image" onerror="imageError(this)">

<script>
function imageError(element) {
    element.onerror='';
    element.src='/images/imgPlaceholder.png';
}
</script>

The second working example, also using the inline method, will call a script that will report the broken image and load the placeholder. The script returns an image with the proper image MIME type.

<img src="images/myImage.jpg" alt="My Image" onerror="imageError(this)">

<script>
function imageError(element) {
    element.onerror='';
    element.src='logBrokenImage.cfm?image=' + element.src';
}
</script>

The third working example uses JavaScript to load the image and displays a placeholder if that image fails to load.

<img id="myImage">

<script>
    $('img').one( 'error', function() {
        $(this).attr( 'src', '/images/imgPlaceholder.png' );
    });

    $('#myImage').attr( 'src', 'myImage.jpg' );
</script>

The final working example recursively searches through each image after the window loads. If it finds the state incomplete or the natural width of the image is 0, then it loads the placeholder image.

<img src="images/myImage.jpg" alt="My Image">

<script>
$(window).load(function() {
    $('img').each(function() {
        if ( !this.complete || ( !$.browser.msie && ( typeof this.naturalWidth == "undefined" || this.naturalWidth == 0 ) ) ) {
            this.src = '/images/imgPlaceholder.png';
        }
    });
});
</script>

#error, #event, #handler, #img, #javascript, #jquery, #src

jQuery UI Dialog Centering Issue

I’m working with some code that’s been worked over many, many times over the years. It could a thorough scrubbing, but that’s not in the scope of my request.

I just upgraded the jQuery UI to version 1.9.2 and jQuery to 1.8.3 and added a simple dialog.

test_dialogIt was expected that the dialog would center itself to the browser window. However it appeared to be centering itself to the html element and scrolling the page to make it centered if the content was longer than the window height.

Here’s the code:

<html>
<head>
    <title>My Title</title>
</head>
<body>
    <div id="userNoticeDialog" style="display: none;" title="Notice">
        test content
    </div>

    <script type="text/javascript">
        $("#userNoticeDialog").dialog();
    </script>
</body>
</html>

After a process of elimination and seeing that there was no doctype declared, I tried adding a doctype. That resolved the issue.

<!DOCTYPE html>
<html>
<head>
    <title>My Title</title>
</head>
<body>
    <div id="userNoticeDialog" style="display: none;" title="Notice">
        test content
    </div>

    <script type="text/javascript">
        $("#userNoticeDialog").dialog();
    </script>
</body>
</html>

I believe the reason was the lack of a DOCTYPE caused the (Chrome) browser to go into quirks mode, while the inclusion of the DOCTYPE caused the (Chrome) browser to go into standards mode. However, I’m not sure how to detect which mode the Chrome browser is currently in. Does anyone know?

#center, #dialog, #doctype, #html, #jquery, #ui

Handling Expired Sessions via AJAX & FW/1

This is a followup to my “Framework One AJAX Method (FW/1)” post (https://christierney.com/2012/07/14/framework-one-ajax-method-fw1/).

Scenario:

  1. You use the session scope to define if a user is logged in or not
  2. You use jQuery AJAX to pull JSON data from FW/1 action URL’s
  3. The user’s session has expired after x minutes of inactivity after login
  4. If the session is expired the user is directed to a login page after trying to navigate

So what happens in this scenario? Instead of the expected JSON data your AJAX call receives the HTML of a login page with a status of 200. Can’t do too much with this.

Here’s a code example that will pass the client a 403 error (Forbidden) in the header and return no content. jQuery will then redirect the user to a login screen when it sees this status code.

First here’s a simlified FW/1 Application.cfc setupRequest() method:

void function setupRequest() {

	var reqData = getHTTPRequestData();

	if( structKeyExists( reqData.headers, 'X-Requested-With' ) && reqData.headers[ 'X-Requested-With' ] == 'XMLHttpRequest' && !structKeyExists( session, 'user' ) ) {
		getpagecontext().getresponse().setstatus( 403 );
		abort;
	}
}

This code detects if the call came from an AJAX request ( getHTTPRequestData().headers.X-Requested-With = ‘XMLHttpRequest’ ) and if the session still knows about the user. If it is an AJAX request and the user is not known, then set the status code of the return page to 403 and stop processing any more code. If you try to use throw instead of abort, it will overwrite the status code to 500.

The second simple example is the jQuery piece:

$( document ).ready( function() {

	$( this ).ajaxError( function( e, jqXHR, settings, exception ) {
		if( jqXHR.status == 403 ) {
			location.href = '?logout';
			throw new Error( 'Login Required' );
		} else if( !jqXHR.statusText == 'abort' && jqXHR.getAllResponseHeaders() ) {
			alert( 'There was an error processing your request.\nPlease try again or contact customer service.\nError: ' + jqXHR.statusText );
		}
	});

});

Here we are globally looking at all AJAX requests. Since the status code 403 is in the error class it will throw an error. The .ajaxError() method picks up this error and handles it.

If the status code is detected as a 403 (which we set in our ColdFusion code) then we direct the user to a logout page (which in turn directs to a login page) and throws a JS error. The throw statement is supposed to stop all JS processing, however if you have an error handler attached to the specific AJAX call, then that will still fire. The error message will just be seen if you are viewing the JS console.

If there’s another error caught it first looks to see if the request was aborted or if the user navigated away from the page. In these two cases I don’t want to display an error. If anything else is caught, I display a generic message.

#ajax, #coldfusion-2, #fw1, #jquery, #session

Hide A DOM Container Clicking Outside It Using jQuery

Let’s say we have a DIV container with some content in it. We want to hide that container if the user clicks outside its bounds. Pretty simple solution:

$( document ).mouseup( function( e ) {
    if( $( "#myDiv" ).has( e.target ).length === 0 ) {
        $( "#myDiv" ).hide();
    }
});

#click, #container, #div, #dom, #hide, #jquery

Programmatically Animated Page Scroll with jQuery

With the increasing use of AJAX to populate a page without reloading it, you come to find out that you’d like to get your users back to a certain point in the page, if not the top. If you where to navigate to a new page via the browser, you always end at the top. But when just replacing a container with new information this doesn’t happen. Depending upon your scenario, this may be a good idea for usability.

The following code snippet is a simple example. It will detect if your user is below 210 pixels on the screen. If you are then it will animate the page, scrolling it up to the 210px mark. You may want to use an extended jQuery .position() method to find the top of your #result container instead of using a static page height value.

$.get( 'ajax/test.html', function( data ) {
    $( '#result' ).html( data );

    if ( $( window ).scrollTop() > 210) {
        $( "html, body" ).animate({
            scrollTop: 210
        }, "slow" );
    }
});

#jquery, #scroll