Refresh qtip content

qtip2 is very handy to show/hide additional information on websites. The qtip content is by default cached to minimize unnecessary fetching. However though, sometimes it’s desirable to fetch for new content every time the tooltip comes up. In that case, use the once : false attribute to force re-fetching of new content.

$('#my_dom_element').qtip({
	content: {
		text: 'loading...',
		ajax: {
			url: 'my_content.php',
			once: false  // need to re-fetch every time
		},
		title: {
			text: 'My title',
			button: true
		}
	},
	position: {
		at: 'left center', // Position the tooltip above the link
		my: 'right bottom',
		viewport: $(window), // Keep the tooltip on-screen at all times
		effect: false // Disable positioning animation
	},
	show: {
		event: 'mouseenter',
		solo: true // Only show one tooltip at a time
	},
	hide: 'unfocus',
	style: {
		classes: 'ui-tooltip-wiki ui-tooltip-light ui-tooltip-shadow',
		width: '800px'
	}
});

Highcharts pie charts can have url links

For some reasons the default highcharts pie chart has no url links. Or at least that feature is not being demo’ed. See their example here: http://www.highcharts.com/demo/pie-basic.

After tweaking the code for a while, here is the code that enables URL links in a pie chart.

var chart;
$(document).ready(function() {
	chart = new Highcharts.Chart({
		chart: {
			renderTo: 'my_chart_id',
			plotBackgroundColor: null,
			plotBorderWidth: null,
			plotShadow: false
		},
		title: {
			text: 'my_title'
		},
		tooltip: {
			formatter: function() {
				var y = this.y;
				var p = Math.round(this.percentage*100)/100;
				return ''+ this.point.name +': ' + y + ' (' + p + '%)';
			}
		},
		plotOptions: {
			pie: {
				allowPointSelect: true,
				cursor: 'pointer',
				dataLabels: {
					enabled: false
				},
				showInLegend: true
			}
		},
		series: [{
			type: 'pie',
			name: 'overall',
			point: {
				events: {
					click: function(e) {
						//this.slice();
						//console.log(e);
						location.href = e.point.url;
						e.preventDefault();
					}
				}
			},
			data: [
				{name: 'Not Tested', color: '#FFA850', y: 87, url: 'http://my_site1.com'},
				{name: 'Fail', color: '#FF2929', y: 2, url: 'http://my_site2.com'},
				{name: 'Pass', color: '#31FF4F', y: 32, url: 'http://my_site3.com'}
			]
		}]
	});
});

The part that enables the clicking is the new url key in data, and the event handling.

series: [{
	type: 'pie',
	name: 'overall',
	point: {
		events: {
			click: function(e) {
				//this.slice();
				//console.log(e);
				location.href = e.point.url;
				e.preventDefault();
			}
		}
	},
	data: [
		{name: 'Not Tested', color: '#FFA850', y: 87, url: 'http://my_site1.com'},
		{name: 'Fail', color: '#FF2929', y: 2, url: 'http://my_site2.com'},
		{name: 'Pass', color: '#31FF4F', y: 32, url: 'http://my_site3.com'}
	]
}]

Representing a class in variable in PHP?

Say you wrote some code below for projectA.

function do_task(){
  // init
  my_init();
  // calling a class function
  $v = class_A::some_method($p1, $p2, $p3, $p4, $p5, $p6, $p7);
  // return
  return $v;
}

Now, your boss loves it. Then he asks you to do a very similar thing for projectB. Naively, you may do:

function do_task($project_type){
  // init
  my_init();
  // calling a class function
  if ($project_type == "projectA"){
    $v = class_A::some_method($p1, $p2, $p3, $p4, $p5, $p6, $p7);
  }elseif ($project_type == "projectB"){
    $v = class_B::some_method($p1, $p2, $p3, $p4, $p5, $p6, $p7);
  }
  // return
  return $v;
}

Um… it would work, but imagine if you will soon have projectC, and D, and on… and what if you need to add one more parameter to the some_method function?

In my opinion, the do_task function needs to be generalized, by taking in the class name as a parameter. Then add a lookup function to translate project names to class names. See below:

function do_task($class_name){
  // init
  my_init();
  // calling a class function
  $my_class = new $class_name();
  $v = $my_class::some_method($p1, $p2, $p3, $p4, $p5, $p6, $p7);
  // return
  return $v;
}

function lookup_class_name($project_name){
  switch ($project_name){
    case "projectA": return "class_A";
    case "projectB": return "class_B";
    // ...
  }
}

// main
$class_name = lookup_class_name("projectB");
$v = do_task($class_name);

The code looks a lot more manageable now. 😉

Text wrapping inside a table

Sometimes I have a long continuing string inside a table that messes up my table width. For example,

SDFSDFDGFHDGREFGFGSDFGFGHGDSFGHJTYRGHTYYEGHJTRTWEGRTYEGFHERGHRETERHRTTWERHGEWGERGW

That will give you a very wide column… Just how to squeeze the column to the width we specify?

“Easy,” you might say, “just set the td width!”

SDFSDFDGFHDGREFGFGSDFGFGHGDSFGHJTYRGHTYYEGHJTRTWEGRTYEGFHERGHRETERHRTTWERHGEWGERGW

Nope. It turns out the same, one long continuous string.

The root problem here is that, the table width is determined by the content, but not the width we specify!

There is this css attribute for the table called table-layout, it disables the usual auto-layout and follows the widths specified. Thus our table will not be shaped by the table content.

SDFSDFDGFHDGREFGFGSDFGFGHGDSFGHJTYRGHTYYEGHJTRTWEGRTYEGFHERGHRETERHRTTWERHGEWGERGW

OK… So our column now has the specified width, but now the long string goes out of the table column. The content spills over. Even though this is the default behavior in html, it doesn’t look right.

Luckily there is this css attribute called word-wrap. Its function is to break up long continuous words.

SDFSDFDGFHDGREFGFGSDFGFGHGDSFGHJTYRGHTYYEGHJTRTWEGRTYEGFHERGHRETERHRTTWERHGEWGERGW

So there you have it, table-layout: fixed and word-wrap: break-word will fix the issue.

Picking the last record of each date in mysql

When we have stats, often times the timestamp is involved. So, say we have a table like the following

CREATE TABLE IF NOT EXISTS `my_stats_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_datetime` datetime NOT NULL,
  `value1` int(11) NOT NULL,
  PRIMARY KEY (`id`)
)

Now, all I want is to grab data for each day. Of course, there could be many rows in a day, and there are many ways to handle that. In my case, I need to grab the last row since it’s most accurate. How to do that?

SELECT * 
FROM `my_stats_table`
WHERE created_datetime IN
  (SELECT max(created_datetime)
    FROM `my_stats_table`
    GROUP BY DATE(created_datetime)
  )

The magic here is GROUP BY DATE(created_datetime). Yea, we are not limited to grouping bare columns only.

What if I want to get the average of the rows of a particular day? Um…

Using memcache in php

So now I assumed you already got memcache running. If not, check out my previous tutorial on how to do that.

Now in your php project, include the following code somewhere in your init.php file. Or you can always make it object oriented if you like.

# Connect to memcache:
global $memcache;
global $memcache_server_up;
$memcache = new Memcache;
$memcache_server_up = $memcache->connect('127.0.0.1', 11211);

# check to see if memcache server is up
function memcache_server_is_up(){
	global $memcache_server_up;
	return $memcache_server_up;
}

# Gets key / value pair into memcache
function getCache($key) {
	global $memcache;
	if (memcache_server_is_up()) {
		return $memcache->get($key);
	}else{
		return "";
	}
}

# Puts key / value pair into memcache
function setCache($key, &$object, $timeout = 600) {
	global $memcache;
	if (memcache_server_is_up()) {
		return $memcache->set($key,$object,MEMCACHE_COMPRESSED,$timeout);
	}
}

My setup will work if you have only one memcache daemon running. If you have a few, then make your changes accordingly.

So in your actual code, just do something like the following.

include_once "init.php";

$cache_key = "a key that uniquely identifies your object";
$obj = getCache($cache_key);
if ($obj == ""){
	$obj = generate_your_obj_somehow();
	setCache($cache_key, $obj, 60*60);
}

So, the idea is to use our cache if it exists. If not, generate our object and then store it in memcache. The above 60*60 will store the $obj for an hour.

Simple idea, but great performance! You can also set up background scripts to keep refreshing your frequently-used objects, so that there is no load time penalty for your users.

Bonus: install this script to see your memcache status! http://livebookmark.net/journal/2008/05/21/memcachephp-stats-like-apcphp/

ref: http://pureform.wordpress.com/2008/05/21/using-memcache-with-mysql-and-php/

Installing memcache on osx for php

Finally got memcache working!

I am compiling the steps here in case I need to do it again.

First install libevent. This is a dependency to memcached, so need to get it.

cd /tmp
curl -OL https://github.com/downloads/libevent/libevent/libevent-2.0.17-stable.tar.gz
tar -xvzf libevent-2.0.17-stable.tar.gz
cd libevent-2.0.17-stable*
./configure
make
sudo make install

Then install memcached.

# Compile memcached utility
cd /tmp
curl -O http://memcached.googlecode.com/files/memcached-1.4.13.tar.gz
tar -xvzf memcached-1.4.13.tar.gz
cd memcached-1.4.13*
./configure
make 
sudo make install

At this point, if everything goes well, the memcache daemon should be ready to run. You can try the following to see if memcached returns anything to you.

memcached -d -m 24 -p 11211
telnet localhost 11211
stats
quit

When you run memcached -d -m 24 -p 11211, you are assigning 24Mb ram for memcache to use, and using the port 11211, which is the default port for memcache. The -d runs memcache as a daemon.

After you run stats, you should see some stats returned on your screen. If so, that means memcache is running fine now.

Next step is to make sure php can talk to memcache.

Download the php extension to memcached from this link: http://pecl.php.net/package/memcache. I recommend getting the stable version, 2.2.6 as of June 2012.

After uncompressing it, do phpize. If you get an error on not having autoconf, install it with brew. See my other tutorial on how to do that.

gzip -d < memcache-2.2.6.tgz | tar -xvf -
cd memcache-2.2.6
phpize

After phpize gives you your php version info, do the usual compile and install:

./configure
make
sudo make install

Double check that the memcache.so file is in your php include directory.

ls /usr/lib/php/extensions/no-debug-non-zts-20090626/

It should be there… if not, you can manually copy the file yourself. It’s located under the “modules” folder.

Now, modify your /etc/php.ini file to include this extension.

extension = memcache.so

Then finally, restart apache.

sudo apachectl restart

If everything goes well, your phpinfo() should give you a section on memcached, indicating memcached is loaded properly.

Congratulation! At this point php is ready to interact with memcache. But just how to do that in code? Let’s wait for my part 2 of this tutorial. 😉

Ref link:
http://readystate4.com/2012/03/15/installing-memcached-on-os-x-10-7-3-lion-on-mamp/
http://www.glenscott.co.uk/blog/2009/08/30/install-memcached-php-extension-on-os-x-snow-leopard/
http://jamiecurle.co.uk/blog/memcached-on-osx-without-macports/

Hide qtip2

Sometimes when you click on a qtip2 popup, you may want to close the popup somehow. For example, if your qtip2 popup contains a list of links, and clicking on the links will open up more popups. In that case, you would really want to close the previous qtip2 popup upon each link click. Well, here is how you can do that programmatically.

$('.qtip:visible').qtip('hide');

Yeah, that’s it. Clean screen. Yeah~~~

Custom sorting in Datatable

My datatable was acting cool and all until I added a link to each integer element of a particular column. All of a sudden the sorting feature was acting weird.

Before I had something like this

  23

After adding a link, I have

  23

It turns out after I added the links, the default sorting function is treating everything as string. So to fix that I will need to add custom sort function to explicitly tell datatable how I want to do my sorting.

jQuery.fn.dataTableExt.oSort['intComparer-asc'] = function (a, b) {
	var m = a.match(/^(d+)/);
	a = m[1];
	var m = b.match(/^(d+)/);
	b = m[1];
	var value1 = parseInt(a);
	var value2 = parseInt(b);
	return ((value1  value2) ? 1 : 0));
};

jQuery.fn.dataTableExt.oSort['intComparer-desc'] = function (a, b) {
	var m = a.match(/^(d+)/);
	a = m[1];
	var m = b.match(/^(d+)/);
	b = m[1];
	var value1 = parseInt(a);
	var value2 = parseInt(b);
	return ((value1  value2) ? -1 : 0));
};
	
$(document).ready(function() {
	$('#my_datatable').each( function(){
		oTable = $(this).dataTable({
			'bPaginate': false, 
			'bInfo': false,
			'bFilter': false,
			'aoColumnDefs': [
				{ 'sType': 'intComparer', 'aTargets': [ 0, 1 ] }
			]
		});
	});
});

The key part is the line “{ ‘sType’: ‘intComparer’, ‘aTargets’: [ 0, 1 ] }“. As you can see, I have my first column and second column assigned to this custom new data type called intComparer. And intComparer-asc and intComparer-desc will take care of the sorting details.

TextMate drawer disappeared…

Sometimes after working on a few TextMate windows, one of them will have its file directory tree drawer missing. I don’t know why this happens but to bring back the drawer, just go to the menu bar and click View -> Show Project Drawer.

Also another tip is, if you prefer the drawer to come out from the right side but it always comes out from the left, all you need to do is
1. Hide the drawer
2. Move your TextMate window to the left side of your screen so there is not much space to display the drawer on the left
3. Show the drawer

Yea, it’s that easy. Happy programming!