Tag Archives: javascript

AjaxQueue and jQuery 1.3

800px-Queue_algorithmn The website that I work on for the majority of my time had been running on the rather out of date jQuery 1.2 for quite some time. I recently found myself with a little bit of downtime and decided it was a good time to see if we could upgrade and take advantage of all the speed improvements that the latest version gives us.

On the most part, the upgrade was fairly painless. Most things worked without change but one particular plug-in did not – AjaxQueue. This is a great little control that acts as an add-on to the ajax method of jQuery, allowing a little more control over how concurrent calls are processed. It gives three modes of operation but for us, the most useful of these are abort and queue and from the names it is fairly obvious what they do.

After I upgraded to jQuery 1.3, it did not take me long to realise that the queue mode was broken. I briefly looked around for an alternative plug-in but found that all of them would require too much change to our existing controls. There was no alternative, I had to open up the hood of AjaxQueue and see what had gone wrong.

What I found was that there must of been a change to a method that the plug-in relied on – a method that it seemed, only became publicly available in version 1.3 – queue. Although the function was similar, the conclusion that I came to was that prior to version 1.3, the queue function automatically de-queued any function added and processed it. When using the latest jQuery, the ajax calls were queued but there was no code to start the processing of the queue.

It was slightly more complex I thought it would be to get it going – I had to ensure the queue was only started once. I finally came up with a solution and a newly revived plug-in, the code of which is below. As the original plugin has not been touched for quite some time (2007), I thought it ok to post my modified version here.

(function($) {
 
    var ajax = $.ajax,
        pendingRequests = {},
        synced = [],
        syncedData = [],
        ajaxRunning = [];
 
 
    $.ajax = function(settings) {
        // create settings for compatibility with ajaxSetup
        settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
 
        var port = settings.port;
 
        switch (settings.mode) {
            case "abort":
                if (pendingRequests[port]) {
                    pendingRequests[port].abort();
                }
                return pendingRequests[port] = ajax.apply(this, arguments);
            case "queue":
                var _old = settings.complete;
                settings.complete = function() {
                    if (_old) {
                        _old.apply(this, arguments);
                    }
                    if (jQuery([ajax]).queue("ajax" + port).length > 0) {
                        jQuery([ajax]).dequeue("ajax" + port);
                    } else {
                        ajaxRunning[port] = false;
                    }
                };
 
                jQuery([ajax]).queue("ajax" + port, function() {
                    ajax(settings);
                });
 
                if (jQuery([ajax]).queue("ajax" + port).length == 1 && !ajaxRunning[port]) {
                    ajaxRunning[port] = true;
                    jQuery([ajax]).dequeue("ajax" + port);
                }
 
                return;
            case "sync":
                var pos = synced.length;
 
                synced[pos] = {
                    error: settings.error,
                    success: settings.success,
                    complete: settings.complete,
                    done: false
                };
 
                syncedData[pos] = {
                    error: [],
                    success: [],
                    complete: []
                };
 
                settings.error = function() { syncedData[pos].error = arguments; };
                settings.success = function() { syncedData[pos].success = arguments; };
                settings.complete = function() {
                    syncedData[pos].complete = arguments;
                    synced[pos].done = true;
 
                    if (pos == 0 || !synced[pos - 1])
                        for (var i = pos; i < synced.length && synced[i].done; i++) {
                        if (synced[i].error) synced[i].error.apply(jQuery, syncedData[i].error);
                        if (synced[i].success) synced[i].success.apply(jQuery, syncedData[i].success);
                        if (synced[i].complete) synced[i].complete.apply(jQuery, syncedData[i].complete);
 
                        synced[i] = null;
                        syncedData[i] = null;
                    }
                };
        }
        return ajax.apply(this, arguments);
    };
 
})(jQuery);

Cuesheet Maker Bookmarklet for Discogs.com

Yesterday I discovered I had a few albums that had been ripped as a single track. I prefer having individual tracks and usually head on over to cuesheet heaven to look for the appropriate file that allows me to split them up with the awesome Medieval Cue Splitter. However I had a few tracks that had I could not find the appropriate cuesheet file for. I find myself in this situation fairly regularly and have also noticed that these albums usually have an entry at discogs.com, which has all the details including track lengths.

This got me thinking that it would be awesome to be able to create cue files from discogs.com. So I created the following – a bookmarklet to do just that.

Disclaimer: it is pretty rough and I cannot guarantee that it will work with all discogs.com entries and there is hardly any error checking. I have only used this in Firefox so I cannot guarantee it will work in any other browser. It only pops up a dialogue with the cuesheet contents on it but that was adequate for me to copy and paste it into a text file and tweak it so that it was a valid cuesheet.

The important thing is that it gathers the track names and artists and works out the index times. It also attempts to cope with double cds and also single tracks that are two different records playing at once (common with DJ mixes). Here is the bookmarklet (drag it into your bookmarks tookbar), followed by the code itself:

Drag this to your bookmark toolbar: Create Cue

The code itself:

javascript:(function(){var s=document.createElement('SCRIPT');
s.type='text/javascript';
s.src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js';
s.onload=function() {
	var title = jQuery.trim(jQuery('div.profile h1').text()),
		performer = jQuery.trim(title.substring(0,title.indexOf('-'))),
		file = title + '.mp3', secCount = 0, trkCount = 0, cues = [], cdNo = 1;
	title = jQuery.trim(title.substring(title.indexOf('-')+1));
	tracks = [];
	jQuery('div.tracklist .section_content > table tr').each(function(ix, el) {
		var trackCd = jQuery.trim(jQuery('td.track_pos',this).text());
		if (trackCd && trackCd != '') {
			var track = {}, tracktime = [], isDouble = false;
			trackCd = trackCd.split('-');
			if (trackCd.length > 1) {
				if (jQuery.trim(trackCd[0]) != cdNo) {
					createCue();
					cdNo = trackCd[0];
				} else {
					if (parseInt(trackCd[1]) == trkCount) {
						isDouble = true;
					}
				}
			}
			if (!isDouble) {
				trkCount+=1;
				track['trackno'] = trkCount;
				track['artist']= jQuery.trim(jQuery('td.track_artists',this).text()).replace(/\s-$/g, '').replace(/\n/g, '').replace(/\s+/g,' ');
				track['artist'] = jQuery.trim(track['artist']);
				track['artist'] = (track['artist'] == '') ? performer : track['artist'];
				track['title'] = jQuery.trim(jQuery('td.track_title',this).text()); 
				tracktime =jQuery.trim(jQuery('td.track_duration',this).text()).split(':'); 
				track['index'] = secondsToMinSec(secCount);
				secCount += (tracktime[0] * 60) + (tracktime[1] * 1);
				tracks[tracks.length] = track;	
			}
		}
 
	});
 
	createCue();
 
	//print it out
	var output = '';
	jQuery.each(cues,function(ix, cue) {
		output += cue + '\n\n\n';
	});
 
	alert(output);
 
	function createCue() {
		var quo = unescape('%22'),
			cue = 'TITLE '+quo+title+quo+'\n';
		cue += 'PERFORMER '+quo+performer+quo+'\n';
		cue += 'FILE '+quo+file+quo+' MP3\n';
		jQuery.each(tracks,function(ix, track) {
			cue += '  TRACK '+track.trackno+' AUDIO\n';
			cue += '    TITLE '+quo+track.title+quo+'\n';
			cue += '    PERFORMER '+quo+track.artist+quo+'\n';
			cue += '    INDEX 01 '+quo+track.index+quo+'\n';
		});
		cues[cues.length] = cue;
		//reset
		secCount = 0; trkCount = 0; tracks = [];
	}
 
	function secondsToMinSec(totsec) {
		var min = Math.floor(totsec / 60), 
			sec = (totsec - (min * 60));
		if (min < 10) {
			min = '0' + min.toString();
		}
		if (sec < 10) {
			sec = '0' + sec.toString();
		}
		return min.toString() + ':' + sec.toString() + ':00';
	}
};
document.getElementsByTagName('head')[0].appendChild(s);})();

I hope this helps some people, as it has me. Feel free to improve on it but if you make any changes, please let me know so that I can improve my version too.

Enjoy

Edit: Well it was not long until discogs changed their layout. I have update the code to reflect this. All should be well again.

Thanks for Breaking Things ASP.NET 3.5 Team

broken_glass I have just spent an intense few hours crawling all the JavaScript files in our application (and there are a quite a few) fixing an issue introduced with our upgrade to the .NET framework 3.5. The entire afternoon in fact was spent fixing a single issue: our AJAX calls had stopped working.

The first hurdle was due to a slightly off-the-norm setup we have for our website. In order to save replicating code across the various websites that make up our solution, we opted for a common virtual directory that serves common assets like JavaScript, images and so on. With a little jiggery pokery, it also allows us to use shared .ascx controls. When we upgraded to the 3.5 framework, the upgrade wizard scans the projects and changes any references that should be pointing the new 3.5 versions. Not all references were changed, as the latest framework sits on top of the 2.0 framework, rather than replaces it. Unfortunately, the upgrade wizard could not figure out that our virtual directory project was not an application at all, so it created an unneeded web.config for it . However, removing that made little difference and it took me quite a while until I found that there were additional script handler settings added for framework 3.5 that were causing the errors. We chose jQuery as the backbone of our JavaScript development and only need to have the .ascx web service handler.

As soon as I removed the unwanted handlers, our AJAX calls sprung into life but still none of our AJAX powered controls worked. A quick look with Firebug revealed the reason. All our JSON responses where wrapped in a {d: } object. A little searching on the interweb and the reason was revealed. As a security measure, wrapping the response in that way can stop cross-site scripting attacks.

So why am I annoyed? Well, I cannot fault the reason behind the change, it stops a potentially nasty attack. However, we had no choice in the matter. It broke every one of our AJAX powered controls. Just because Microsoft had handled this change in their own AJAX controls, they decided that nobody else would be using the JSON serializer for anything else. As such, they seem to have no configuration setting to turn it off. We had been running the 2.0 AJAX extension with that vulnerability in it for a while, so I think we should at least be allowed to choose whether we continue that way. Instead, I have to spend hours checking all the JavaScript file for AJAX calls and making them look for their data inside the d object.