Anders Tornblad

All about the code

Label archive for project time clock

Tajmkiper now public

Today I launch Tajmkiper as a publicly available, free-to-use, utility. You are welcome to use it as much as you want. If you like it, please tell your friends and colleagues about it.

It is designed mobile-first, so it really looks its best on a smaller screen, but works fine in any modern browser.

Try it out: Tajmkiper.com

Read more about it:
Tajmkiper, part 1
Tajmkiper, part 2
Tajmkiper, part 3

Tajmkiper, part 3

Thanks to my earlier efforts (part 1, part 2 and part 3), exporting all projects to a CSV file that is saved locally is really easy. That is also the only part of the tajmkiper.com utility that I'll blog about. The complete code will be available on Github when the utility is launched for public use, so you are free to check out the complete code.

function exportToCsv() { // Order of properties var propertyOrder = ["name", "time"]; // Create CSV exporter var csv = new Csv(propertyOrder); // Add header line csv.add({ "name" : "Project", "time" : "Total time" }); // Add all projects, using the same (omitted) formatTime function from before allProjects.forEach(function(project) { csv.add({ "name" : project.name, "time" : formatTime(project.getTotalTime()) }); }); // TODO: Create a formatTodaysDate function var filename = "tajmkiper-" + formatTodaysDate() + ".csv"; csv.saveAs(filename); }

Future possibilities

There is actually something that I would like to do on the server-side, that doesn't really take anything away from the beauty (?) of a purely client-side codebase. I would like to be able to transfer my projects from one browser to another. One possibility would be to simple take the JSON representation saved locally, and expose it for copy/paste, but that's not really smooth enough. I want to be able to save my projects to a server, and then load them up on another browser.

One use case for this is when I use the utility on a mobile device, but my company's time report utility uses Excel. Then I would like to open the projects on my desktop computer, export to CSV, which I then import in Excel, and keep working there.

EDIT: Tajmkiper is now publicly available to anyone who wants to use it.
Try it: tajmkiper.com

Tajmkiper, part 1
Tajmkiper, part 2
Tajmkiper, part 3 (this part)

Tajmkiper, part 2

One important feature that I want for Tajmkiper ("time keeper" written phonetically in Swedish) is that everything should run locally, including data storage. The data model is really simple: I'll write a class called Project with only three properties: name, totalFinishedTime and startedOn

Project.name
The name of the project.
Project.totalFinishedTime
Number of seconds the project has been running in completed chunks. If this project's time clock is currently running, the totalFinishedTime does not include the number of seconds on the running clock.
Project.startedOn
The Unix timestamp (Date.getTime()) for when this project's time clock started, or null if the time clock is not started.

This way the project objects don't need to be constantly update every second. To present the currently elapsed time on the clock, the user interface simply adds totalFinishedTime to the number of seconds that passed since startedOn. Assuming most people don't fiddle around with the system clock, this will also make it perfectly possible to close the browser and reopen it any amount of time later, and the time clock will remember when it was started.

function Project(name, totalFinishedTime, startedOn) { this.name = name; this.totalFinishedTime = totalFinishedTime; this.startedOn = startedOn; } Project.prototype.getTotalTime = function() { if (this.startedOn) { // Bitwise or with zero forces the result to be an integer return this.totalFinishedTime + (Date.now() - this.startedOn) / 1000 | 0; } else { return this.totalFinishedTime; } }; Project.prototype.stop = function() { this.totalFinishedTime = this.getTotalTime(); this.startedOn = null; }; Project.prototype.start = function() { this.totalFinishedTime = this.getTotalTime(); this.startedOn = Date.now(); }; var allProjects = []; function saveProjects() { var json = JSON.stringify(allProjects); localStorage["projects"] = json; } function loadProjects() { allProjects = []; var json = localStorage["projects"]; if (json) { var temp = JSON.parse(json); temp.forEach(function(item) { allProjects.push(new Projects(item.name, item.totalFinishedTime, item.startedOn)); }); } }

The next step is to connect the Project objects up to some HTML generation. I could do this using a jQuery templating plugin, but i choose to do it myself, just for the hell of it.

function createProjectsHtml() { // First, clear any existing content of the #projects UL element var ul = document.getElementById("projects"); ul.innerHTML = ""; // Go through all projects, create html elements for each, and add them to the UL allProjects.forEach(function(project) { var li = createProjectElement(project); ul.appendChild(li); }); // TODO: Add the #total LI element } function createProjectElement(project) { // Create an LI element, and store a reference to it in the Project object for later use var li = document.createElement("LI"); project.element = li; // Store a reference to the Project object in the clickable link var a = document.createElement("A"); a.href = "#"; a.projectReference = project; li.appendChild(a); a.addEventListener("click", onProjectClick, false); var header = document.createElement("HEADER"); header.textContent = project.name; a.appendChild(header); var timeSpan = document.createElement("SPAN"); timeSpan.className = "time"; a.appendChild(timeSpan); updateProjectElement(project); return li; } function updateProjectElement(project) { var li = project.element; var timeSpan = li.querySelector("span.time"); var totalTime = project.getTotalTime(); var timeText = formatTime(totalTime); timeSpan.textContent = timeText; li.className = (project.startedOn) ? "running" : ""; } // TODO: Write a formatTime function var running = null; function onProjectClick(e) { e.preventDefault(); // Stop the currently running project first if (running) { running.stop(); updateProjectElement(project); } // Start the clicked project var project = this.projectReference; project.start(); updateProjectElement(project); running = project; saveProjects(); }

Now, the projects are clickable, and the html gets updated at each click. What is still missing is a timer function to repeatedly update the running project's time display. Also, now that a reference to the LI element is stored in the Project object, we need to modify the saveProjects function, so that is doesn't try to store any HTML elements.

function saveProjects() { // Filter out properties to only include array indices, and those properties that need storing var json = JSON.stringify(array, function(key, value) { if (key == "name" || key == "totalFinishedTime" || key == "startedOn" || key >= 0) { // Only certain properties of Project are saved return value; } return undefined; }); localStorage["projects"] = json; } function timerFunc() { if (running) { updateProjectElement(running); } // Wait for the next second on the clock var now = Date.now(); var millis = now % 1000; var wait = 1000 - millis; window.setTimeout(timerFunc, wait); } document.addEventListener("DOMContentLoaded", function() { loadProjects(); createProjectsHtml(); timerFunc(); }, false);

Next step

There you have it. What is still missing?

  • Export to CSV
  • Working stop button
  • Clear all timers
  • Remove all projects

EDIT: Tajmkiper is now publicly available to anyone who wants to use it.
Try it: tajmkiper.com

Tajmkiper, part 1
Tajmkiper, part 2 (this part)
Tajmkiper, part 3

Tajmkiper, part 1

There are lots of project time clock apps out there for those who want to keep track of how much time they spend in different projects. There's a genuine need for this type of app, but I have still not found one that is as simple and easy to use as it could be. Most of them either cost money, require that I install some piece of software on my smartphone, or demand my email address for some reason.

I would like a minimal functional web interface, designed mobile-first, that runs completely local to my browser of choice. I don't want my project data to be stored anywhere but locally in the browser. And I want to be able to export my projects and time spent to Excel. That shouldn't be so difficult to create, so I'll start hacking.

Mockup

After som pen-on-paper action, I came up with the following html/css mockup:
Tajmkiper Mockup

<html> <head> <link rel="stylesheet" href="tajmkiper.css"> </head> <body> <ul id="projects"> <li class="running"> <a href="#"> <header>Creating Tajmkiper</header> <span class="time">1:37:11</span> </a> </li> <li> <a href="#"> <header>Killing some time</header> <span class="time">2:12:54</span> </a> </li> <li id="total"> <a href="#"> <header>Total:</header> <span class="time">3:50:05</span> </a> </li> </ul> <nav> <ul> <li id="addProject"><a href="#addProject"><header>Add new project</header></a></li> <li id="stopTimer" class="hideSpan"><a href="#stopTimer"><span>Stop timer</span></a></li> <li id="settings" class="hideSpan"><a href="#settings"><span>Tools</span></a></li> </ul> </nav> </body> </html> ul, li { margin: 0; padding: 0; list-style: none none inside; text-indent: 0; } a { color: inherit; text-decoration: none; } a:hover { text-decoration: underline; } #projects { position: fixed; top: 2px; left: 2px; right: 2px; bottom: 36px; overflow-x: hidden; overflow-y: auto; } #projects li { position: relative; background: #259; color: #fff; height: 32px; margin-top: 2px; overflow: hidden; } #projects a.activate { position: absolute; display: block; left: 0; top: 0; right: 0; bottom: 0; } #projects header { position: absolute; display: block; left: 8px; top: 0; right: 80px; bottom: 0; overflow: hidden; font-weight: normal; line-height: 32px; text-decoration: inherit; } #projects .time { position: absolute; display: block; right: 0px; width: 70px; padding-right: 8px; top: 0; bottom: 0; border-left: solid 1px #fff; overflow: hidden; line-height: 32px; font-size: 80%; text-decoration: none; text-align: right; background-color: #259; transition: background-color 0.25s ease-in-out; } #projects .running .time { background-color: #5f3; color: #281; font-weight: bold; } #projects #total { background: #ddd; } #projects #total header { text-align: right; } #projects #total .time { color: #000; font-weight: bold; } #projects li:first-child { margin-top: 0; } li { border-radius: 3px; } nav { position: fixed; bottom: 2px; left: 2px; right: 2px; height: 32px; } nav > ul { position: absolute; left: 0; top: 0; right: 0; bottom: 0; } nav li#addProject { position: absolute; left: 0; top: 0; bottom: 0; right: 68px; background: #4a2; color: #fff; } nav li#stopTimer { position: absolute; top: 0; bottom: 0; right: 34px; width: 32px; background: #a31 url('stop.png') no-repeat scroll 50% 50%; color: #fff; } nav li#settings { position: absolute; top: 0; bottom: 0; right: 0; width: 32px; background: #259 url('settings.png') no-repeat scroll 50% 50%; color: #fff; } nav a { display: block; position: absolute; left: 0; right: 0; top: 0; bottom: 0; padding: 0 8px 0 8px; line-height: 32px; } .hideText span { display: none; }

My thought is to simply click on a project to start that project's time clock (and stop any other running time clock), click on Add new project to pop up a dialog where I enter my project's name, and then the new project's time clock starts. When I go to lunch, I click the stop button. If I want to remove all projects, set all time clocks to zero, or export everything as a CSV file (using the CSV framework I've made), I click the "settings" button in the lower right corner.

I think this looks good for now. Next time, I'll start writing some JavaScript

EDIT: Tajmkiper is now publicly available to anyone who wants to use it.
Try it: tajmkiper.com

Tajmkiper, part 1 (this part)
Tajmkiper, part 2
Tajmkiper, part 3