Open Source


24
Mar 10

jQuery :contains selector and unicode characters

I have an element like this (to save space in the menu since they can put up to 255 characters in it):

    <span class="tool_tip" title="The full title">The ful&#8230;</span>

While this seems to work:

    jQuery('span:contains(…)');

this does not:

    jQuery('span:contains(&#8230;)');

I am pretty sure that it would be bad to use the first one because if someone else saves the file, or the browser decides to get the file in a different character set for some reason things will not work.

There has to be a way to properly select this span, right? Turns out there is:

 
    jQuery('span:contains(\u2026)');

In other words:

Use the hex value instead of the decimal value as the selector and things will work out fine.


17
Mar 10

Documenting PHP Code

I know we all hate documenting code, but it can really help out future you or me when we need to go back and fix something. Poorly documented code is the bane of anyone who will be taking on your code after you leave (or invite more people into) the project. One thing that can really help is documenting the variables that you can get via the __get and __set magic methods:

<?php
 
/**
 * @property bool $development_environment
 * @property string $base_url
 * @property string $index_file
 * @property string $allowed_uri_characters
 * @property string $char_encoding
 * @property string $default_location
 * @property string $helper_prefix
 * @property array $auto_load
 * @property string $auto_load['helpers']
 * @property array $db
 * @property string $db['name']
 * @property string $db['user']
 * @property string $db['password']
 * @property string $db['host']
 * @property string $db['type']
 * @property array $users
 * @property string $users['table']
 * @property string $users['encryption']
 * @property array $users['fields']
 * @property string $users['session_name']
 */
class config{
	/**
	 * Get a var from the config values
	 * @param string $var
	 */
	function __get($var) {
		if(isset($this->data[$var])) {
			return $this->data[$var];
		}
		throw new Exception($var . ' does not exist in '. __CLASS__);
	}
 
	/**
	 * Set a new/current config value
	 * @param string $var
	 * @param multiple $value
	 * @return  multiple
	 */
	function __set($var, $value) {
		return $this->data[$var] = $value;
	}
 
}

Adding the @property values to the top of the class lets you see at a quick glance what default properties that the class gives you. It can even help you in your ide when you are trying to figure out what you can actually get out of a class:

Remember, try not to piss off future you, they know where you live.


10
Mar 10

Using the jQuery-UI Autocomplete Widget

The latest version of jQuery-ui has an autocomplete widget. No more looking for one that will work (you get the point) with the version of jQuery that you have.

All you have to do is pull the latest one down from the jQuery-ui site and you are good to go.

First the jQuery to use the autocomplete plugin:

 
//we will be using this to cache the responses from the server
var ajaxCache = {};
 
//activate autocomplete on boxes that have the autocomplete class
$("input.auto_complete").autocomplete({
	source: function(request, response) {
		//what are we searching for
		var query_type = $(this).attr('element').attr('id');
		//the cacheterm that we use to save it in the cache
		var cachedTerm = (request.term + '' + query_type) . toLowerCase();
		//if the data is in the cache and the data is not too long, use it
		if (ajaxCache[cachedTerm] != undefined && ajaxCache[cachedTerm].length < 13) {
			//map the data into a response that will be understood by the autocomplete widget
			response($.map(ajaxCache[cachedTerm], function(item) {
				return {
					label: item.value,
					value: item.value
				}
			}));
		}
		//get the data from the server
		else {
			$.ajax({
				url: "/ajax/auto_complete.php",
				dataType: "json",
				data: {
					query_type: query_type,
					q: request.term
				},
				success: function(data) {
					//cache the data for later
					ajaxCache[cachedTerm] = data;
					//map the data into a response that will be understood by the autocomplete widget
					response($.map(data, function(item) {
						return {
							label: item.value,
							value: item.value
						}
					}));
				}
			});
		}
	},
	//start looking at 3 characters because mysql's limit is 4
	minLength: 3,
	//when you have selected something
	select: function(event, ui) {
		//close the drop down
		this.close
	},
	//show the drop down
	open: function() {
		$(this).removeClass("ui-corner-all").addClass("ui-corner-top");
	},
	//close the drop down
	close: function() {
		$(this).removeClass("ui-corner-top").addClass("ui-corner-all");
	}
});

The ajax/auto_complete.php would look something like this:

 
<?php
 
//make sure that we only allow valid query types
/**
 * @var array
 */
$validQueryTypes = array(
	'country',
	'city',
	'first_name',
	'last_name',
);
/**
 * @var string
 */
$column = (isset($_POST['query_type']) && in_array($_POST['query_type'], $validQueryTypes))? $_POST['query_type'] : null;
/**
 * @var string
 */
$q = isset($_POST['q'])? $_POST['q'].'%':null;
 
 
if($column && $q){
	switch($column){
		case 'country':
		case 'city':
			$q = new sQuery();
			$results = $q->from('addresses')
				//to make things simpler for the javascript, always select as value
				->column($column . ' as value')
				->where($column, $q, 'LIKE')
				->getAll();
 
			//SELECT $column as value FROM addresses WHERE $column LIKE '$q%';
			break;
		case 'first_name':
		case 'last_name':
			$q = new sQuery();
			$results = $q->from('users')
				->column($column . ' as value')
				->where($column, $q, 'LIKE')
				->getAll();
			//SELECT $column as value FROM users WHERE $column LIKE '$q%';
			break;
	}
 
	//$result is something like this:
	array(
		array('value'=>'Canada'),
		array('value'=>'America'),
		array('value'=>'Mexico'),
		array('value'=>'Netherlands'),
	);
 
	//then return it to the javascript
	echo json_encode($results);
}
exit;

Then the simplest part of the exercise:

 
City:
<input type="text" name="city" id="city" title="enter a city" class="auto_complete" />
 
Country:
<input type="text" name="country" id="country" title="enter a country" class="auto_complete" />
 
First Name:
<input type="text" name="first_name" id="first_name" title="enter a first_name" class="auto_complete" />
 
Last Name:
<input type="text" name="last_name" id="last_name" title="enter a last_name" class="auto_complete" />

And if they don’t have javascript enabled it doesn’t detract from the form (go progressive enhancement!).

Edit:

This post is pretty old, but just in-case you were wondering sQuery is this: https://github.com/SeanJA/ShoestringPHP/blob/master/library/classes/squery.class.php and there is also a better tested more standalone-y version here as a separate project: https://github.com/SeanJA/query-builder.


3
Mar 10

Finding corrupted images

As zoe kindly pointed out, two Wednesdays have passed since I last posted. I blame the Olympics (I was in Vancouver for the first week, and I was still recovering from it the second week Go Canada!). You can see my photos on flickr.com as I post them here. Apparently you can only post 100 mb/month, so it might take a while.

DSCN0543

Anyway, onto the post:

This month at work, one of our servers decided to act up and corrupt quite a few things. We had about 10000 images on that server, many of them were corrupted, many of them were not. Instead of going through all of the images one by one, opening them and checking if they were corrupted, I was tasked with writing a php script to find them. My first instinct was to do something like this:

 
<?php
 
if ($handle = opendir('/path/to/files')) {
    echo "Directory handle: $handle\n";
    echo "Files:\n";
 
    /* loop over the directory. */
    while (false !== ($file = readdir($handle))) {
        if(@getimagesize($file) === false){
            echo "$file\n";
        }
    }
}
 
?>

Unfortunately, getimagesize only looks at the start of a png file to get the information, meaning that if the file was corrupted after those first few bits of information it would not show up on the list.

The second idea was to use this method:

<?php
 
if ($handle = opendir('/path/to/files')) {
    echo "Directory handle: $handle\n";
    echo "Files:\n";
 
    /* Since they are all png files, this will work: */
    while (false !== ($file = readdir($handle))) {
        if(@imagecreatefrompng($file) === false){
            echo "$file\n";
        }
    }
}

Which, in theory, should have worked because it reads the whole file in and then returns false if the image could not be created. Unfortunately there was (is) something wrong with the libpng library so I kept getting errors like this locally:

libpng warning: Ignoring bad adaptive filter type
libpng warning: Extra compressed data.
libpng warning: Extra compression data

Which would be fine if they were php errors. I could have ignored them because the script worked properly and listed all of the files that had problems. Unfortunately for what ever reason, the server was dying on these errors instead of continuing like it was doing locally.

In comes imagemagick to the rescue. Using the identify command (at the command line) it reads in the whole image file and then tells you what it is. Since you might have 10000 images like we did, it is probably also a good idea to send the output to a file instead:

    identify "./path/to/files/*" >badImages.txt 2>&1

You will also want to make sure that the path has ” ” around it, because otherwise you will end up with an “Argument list too long” error. In the badImages.txt file you will have a list of images that are in the folder you specified. Any of the lines that start with identify are no good.


9
Aug 09

A couple useful jQuery Snippets

For popups, just add class=”popup”

jQuery('a.popup').live('click', function(){
	newwindow=window.open($(this).attr('href'),'','height=200,width=150');
	if (window.focus) {newwindow.focus()}
	return false;
});

To open in a new tab, add class=”newTab”

jQuery('a.newTab').live('click', function(){
	newwindow=window.open($(this).href);
	jQuery(this).target = "_blank";
	return false;
});

Check if something is in view

function in_view(elem){
	var docViewTop = jQuery(window).scrollTop();
	var docViewBottom = docViewTop + jQuery(window).height();
 
	var elemTop = jQuery(elem).offset().top;
	var elemBottom = elemTop + jQuery(elem).height();
 
	return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom) && (elemBottom <= docViewBottom) &&  (elemTop >= docViewTop));
}