Anders Tornblad, web developer

I'm all about the web

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
The name of the project.
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.
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) { = 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 + ( - 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 =; }; 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.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 =; 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 =; 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, part 1
Tajmkiper, part 2 (this part)
Tajmkiper, part 3

Add a comment