Selecting rows in a paginated data grid

When displaying sets of records on the web it’s a very common thing to include a checkbox on each row to allow users to select rows for things like, deleting multiple records, moving records from one place to another or whatever your case may be. It’s pretty standard and a nice addition to add a single checkbox in the header to allow users to select all the rows with one click – and then you add pagination and the user experience falls apart. I had a user ask me, “When I click the ‘select all’ checkbox, are all the rows selected, or just the ones on this page?”.
Clearly I needed to be a bit more specific. Here’s the solution that was used.

I swapped out the header checkbox with the following set of 3 checkboxes and bits of HTML.
[code]

[/code]
The above HTML leaves one checkbox in the header and two more in a hidden div. The one that’s visible will be used to indicate some kind of selection, and the other two will be shown and hidden with jQuery to allow two choice options.

  • All records on current page
  • All records on all pages

The first part to be handled is the showing and hiding of the checkboxes. Using a timer, I added a delay to the ‘hide’ function so that it’s less jumpy and moving your mouse outside the element by accident won’t hide it and frustrate my users. While it’s not required, notice that I named my anonymous functions with something that helps clarify their purpose, “enterElement” and “exitElement”.
[code]
var moreOptsTimer;
$(“#moreOptsParent”).hover(function enterElement() {
clearTimeout(moreOptsTimer);
$(“#moreOptsPanel”).show().animate({ height: 40 },
{ duration: “slow” });
},
function exitElement() {
moreOptsTimer = setTimeout(function () {
$(“#moreOptsPanel”).animate({ height: 0 },
{ duration: “slow” },
function () {
// Animation complete.
$(“#moreOptsPanel”).hide();
});
}, 500);
}
);
[/code]
Now that the two selection choices hide and show nicely we have to add code to manage the state of the checkboxes to show selections. When we select “all rows current page”, we need to check the header checkbox and the one on each row. When we select “all rows on all pages”, we need to check the header checkbox and uncheck & disable the one on each row. For completeness I’ve mirrored the “All Records current page” function on the original checkbox but because the behaviour is so slightly different, it gets it’s own click function.

[code]
/* attach event handler to “check all” checkbox */
$(‘#checkAllDisplay’).click(function () {
$(“#MyGridItems tbody input:checkbox”).removeAttr(“disabled”)
.attr(“checked”, this.checked);
$(‘#checkAllRecords’).attr(“checked”, this.checked);
if (!this.checked) {
$(‘#checkAllPages’).attr(“checked”, false);
}
});

/* attach event handler to “check all on current page” checkbox */
$(‘#checkAllRecords’).click(function () {
$(“#MyGridItems tbody input:checkbox”).removeAttr(“disabled”)
.attr(“checked”, this.checked);
$(‘#checkAllDisplay’).attr(“checked”, this.checked);
if (this.checked) {
$(‘#checkAllPages’).attr(“checked”, false);
}
});

/* attach event handler to “check all on all pages” checkbox */
$(‘#checkAllPages’).click(function () {
$(‘#checkAllDisplay’).attr(“checked”, this.checked);
if (this.checked) {
$(‘#checkAllRecords’).attr(“checked”, false);
$(“#MyGridItems tbody input:checkbox”)
.attr(“disabled”, true)
.attr(“checked”, false);
} else {
$(“#MyGridItems tbody input:checkbox”)
.removeAttr(“disabled”);
}
});
[/code]

Try the demo and leave a comment below if you found it useful or had a suggestion.

Hide changing content with JQuery UI

While working on an ajax UI, I wanted to show that something was happening while preventing the user from interacting with page elements that are getting replaced. I used the .position() function from JQuery UI to place a div directly over the changing content.
[code]
$(“#busyIndicatorDiv”).position({
of: $(“#changingContentDiv”),
my: “left top”,
at: “left top”,
offset: 0,
collision: “none none”
});
[/code]

Looking at the code above, you can see that we’re setting the top left corner of our busy indicator at the top left corner of our changing content. The collision property is used to deal with bumping into the edges of the window and since we’re not showing popup menus etc, we don’t want to do anything special so I left it at ‘none none’.

In my particular case I had several elements that were all being loaded with ajax, so I used the JQuery .clone() function to make duplicates of a busy indicator div for each one. I was careful to change the id attribute of each clone to keep them unique and something that I would be able to find easily when it’s time to remove the busy indicator.
[code]
$(‘#busyOverlayBase’).clone(true).removeAttr(‘id’).attr(‘id’, busyName).insertAfter($(‘#busyOverlayBase’));
[/code]

Try the demo and leave a comment below if you found it useful or had a suggestion.

Styling the Explicitly Styled

Every now and then I run into a site that has some explicit CSS styling on an element that really doesn’t fit with the site design. If the site is a one-off or ‘custom’ website, that’s easy to change but when it’s a 3rd party product or one that will need upgrading at some point, making changes to many files removing an explicit style is an unpalatable option.

I was working on a site that contained this bit of HTML in *many* files and having bits of red text splattered all over the website looked horrible.

[code]
<span style=”color: #900; font-weight: bold;”>
[/code]

The last two times I ran into this I was able to fix the problem with a little JQuery script in the site header.

[code]
// Tweak span colors without modding all files in the website
$(document).ready(function () {
$(‘span’).each(function(i){
if ($(this).css(‘color’) == “rgb(153, 0, 0)”) {
$(this).css(‘color’, ‘#000’);
}
});
});
[/code]

The code above simply goes through all the Span’s in a page and checks the color that’s applied and if it’s Red (rgb(153, 0, 0)), it gets replaced with Black.

I expected to be able to see the colour change some time after the page rendering had completed but it’s fast enough that you never see it.  Perfect!

Spamassassin

I’ve recently seen an influx of spam on several of the sites that I run. This led to a closer investigation of the default settings in Spamassassin.

You can automatically delete messages marked as spam. First set the number of hits required before mail is considered spam.

(Note: “5” is the default setting. The lower the number, the more conservative the setting.)

The description in CPanel isn’t as clear as it could be so I had to look further.

The numeric value is the measure at which Spam gets deleted.  Messages are assigned points based on how ‘spammy’ they appear.  Given that piece of info, the lower the number, the fewer messages get through.

Zend Framework Quickstart on Ubuntu 11.10

Running through the Zend Framework quickstart on Ubuntu 11.10, I ran into a few issues. Granted they’re not huge issues, but they slowed me down nonetheless.

1) Make sure that the php.ini for the cli is updated to include the zend framework. See the /etc/apache2/php.ini for the changes required.

2) When creating the /data and /scripts folders, they need to be in the root of your quickstart app, beside your “public” folder.

3) I didn’t have the Sqlite driver installed on my machine so that had to be installed before I could create the database.

AN ERROR HAS OCCURED: The sqlite driver is not currently installed

To fix this, I installed the “php-mdb2-driver-sqlite” package from Synaptic.

After fixing these 3 problems, the data loading script worked like a charm!

There was one more glitch getting the Quickstart app completed. Apparently in Ubuntu 11.10, Sqlite has been upgraded to Sqlite3 where Zend is still using the older version. Here’s a workaround to add the prior version to get through: https://bugs.launchpad.net/ubuntu/+source/php5/+bug/875262/comments/10

Fixing POST errors that seem almost random

Recently my webhost installed mod_security on my webserver. It seemed like a non-event until I started getting calls from a few clients who had been merrily working for a long time. When they tried to update content in either Open-cart or WordPress, they were getting strange errors such as “501 Not Implemented” or “406 Not acceptable”.

The fix is to sidestep mod_security for those requests. Ideally it should be limited to the offending request, but in the interest of getting people running, I simply added a .htaccess file in the /admin folder of the broken sites with the following contents.

[code]

SecFilterEngine Off
SecFilterScanPOST Off

[/code]

Domain Registration – on my schedule!

Working as a freelance web developer means that I work really strange hours. I may not get to a project until my kids are in bed and keep working late into the evening. When there’s something that needs to be done, nothing ticks me off quite like a broken website that requires a phone call to customer support. Once or twice I’ll let someone get away with it, after-all sometimes stuff breaks. That’s just the way it is. But when it came time to renew my website domain – the face of my freelancing business, and I discovered yet again that the domain renewal pages of my registrar *still* would not allow me to renew online I nearly lost it. I called up their customer support where the person on the other end asked me for my username and password so that they could enter the transaction for me. Yeah right. It was time to find myself a new registrar. After the usual poking around the Internet and some discussion among my associates, I settled on http://www.namespro.ca/. They’ve been in the business since 2003 and are a Canadian business so I feel like I can trust them to manage my domains properly. I signed up and was able to quickly and easily move over my domain. The website is easy to follow and the domain management pages are clean and easy to use. I sent a couple questions out to their tech support and received prompt replies that made me very happy with my choice. I’ve since moved a couple more domains to them and will continue to chance my domains over to namespro as they come due. Managing my domains from a proper web interface on my own schedule has never been simpler!

Using Cron jobs from CPanel

I’ve set this up before but couldn’t remember just how I did it. Instead of having to figure it out all over again next time, I thought I’d write it somewhere.

Setting the time in CPanel’s Standard Cron UI is very simple so I won’t bother with that but the command for triggering a web page has a few things that messed me up.

For those who don’t care to read much, here’s the command:

[code] wget -O – -q -t 1 ‘http://www.mydomain.net/doit.php?id=12345’ >/dev/null 2>&1 [/code]

Or, if you’re using a .htaccess username and password, like this:

[code]
wget -O – -q -t 1 ‘http://username:password@www.mydomain.net/doit.php?id=12345’ >/dev/null 2>&1
[/code]

Of course you’ll need to insert your own url but you can otherwise cut and paste the line above and start running your php script as a cpanel cron job. But what does it all mean?

“wget” is a linux command line utility to fetch the contents of a web page. In our case we don’t care about the output of the page, only that it gets triggered – or ‘looked’ at.

“-O -” says to discard the output (aka, the page contents), we’re not interested to keep these!

“-q” put wget into quiet mode, I don’t want to know about errors here either.

“-t 1” tells wget to only try once, if the page doesn’t work for some reason, give up.

The url should be wrapped in single quotes! This is an easy one to miss because in a simple case, it’s not required but if you have url parameters it’s a must otherwise the url gets truncated and the request won’t work as you planned it to.

“>/dev/null 2>&1” This tells cron *not* to send out a notification that the script was called. The details of this part gets a bit hairy but I’ll do my best to explain. As the script I’m triggering already sends an email when it completes, I don’t really want the one that is sent out automatically each time the cron runs. As well, the subject for the email is the complete cron command. I don’t know about you but if your scheduled cron command includes anything even sort-of private, I’d prefer not to blast it out in email every day! Consider if you included a URL to a script behind an htaccess password, the username and password would be right there in the subject of your email every day!

The first part, “>/dev/null” redirects the output (standard out) of the command to /dev/null (the Linux equivalent of a black hole!). The second part, “2>&1” redirects ‘standard error’ to ‘standard out’. The short story is that there is no more output from your command and therefore there’s no email to be sent out!

Recovering files deleted from an SVN repository

This morning I find myself in need of a file that used to exist in an SVN project. The file was deleted several months ago and I don’t have a copy anywhere else. Finding it in my SVN repository turned out to be quite simple.

Run the following command to dump the complete activity log from the project.
[code]
> svn log –verbose > myLog.txt
[/code]

Then simply open the ‘myLog.txt’ file with your favourite editor and hunt for the last mention of the file you need. I’m looking for “register.module.php”, so for me that looks like this:
[code]
————————————————————————
r428 | mvoorberg | 2010-11-15 22:06:33 -0700 (Mon, 15 Nov 2010) | 2 lines
Changed paths:
M /faq.php
M /login.action.php
D /register.module.php
[/code]

Use the following command to restore the file to the local filesystem at which point you can copy it out or do whatever you like with it. Notice the version number (427) is one less than the version that included the Delete action above.
[code]
> svn up -r 427 register.module.php
[/code]

Once I copied the file out, I removed it again by simply running:
[code]
> svn up
[/code]

Don’t forget to remove the ‘myLog.txt’ file when you’re done with it.

Moving Joomla site to a subfolder

Before rebuilding this website, I wanted to make sure I was able to keep the old site in a subfolder to help keep my search ranking and not break any external links to my site. This is clearly a job for modRewrite so I had a look around and goolged things like “Joomla to subdirectory” and the like. Funny enough I couldn’t find any posts about it and no-one had posted the required .htaccess entries to make it happen. As I’d never used SEF urls with my Joomla site and I wasn’t going to use Joomla for the new website, it was easy to find a pattern that all my urls would match. I could simply send anything that contained the string “option=com” to my “archived” subfolder.

Here’s the entries I added to the .htaccess in the root of my website:
[code]
# Make sure this module is turned on
RewriteEngine on

# Match any Joomla Urls
RewriteCond %{QUERY_STRING} ^option=com(.*)$

#Only forward Get and Head requests to the archived site
RewriteCond %{THE_REQUEST} ^(GET|HEAD)

# Don’t match urls that already contain the archived subfolder
RewriteCond %{REQUEST_URI} !^/archived/$

# Send a 301(Permanent Move) to the archived folder
# and don’t process any more rules(L)
RewriteRule ^index.php$ /archived/index.php [L,R=301]

[/code]

Follow-up:
As it turns out you can’t set the database permissions for specific tables when running in a typical shared LAMP environment. I was hoping for SELECT only and INSERT on some Joomla session tables. I noticed that I was still getting some new spam user registrations on the old site so I’ve disabled POST requests with the following entries added to the .htaccess in the /archive folder of the old Joomla site:
[code]
# Make sure this module is turned on
RewriteEngine on

# Disallow POST requests to the archived site by sending a 404 error
RewriteCond %{THE_REQUEST} !^(GET|HEAD)
RewriteRule ^ / [R=404]

[/code]