ILearnable .Net

October 12, 2010

How to make your world better one browser extension at a time

Filed under: Uncategorized — andreakn @ 21:22

As a consultant I have been exposed to a lot of different timesheet applications. Everything from a simple excel sheet to fairly advanced web sites into which you enter the amount of time spent on various (hopefully) project-related activities. The fidelity with which the hours are to be registered also varies wildly. Generally being a bit of a slob I prefer when the input process is simple and the fidelity is almost nonexisten. This frees me up to deliver quality work instead of painstakingly documenting *what* work I did *when* and for *how long*.

In my simplistic world-view the hourly report forms you have to fill out are a necessary evil, but they’re still evil 🙂

So, when I got subcontracted through a firm with this input process: (snip: )
First I threw a fit and then I started to think: “is there any way for me to automate this”.

as it turns out, yes there is. I won’t bore you with all the juicy details on how to create a chrome plugin that modifies a webpage on your behalf,
but in essence all you do is to have a manifest file declare on which urls this plugin should kick in, and what javascript files it should inject at the end of the loading process for that page.

For me in this case the file (which must be called manifest.json by the way) looks like this:

  "name" : "ElanIt Timereg decrapifier",
  "version" : "0.1",
  "description" : "Makes timereg useable",

  "content_scripts": [
      "matches": ["*"],
      "js": ["jquery.js", "timeregmod.js"]

which as you can see loads a bundled version of a minified jquery 1.4.2 and also a custom js file which contains the logic I needed to inject onto the page.
here is the timeregmod.js file:

$.fn.getNonColSpanIndex = function () {
    if (!$(this).is('td') && !$(this).is('th'))
        return -1;

    var allCells = this.parent('tr').children();
    var normalIndex = allCells.index(this);
    var nonColSpanIndex = 0;

        function (i, item) {
            if (i == normalIndex)
                return false;

            var colspan = $(this).attr('colspan');
            colspan = colspan ? parseInt(colspan) : 1;
            nonColSpanIndex += colspan;

    return nonColSpanIndex;

$(document).ready(function () {
    findAllWorkDays($('.ITMTimesheetDetail')).not(':last').each(function () {
        var tr = $(this);
        tr.append($('<td></td>').append($('<input type="button"></input>)').val('>>').css('font-size','9px').click(function () {
            var input = $(this);
            var thisRow = input.parents('tr').first();

            var nextRow = findNextWorkDay(thisRow);

            $('input[type=text]', thisRow).each(function () {
                var textbox = $(this);
                var index = textbox.parent().getNonColSpanIndex();

                $('td:eq(' + index + ') input', nextRow).val(textbox.val());
    $('.ITMTimesheetDetail tr:eq(2)').each(function () {
        var tr = $(this);
        tr.append($('<td></td>').append($('<input type="button"></input>)').val('Kopier til alle').css('font-size', '9px').click(function () {
            $('input[type=text]', tr).each(function () {
                var textbox = $(this);
                var value = textbox.val();
                var index = textbox.parent().getNonColSpanIndex();
                var workdays = findAllWorkDays('.ITMTimesheetDetail');
                $('td:eq(' + index + ') input', workdays).val(value);

function findAllWorkDays(table) {
    return $('tr:gt(1)', table).filter(function (index) {
        var tr = $(this);
        return isWorkDayRow(tr);

function isWorkDayRow(tr){
    if(tr.children().first().hasClass('ITMTimesheetDetailDayOfMonthHoliday') || tr.children().first().hasClass('ITMTimesheetDetailDayOfMonthSaturday')){
        return false;
    return true;

function findNextWorkDay(currentRow) {
   var nextRow =;

   while (!isWorkDayRow(nextRow)) {
       nextRow =;
   return nextRow;

I won’t pretend that I’m overly happy with the javascript, it should be shorter and better, but it gets the job done, so I’m stopping working on it.

The timesheet application (which is called Manpower Timereg apparently) now looks like this for me (when browsing using google chrome with the plugin loaded):

(yes, the buttons on the right hand side was added by this plugin).

SO: if you have to deal with this (dare I say somewhat challenging) system and would like to improve your own experience, feel free to take the sourcecode in this blogpost and create your own chrome plugin. You probably want to change the url in manifest.json (unless you happen to be subcontracting for ElanIt in norway)

creating a chrome plugin based on this is as simple as:
– create a new folder and call it MySpecialPlugin or something like that
– download the latest version of jquery into that folder and rename the file to jquery.js
– put the two files as described in this blogpost into the folder.
– in chrome: navigate to the url chrome://extensions and click the “load unpacked extension”
– select the folder you created a few bullets ago

and voila: Bob’s your uncle


Blog at