No one else submitted entries for the challenge so you get to look at my solution. I delved further into the chrome api and thought that using the chrome storage sync would be a good idea because it allows the user to store data that will remain constant even if they accidentally close their browser. The only caveat is that time will not be recorded when they close the browser, not much I can do about that.
The manifest file for the extension is pretty straight forward. The only new thing this time is that we added storage in the permissions section to gain access to the chrome storage api
{
"manifest_version": 2,
"name": "Time Tracker",
"description": "Tracks a user's time.",
"version": "1.0",
"browser_action": {
"default_icon":"icon16.png",
"default_popup":"app.html",
"css": "mycss.css",
"scripts":["jquery-3.2.1.js" , "app.js"]
},
"background": {
"scripts" : ["jquery-3.2.1.js","background.js"],
"persistent" : true
},
"permissions" : [
"storage"
]
}
var seconds=0;
var myTimer;
var iCompany="Company 1";
var iDescription="Stuff I do";
var finalTime =0;
var myCompany, myFinalTime, myDescription, myObject, myRecords;
//set the initial values in chrome storage for all the variables
chrome.storage.sync.set({
'company' : iCompany,
'description' : iDescription,
'startButton' : false,
'stopButton' :true,
'showRecord' :true,
'finalTime': finalTime,
'items':[]
});
//listen for actions from app.js
chrome.runtime.onMessage.addListener(
//receive click event from start timer button, run the timer in the background
function (request,response) {
if(request.action === "startTimer"){
myTimer=setInterval(updateTimer,1000);
};
//stop the timer when stoptimer button pushed, reset the second in timer
if(request.action === "stopTimer"){
clearInterval(myTimer);
chrome.runtime.sendMessage({action: "timerStop" , data: seconds});
seconds=0;
};
//add the newest record to the items array, reset the finaltime to 0
if(request.action === "showRecords"){
chrome.storage.sync.get(['company','finalTime','description'],function(results){
myCompany=results.company;
console.log(myCompany);
myFinalTime=results.finalTime;
console.log(myFinalTime);
myDescription=results.description;
console.log(myDescription);
myObject={'company': myCompany,'finalTime': myFinalTime, 'description': myDescription};
console.log(myObject.finalTime);
chrome.runtime.sendMessage({action: "getRecords", data: myObject});
chrome.storage.sync.get(function(items) {
if (Object.keys(items).length > 0 && items.data) {
// The data array already exists, add to it the new record
items.data.push(myObject);
} else {
// The data array doesn't exist yet, create it
items.data = [myObject];
}
chrome.storage.sync.set(items, function() {
console.log('Data successfully saved to the storage!');
});
});
myFinalTime=0;
chrome.storage.sync.set({
'finalTime': myFinalTime
});
});
};
//reset all items to original values
if(request.action ==="reset"){
seconds=0;
myTimer=0;
iCompany="Company 1";
iDescription="Stuff I do";
finalTime =0;
chrome.storage.sync.set({
'company' : iCompany,
'description' : iDescription,
'startButton' : false,
'stopButton' :true,
'showRecord' :true,
'finalTime': finalTime
});
chrome.storage.sync.get(function(items) {
//for some reason setting the index to equal 0 had no effect so cycle through all records and use the pop function to delete them.
if (Object.keys(items).length > 0 && items.data) {
$.each(items.data , function (index){
items.data.pop(index);
});
}
chrome.storage.sync.set(items, function() {
console.log('Data successfully saved to the storage!');
});
});
};
});
//timer function constantly sends an update to the timer display in app.html
function updateTimer() {
seconds += 1;
chrome.runtime.sendMessage({action: "update",data : seconds});
};
As I was discussing with Ayman and Ryan in the Google Hangout, I found the most challenging part of this exercise in the show records action of the background script. With all other properties of the storage object I can just reference them with a chrome.storage.sync.set call and change the value. But with the items property, because it is an array, I had to surround the chrome.storage.sync.set with a chrome.storage.sync.get call, push in the new value and then run the set function. I don't understand why I can't just duplicate existing array, push in a new value and then use a simple set function to overwrite the existing array. The same thing when resetting the array. I had to use the pop function while cycling through the array to clear out the items on a reset. I couldn't just re-initialize the array or set the array length to zero.
$(function () {
//show the current values of all items based on settings in storage whenever app.html displayed
chrome.storage.sync.get(function (items) {
$('#recordsTest').html('')
if (Object.keys(items).length > 0 && items.data) {
$.each(items.data, function (index, val) {
$('#recordsTest').append("" + val.company + " , " + val.finalTime + " , " + val.description + "
")
});
}
chrome.storage.sync.get(['company', 'description', 'startButton', 'stopButton', 'finalTime', 'showRecord'], function (results) {
$('#comp').val(results.company);
$('#work').val(results.description);
$('#timerStart').prop('disabled', results.startButton);
$('#timerStop').prop('disabled', results.stopButton);
$('#final').prop('disabled', results.showRecord);
if ($('#final').prop('disabled')) {
//don't show final time if the show records button disabled
} else if (results.finalTime > 0) {
$('#totalTime').html(results.finalTime);
}
});
});
//set storage of company on change of dropdown
$('#comp').change(function () {
chrome.storage.sync.set({
'company': $('#comp').val()
});
});
//set storage of description on change of dropdown
$('#work').change(function () {
chrome.storage.sync.set({
'description': $('#work').val()
});
});
//start timer on background page, disable the start timer button, enable the stop timer button
$('#timerStart').click(function () {
chrome.runtime.sendMessage({
action: "startTimer"
});
this.disabled = true;
$('#timerStop').prop('disabled', false);
chrome.storage.sync.set({
'startButton': true,
'stopButton': false
});
});
//stop the timer running in the background, enable the show record button, disable the stop timer button
$('#timerStop').click(function () {
chrome.runtime.sendMessage({
action: "stopTimer"
});
this.disabled = true;
chrome.storage.sync.set({
'stopButton': true,
'showRecord': false
});
$('#final').prop('disabled', false);
});
//request the show records function from the background page, disable the show record button, re-enable the start button
$('#final').click(function () {
chrome.runtime.sendMessage({
action: "showRecords"
});
this.disabled = true;
chrome.storage.sync.set({
'showRecord': true,
'startButton': false
});
$('#timerStart').prop('disabled', false);
});
//request the reset action form the background page, clear the recordstest and total time fields.
$('#reset').click(function () {
chrome.runtime.sendMessage({
action: "reset"
});
$('#recordsTest').html('');
$('#totalTime').html('');
});
//listen for instructions from the background app
chrome.runtime.onMessage.addListener(
function (request) {
//update the timer field
if (request.action === "update") {
$('#showTimer').html(request.data);
};
//get the fianl time from the background app, set the storage of the final time, clear the final time field
if (request.action === "timerStop") {
$('#totalTime').html(request.data);
chrome.storage.sync.set({
'finalTime': request.data
});
$('#showTimer').html('');
};
//get the current set of records from the background app, append them to the list of records, clear the final time field
if (request.action == "getRecords") {
var x = request.data;
$('#recordsTest').append(""+x.company+" , " + x.finalTime + " , " + x.description + "
");
$('#totalTime').html('');
};
});
});
Obviously, instead of dropdowns we could link to a datasource and use predictive type to do a lookup on the companies you would want to choose from, but a simple dropdown works for demonstration purposes. The same with the description field. You could fill it with descriptions of the standard tasks you generally perform.
Time Tracker
I did do a bit of css to make the text in the buttons look right and to align things a bit. Here is a link to all the files from my dropbox if you want to try plugging in the extension into your browser.
Lengieng also asked us which method we would use to stop users from using the inspect option in your browser to preview question and answer matching when pulling JSON data dropdown from the server. I think the 2 best methods to accomplish this are to use BSON instead of JSON or to use an encryption library for javascript.
Here is a link to the Bson spec, and here is a link to the npm page for crypto.js