I’d been looking for a way of using the jQuery UI datepicker the same way that the Google Analytics datepicker works. Unfortunately I hadn’t found any solutions that I really liked. After a little more searching I found a tantalising piece of code that is already part of the jQuery UI Date Picker.

The piece of code is actually and Option, in this instance beforeShowDay. As the name suggests this piece of code is executed before the day is drawn. What I’ve done is pass the date of the day being drawn into the function and compared the date with some values I recorded earlier.

Based on some simple logic an array is returned (see the jQuery UI API documentation for more details), this important part for us is the class being added to the day (red for one selected period, blue for the second and split if any of the dates intersect.).

Now is this is great I can draw different coloured selected regions based on some stored dates. Now to make the ranges changeable.

I went back to the dates I was already storing. I knew that using the onSelect option I could use the date returned to my advantage. I stored the selected date in the object I’m using to hold the date ranges. But how to define the start and end?

I created a couple of input fields (a start and end) and a variable to track which field was being populated. On select the “selected field” is highlighted and the corresponding element in the date object is filled. Once the selection of a date is complete the focus is advanced to the next field.

The next step was simple I added a checkbox whose state determined if we were populating the first date range or another and added a couple more input boxes.

The resulting code you can find below and you can download a working example of the date picker. I think it’s a good starting point and you may wish to expand it’s functionality.

var dateRanges={
		"StartDate": new Date( 2013,11,18 ),
		"EndDate": new Date(2013,11,25), 
		"CompStartDate": new Date(2013,11,10 ),
		"CompEndDate": new Date(2013,11,17)
	};
var selection=1;
var range=1;
var compare=0;
var textMonths=new Array( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );

jQuery(document).ready(function(){

	jQuery("#selectDates").datepicker({
					
		changeMonth: true,
		dateFormat: 'yy/mm/dd',
		numberOfMonths: 3,
		beforeShowDay: function (date){
						
			if (date >= dateRanges.StartDate && date <= dateRanges.EndDate ) 
			{
								
				if (date >= dateRanges.CompStartDate && date <= dateRanges.CompEndDate && compare==1 )
				{
								
					return [true, 'split', ''];
								   
				}
								
				return [true, 'red', ''];
								
			}
							
			if (date >= dateRanges.CompStartDate && date <= dateRanges.CompEndDate && compare==1 ) 
			{
							
				if (date >= dateRanges.StartDate && date <= dateRanges.EndDate )
				{
								
					return [true, 'split', ''];
								   
				}                
								
				return [true, 'blue', ''];
								
			}       
							
			return [true, '', ''];
							
		},
		onSelect: function( selectedDate ){

			parts=selectedDate.split('/');

			if ( selection==1 )
			{

				if ( range==1 )
				{

					dateRanges.StartDate = new Date( parts[0], (parts[1]-1), parts[2] );

					jQuery('#fldRangeStart').val( dateRanges.StartDate.getDate()+' '+textMonths[dateRanges.StartDate.getMonth()]+' '+parts[0] );

				}
				else
				{

					dateRanges.CompStartDate = new Date( parts[0], (parts[1]-1), parts[2] );

					jQuery('#fldCompStart').val( dateRanges.CompStartDate.getDate()+' '+textMonths[dateRanges.CompStartDate.getMonth()]+' '+parts[0] );                    

				}

			}

			if ( selection==2 )
			{
						
				if ( range==1 )
				{
				
					dateRanges.EndDate = new Date( parts[0], (parts[1]-1), parts[2] );

					jQuery('#fldRangeEnd').val( dateRanges.EndDate.getDate()+' '+textMonths[dateRanges.EndDate.getMonth()]+' '+parts[0] );

				}
				else
				{

					dateRanges.CompEndDate = new Date( parts[0], (parts[1]-1), parts[2] );

					jQuery('#fldCompEnd').val( dateRanges.CompEndDate.getDate()+' '+textMonths[dateRanges.CompEndDate.getMonth()]+' '+parts[0] );

				}

			}

			selection=selection==1 ? 2 : 1;

			if ( selection==2 )
			{

				if ( range==1 )
				{

					jQuery('#fldRangeEnd').addClass('focusBorder');  
					jQuery('#fldRangeStart').removeClass('focusBorder');

				}
				else
				{

					jQuery('#fldCompEnd').addClass('focusBorder');
					jQuery('#fldCompStart').removeClass('focusBorder');

				}

			}
			else
			{

				if (range==1 )
				{

					jQuery('#fldRangeStart').addClass('focusBorder');
					jQuery('#fldRangeEnd').removeClass('focusBorder');

				}
				else
				{

					jQuery('#fldCompStart').addClass('focusBorder');
					jQuery('#fldCompEnd').removeClass('focusBorder');

				}
			
			}
			
		}

	});

	jQuery('#fldRangeStart').click(function(e){

		e.preventDefault();
		selection=1;
		range=1;
		jQuery('#fldRangeStart').addClass('focusBorder');  
		jQuery('#fldRangeEnd,#fldCompStart,#fldCompEnd').removeClass('focusBorder');

	});

	jQuery('#fldRangeEnd').click(function(e){

		e.preventDefault();
		selection=2;
		range=1;
		jQuery('#fldRangeEnd').addClass('focusBorder');  
		jQuery('#fldRangeStart,#fldCompStart,#fldCompEnd').removeClass('focusBorder');

	});    

	jQuery('#fldCompStart').click(function(e){

		e.preventDefault();
		selection=1;
		range=2;
		jQuery('#fldCompStart').addClass('focusBorder');  
		jQuery('#fldCompEnd,#fldRangeStart,#fldRangeEnd').removeClass('focusBorder');

	});

	jQuery('#fldCompEnd').click(function(e){

		e.preventDefault();
		selection=2;
		range=2;
		jQuery('#fldCompEnd').addClass('focusBorder');  
		jQuery('#fldCompStart,#fldRangeStart,#fldRangeEnd').removeClass('focusBorder');

	});    

	jQuery('#fldRangeStart').addClass('focusBorder');			
				
	jQuery('#fldCompare').click(function(){
				
		compare=compare==1 ? 0 : 1;
				
		if ( compare==1 )
		{

			jQuery('.compfields').show();
			jQuery('#selectDates').datepicker('refresh');
			
		}
		else
		{
		
			jQuery('.compfields').hide();
			jQuery('#selectDates').datepicker('refresh');
			
		}
				
	});

	jQuery('#fldRangeStart').val( dateRanges.StartDate.getDate()+" "+textMonths[dateRanges.StartDate.getMonth()]+" "+dateRanges.StartDate.getFullYear() );
	jQuery('#fldRangeEnd').val( dateRanges.EndDate.getDate()+" "+textMonths[dateRanges.EndDate.getMonth()]+" "+dateRanges.EndDate.getFullYear() );
	jQuery('#fldCompStart').val( dateRanges.CompStartDate.getDate()+" "+textMonths[dateRanges.CompStartDate.getMonth()]+" "+dateRanges.CompStartDate.getFullYear() );
	jQuery('#fldCompEnd').val( dateRanges.CompEndDate.getDate()+" "+textMonths[dateRanges.CompEndDate.getMonth()]+" "+dateRanges.CompEndDate.getFullYear() );				
	
	jQuery('.compfields').hide();
	
});
.red a
{

	background: #ff0000 none 0 0 no-repeat !important;

}

.blue a
{

	background: #0000ff none 0 0 no-repeat !important;
	color: #fff !important;

}

.split a
{

	background: #ff00ff none 0 0 no-repeat !important;

}
		
.datedsps
{

	width: 75px;
	font-size: 11px;

}

.focusBorder
{

	border: 2px solid #ff0000;

}
-

Compare Previous Period

-

Filed under: Programming — Tags: , , , , , , — admin @ 2:50 pm

A recent design I had to build required that each list item in a menu (WordPress) be a different colour. After some head scratching I came up with a method using jQuery.

We start off be making sure our counter is 0. Then loop through each list item. We can get the current item number from jQuery and which element we are currently acting on. Setting the colour variable to be 1 + i means we count from 1 up to the total number of list items. I’m using modulo operator to loop the numbers after 8. Using jQuery we can then add class to the current element. The class could contain any CSS operation. I used this for a spectrum of colours.

    colour=0;
	
	$('.nav ul li').each( function(i, elem) 
	{
	
		colour=1+(i%8);
	
		$(elem).addClass('listitem'+colour);
			
	});
Filed under: Programming — Tags: , , , — admin @ 8:29 pm

Using jQuery and CSS we can change the colour of alternate rows in a table. The code below will lock on to each table row and change the background colour on alternate rows. I’m making the change with CSS but you could always add a CSS class instead.

    jQuery('table tr').each(function(i, domEle)
	{
	
         /* skip the first row (probably your headings) */
	  if ( i!=0 )
	  {
	  
                /* if i / 2 has no remainer set the background to this */ 
		if ( i%2==0 )
		{
	  
	      jQuery( domEle ).css('background-color','#dcddde');
		
		}
		else  /* if i / 2 does have a remainder change the colour to this */
		{
		
		  jQuery( domEle ).css('background-color','#f3f3f4');
		
		}
	  
	  }
	
	});

If you want add a class instead replace


jQuery( domEle ).css('background-color','#f3f3f4');

/* with */

jQuery( domEle ).addClass('odd');
Filed under: Programming — Tags: , , , , , , — admin @ 8:19 pm

If you’re trying to include YouTube videos or Google maps, in fact anything that uses an IFRAME in your WordPress you’ve probably run into problems. The visual view in WordPress removes the IFRAME code. Well I have a solution for you, use javascript to insert the code. Below is the code I’m using. All you would need to do is change the address in the URI variable and alter the width and height attributes.


Here is the video.

Filed under: Programming — Tags: , , , — admin @ 7:51 pm

What happens if your page get found on Google but it should be inside an iframe? For me the answer was simple, detect if you’re inside an iframe and if not redirect to the destination you want.

To find out if you’re in an iframe you can get the number of frames from the parent window. If the number of frames if less than 0, you’re not in an iframe and you need to redirect. The same principle can be used to escape from an iframe.

if ( window.parent.frames.length>0 )  {}  // check how many frames, if none do nothing
else
{
  
  window.location="[new destination]";  // else change the current location 
  
}
Filed under: Programming — Tags: , — admin @ 8:49 pm

Having created a Javascript k-means function to process a single dimentional array I moved on to working out how to process a multi-dimentional array. The basic premise is the same, but you have to loop through the additional arrays and use a more advanced Euclidean n-space formula to calculate which cluster to allocate the point to.

Below is my k-means clustering learning machine algorithm.

function kmeans( arrayToProcess, centroids, clusters )
{	
	
	Groups=Array();
	iterations=0;
	
	function getType(o)
	{
		var _t;
		return ((_t = typeof(o)) == "object" ? o==null && "null" || Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase();
	}
	
	function deepCopy(target,source)
	{
		for(var p in source)
		{
			if(getType(source[p])=="array"||getType(source[p])=="object")
			{
				target[p]=getType(source[p])=="array"?[]:{};
				arguments.callee(target[p],source[p]);
			}
			else
			{
				target[p]=source[p];
			}
		}
	}						
	
	tempdistance=0;
	oldcentroids=[];
	deepCopy( oldcentroids, centroids );
	
	do
	{	

		for( reset=0; reset < clusters; reset++ )
		{

			Groups[reset]=Array();

		}
		
		changed=false;
		
		for( i=0; i < arrayToProcess.length; i++)
		{	  

			lowdistance=-1;
			lowclusters=0;	

			for( clustersloop=0; clustersloop < clusters; clustersloop++ )
			{	    

				dist=0;	  

				for( j=0;  j < arrayToProcess[i].length; j++ )
				{

					dist+=Math.pow( Math.abs( arrayToProcess[i][j] - centroids[clustersloop][j] ), 2 );

				}

				tempdistance=Math.sqrt( dist );

				if ( lowdistance==-1 )
				{

					lowdistance=tempdistance;
					lowclusters=clustersloop;

				}
				else if ( tempdistance <= lowdistance )
				{

					lowclusters=clustersloop;
					lowdistance=tempdistance;

				}

			}

			Groups[lowclusters].push( arrayToProcess[i].slice() );  

		}

		for( clustersloop=0; clustersloop < clusters; clustersloop++)
		{

			for( i=0, totalGroups=Groups[clustersloop].length; i < totalGroups; i++ )
			{

				for( j=0, totalGroupsSize=Groups[clustersloop][i].length; j < totalGroupsSize; j++ )
				{
				  
					centroids[clustersloop][j]+=Groups[clustersloop][i][j]

				}
					
			}

			for( i=0; i < centroids[clustersloop].length; i++ )
			{

				centroids[clustersloop][i]=( centroids[clustersloop][i]/Groups[clustersloop].length );

				if ( centroids[clustersloop][i]!=oldcentroids[clustersloop][i] )
				{

					changed=true;
					oldcentroids=[];
					deepCopy( oldcentroids, centroids );

				}

			}
		
		}
		
		iterations++;
		
	}
	while(changed==true);

	return Groups;
	
}

Download the script (right click and choose save target as)

Filed under: Mathematics,Programming — Tags: , , , , , , — admin @ 9:32 pm

A client wanted to find out which links were the most popular without using Google Analytics (yeah….I know). My initial approach was to pass all links through a script and record the details and then perform a rediect, I’ll admit a bit of a quick fix.

Having had a bit of time to sit and think about this I decided to intercept the anchor tags perform the action and then continue. The benefits of this means you can easily apply the script to an existing site.

We start off by listening for clicks on anchor tags.

$(document).ready(function(){

  $("a").click(function(e){

  });

});

Next we want to stop the click from going to its destination.

$(document).ready(function(){

  $("a").click(function(e){

    // as the name would suggest, prevent the default action.
    e.preventDefault();

  });

});

Once the default action has been stopped we can instruct the browser to do what we want. If the script you’ll be calling is on the same server you can use AJAX otherwise you’ll have to be a bit more creative, we’ll move on to that in a bit.

$(document).ready(function(){

  $("a").click(function(e){

    // as the name would suggest, prevent the default action.
    e.preventDefault();

    // put the original URL into a variable
    dest=$(this).attr('href');

    // call your script, passing the variable (you'll probably need to include a random variable to ensure the URL called is unique), once the script returns success, call the callback function, in this instance, go to the original destination.
    $.get( "[your_script]", {[the_data_you_are_passing]}, function(){ window.location.href=dest } ); 

  });

});

What about is the script your calling isn’t on the same server, AJAX prohibits this so we need a way around it. My solution is that once the link has been clicked add a div tag to the site containing an image. The image can be held on another server. Now you’re thinking how can I pass variable to an image. My solution to this was simple, the image isn’t an image but script that returns an image, you can pass any variables you like to the script.

$(document).ready(function(){

  $("a").click(function(e){
 
    e.preventDefault();
 
    // make sure we have a unique url
    random=Math.random()*999999;

    dest=$(this).attr('href');

    // the variables you want to pass to the script
    variables='boo='+yaa;

    // allocate space for the image	
    var img = new Image();

   // append the div tag to the body	
   $("body").append('
'); // load the script into the image and append to the div tag, then redirect to the original destination $(img).load(function () { $('#trackingdiv').append(this); window.location.href=dest; }).error( function () {} ).attr('src', '[the_url_of_your_script]?rand='+random+'&'+variables); }); });
Filed under: Programming — Tags: , , — admin @ 6:54 pm

Javascript bubble sort

April 9, 2010

One of the first things I was taught was how to sort an array. Most modern languages have functions to do this for you but it’s still worth knowing the basics behind how. You would be surprised the number of times the same principles are used to perform other operations. Below is my Bubble sort written in Javascript.

A bubble sort works by accending the array and comparing the value at your current location “O(n)” with the value in the next location “O(n+1)”. If the value in the next location is less than the value of your current location “O(n+1) < O(n)” then the two values are swapped “O(n+1) = O(n) and visa versa”.

You can expand on the bubble sort further by running comparisons both accending and descending at the same time, this is known as a double bubble sort. The descending sort has to compare the current position with the one before “O(n-1) > O(n)”, if true the positions are swapped.

function bubbleSort( arrayToSort )
{

  do
  {

    changed=false;

    // loop through the array
    for( i=0;i<(arrayToSort.length-1);i++)
	{

	  // if the O(n) > O(n+1) move O(n) to O(n+1) and O(n+1) to O(n)
	  if ( arrayToSort[i] > arrayToSort[i+1] )
	  {

	    current=arrayToSort[i+1];	  

	    arrayToSort[i+1]=arrayToSort[i];
		arrayToSort[i]=current;

	    changed=true;

	  }

	}

  }
  while( changed==true );
  // break out of loop if no changes are made.

  return arrayToSort;

}

Click the buttons below to see the sort in progress



Filed under: Programming — Tags: , , — admin @ 7:29 pm

After reading an article by Howard Yeend (Pure Mango) I decided I would try to write a version of the basic learn algorithm.

Below is what I came up with.

function kmeans( arrayToProcess, Clusters )
{

  var Groups = new Array();
  var Centroids = new Array();
  var oldCentroids = new Array();
  var changed = false;

  // initialise group arrays
  for( initGroups=0; initGroups < Clusters; initGroups++ )
  {

    Groups[initGroups] = new Array();

  }  

  // pick initial centroids

  initialCentroids=Math.round( arrayToProcess.length/(Clusters+1) );  

  for( i=0; i < Clusters; i++ )
  {

    Centroids[i]=arrayToProcess[ (initialCentroids*(i+1)) ];

  }

  do
  {

    for( j=0; j < Clusters; j++ )
	{

	  Groups[j] = [];

	}

    changed=false;

	for( i=0; i < arrayToProcess.length; i++ )
	{

	  Distance=-1;
	  oldDistance=-1

 	  for( j=0; j < Clusters; j++ )
	  {

        distance = Math.abs( Centroids[j]-arrayToProcess[i] );	

		if ( oldDistance==-1 )
		{

		   oldDistance = distance;
		   newGroup = j;

		}
		else if ( distance <= oldDistance )
		{

		    newGroup=j;
			oldDistance = distance;

		}

	  }	

	  Groups[newGroup].push( arrayToProcess[i] );	  

	}

    oldCentroids=Centroids;

    for ( j=0; j < Clusters; j++ )
	{

      total=0;
	  newCentroid=0;

	  for( i=0; i < Groups[j].length; i++ )
	  {

	    total+=Groups[j][i];

	  } 

	  newCentroid=total/Groups[newGroup].length;  

	  Centroids[j]=newCentroid;

	}

    for( j=0; j < Clusters; j++ )
	{

	  if ( Centroids[j]!=oldCentroids[j] )
	  {

	    changed=true;

	  }

	}

  }
  while( changed==true );

  return Groups;

}

Download the script (right click and choose save target as)

I’ve expanded the script now to support multiple dimensions, you can see my script here.

Filed under: Mathematics,Programming — Tags: , , , , , — admin @ 8:35 pm

About me

Jonathan Spicer

My CV

My curriculum vitae and a wealth of other facts about me.

Warhammer Quest tools

Flickering flame effect Flickering flame effect Flickering flame effect

Powered by WordPress