Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save timesheet automatically #23

Open
crittermike opened this issue Aug 15, 2014 · 1 comment
Open

Save timesheet automatically #23

crittermike opened this issue Aug 15, 2014 · 1 comment

Comments

@crittermike
Copy link
Contributor

This should help with losing data due to getting logged out. Can take inspiration from the code of https://chrome.google.com/webstore/detail/enhanced-openair-timeshee/cndbpehenhdahdpiodadlihdkofcakkm?hl=en which does this (or did).

@crittermike
Copy link
Contributor Author

Here's the JS from the linked extension which auto-saves every 15 minutes of no activity. The good stuff is at the bottom.

var Entry = function(cell, month_year) {
  this.init(cell, month_year);
  this.enhance();
}

Entry.prototype.init = function(cell, month_year) {
  this.cell = cell;
  this.month_year = month_year;
  this.input = $('input', cell)[0];
  this.noteslink = $('a', cell)[0];
  this.name = this.input.name;

  this.notesinput = document.forms[0][this.name + '_dialog_notes'];
  this.fixHTML();
}

Entry.prototype.enhance = function() {
  this.addTimer();
  this.improveNotes();
  this.markToday();
}

Entry.prototype.fixHTML = function() {
  var nobr = this.cell;
  this.cell = $(this.cell).parent('td');
  $(nobr).wrap('<div class="entry-wrapper"></div>');
  $(nobr).children().unwrap();
  this.cell.addClass('entry-cell');
  this.cell = $('.entry-wrapper', this.cell);

  $(this.input).attr('autocomplete', 'off');
}

/**
 * Adds timer functionality to this entry.
 */
Entry.prototype.addTimer = function() {
  if (this.isToday() || this.hasSavedTimer()) {
    this.start = $('<a class="start-timer">Start Timer</a>').appendTo(this.cell)
    var input = this.input, self = this;

    this.start.toggle(function(){
      self.startTimer();
      $(this).text('Stop Timer');
    },
    function() {
      self.stopTimer();
      $(this).text('Start Timer');
    });

    this.loadTimer();
  }
}

Entry.prototype.startTimer = function() {
  var input = this.input;
  var ref = this;

  if (!input.value) { input.value = '0.00'; }
  this.timer = setInterval(function(){
    ref.input.value = Math.round((parseFloat(ref.input.value) + 0.01) * 100)/100;
    $(ref.input).trigger('change').trigger('blur');
    ref.timed += 0.01;
  }, 36000);

  $(this.input).addClass('running-timer');
}

Entry.prototype.stopTimer = function() {
  if(this.timer) {
    clearInterval(this.timer);
    this.timer = false;
    this.timed = 0;
  }
  $(this.input).removeClass('running-timer');
}

Entry.prototype.saveTimer = function(remainder) {
  var time = 0;
  if (window.is_form_save || remainder) {
    var timer = parseFloat(this.input.value);
    time = Math.round((timer * 100) % 25) / 100;
    if (time >= 0.13) {
      time = -(Math.round((0.25 - time) * 100) / 100);
    }
  } else {
    time = this.timed;
  }

  this.save('timer', time);
}

Entry.prototype.wasPersisted = function() {
  return $(this.input).parent('tr').has('font.error').size() > 0;
}

Entry.prototype.loadTimer = function() {
  var item = this.load('timer');
  if (item) {
    var val = Math.round(parseFloat(this.input.value) * 100)/100;
    if (isNaN(val)) { val = 0.0; }
    added = parseFloat(item);
    if (isNaN(added)) { added = 0.0; }
    val += added;
    this.timed = added;
    if (!this.wasPersisted()) {
      this.input.value = Math.round(val * 100) / 100;
    }
    this.start.click();
    $(this.input).change();

    this.remove('timer');
  }
}

Entry.prototype.hasSavedTimer = function() {
  return this.load('timer') ? true : false;
}

/**
 * Retrieves the notes for a given entry.
 */
Entry.prototype.getNotes = function() {
  var message = '';
  if (this.notesinput) {
    message = this.notesinput.value;
  }
  return message;
}
/**
 * Renders a Notes edit form.
 */
Entry.prototype.improveNotes = function() {
  var value = this.getNotes();
  var name = this.name + '_dialog_notes';

  $(this.noteslink).remove();
  var ref = this;

  $(this.input).focus(function() {
    Entry.closeAllNotes(ref);

    if (!ref.haspopover) {
      $(ref.input).popover({
        html : true,
        placement : 'bottom',
        trigger : 'manual',
        content : function() {
          var popover = $('<div class="tooltip-notes" id="' + ref.name + '_notes"></div>');
          var text = $('<textarea name="' + ref.name + '_notes_real_input" id="'+ ref.name +'_notes_real_input" placeholder="Note details..."></textarea>');
          text.text(ref.getNotes()).appendTo(popover);
          text.after('<div class="instructions pull-left"><sub><b>Enter</b> to Save, <b>Esc</b> to Cancel, <b>Shift+Enter</b> for newline.</sub></div>' +
            '<div class="popover-group pull-right clearfix">' +
            '<button class="save-notes btn btn-primary">Save</button>' +
            '<button class="close-notes btn btn-danger">Cancel</button>' +
          '</div>');
          return popover[0].outerHTML;
        },
        title : 'Notes',
      });

      // Show the Popover
      $(ref.input).popover('show');
      ref.haspopover = true;

      // Textarea Bindings
      $('#'+ref.name +'_notes_real_input').keyup(function(e){
        // Auto-height
        while(parseInt($(this).css('height')) <= $(this)[0].scrollHeight ){
          $(this)[0].rows++;
        }
      }).keydown(function(e){ //have to do this in keydown to prevent the enters from being accepted by the text box
        // Handle Keys
        key = (e.keyCode ? e.keyCode : e.which);
        if(key == 13 && !(e.ctrlKey || e.altKey || e.metaKey || e.shiftKey)) { // Enter
          e.stopPropagation();
          return ref.closeNotes(true);
        }else if(key == 27){ // Escape
          return ref.closeNotes();
        }
      }).keyup(); // Call on box display

      // Button Bindings
      $('.close-notes').click(function() {
        return ref.closeNotes();
      });
      $('.save-notes').click(function(){
        return ref.closeNotes(true);
      });
    }
  }).click(function(e){  //make it easier to get the popover back if you accidently close it
    $(this).trigger('focus');
  });

  this.loadNotes();
}

Entry.prototype.closeNotes = function(save, nofocus){
  if (save) {
    var notes = $('#' + this.name + '_notes_real_input');
    if (notes.size()) {
      this.notesinput.value = notes.val();
    }
  }
  if (!nofocus) { this.input.focus(); }
  $(this.input).popover('destroy');
  this.haspopover = false;
  return false;
};

Entry.closeAllNotes = function(skip) {
  if (window.entries) {
    var e = window.entries.length;
    while(e--) {
      var entry = window.entries[e];
      if (skip && entry === skip) {
        continue;
      }
      entry.closeNotes(false, true);
    }
  }
};

Entry.prototype.saveNotes = function() {
  this.save('notes', this.getNotes());
}

Entry.prototype.loadNotes = function() {
  var local = this.load('notes');
  if (!this.getNotes() && local) {
    this.notesinput.value = local;
  }
}

Entry.prototype.getTid = function() {
  if (!this.tid) {
    var search = window.location.search.split(';'),
    params = [];
    for(var i = 0; i < search.length; i++) {
      var exploded = search[i].split('=');
      params[exploded[0]] = exploded[1];
    }

    this.tid = params['timesheet_id'];
  }
  return this.tid;
}

Entry.prototype.save = function(name, value) {
  var tid = this.getTid();
  localStorage[tid + '_' + this.name + '_' + name] = value;
}

Entry.prototype.load = function(name) {
  var tid = this.getTid();
  if (localStorage[tid + '_' + this.name + '_' + name]) {
    return localStorage[tid + '_' + this.name + '_' + name];
  }
  return false;
}

Entry.prototype.remove = function(name) {
  var tid = this.getTid();
  localStorage.removeItem(tid + '_' + this.name + '_' + name);
}

Entry.prototype.isToday = function() {
  if (typeof this.is_today == 'undefined') {
    //get our date string for this column
    var col = (this.name.split('_'))[1].replace('c', '');
    col = (+col) - 3;

    var date = parseInt($('.table-header-row td > font').eq(col).text());
    var col_date = new Date(this.month_year.join('-') + '-' + date);
    var today = new Date();

    this.is_today = (today.getDate() == date) && (today.getMonth() == col_date.getMonth()) && (today.getFullYear() == this.month_year[0]);
  }
  return this.is_today;
}

Entry.prototype.markToday = function() {
  if (this.isToday()) {
    this.cell.addClass('today');
  }
}

// Helper to get the date range for a timesheet
function getTimesheetMonthYear() {
  var month_year = document.forms[0]['_date'].value;
  return month_year.split('-').slice(0, 2);
}



// ======================== SETUP FUNCTION and EVENTS ================================ //
//If this is the proper timesheet view, create entries
if (window.location.search.indexOf(';action=grid;') > -1) {
  var m_y = getTimesheetMonthYear();

  var entries = [];
  //Create an Entry object for each time entry slot.
  $('td nobr', formtable).has('input').has('.notesIcon').removeClass('disabled').each(function(ind, el){
    entries.push(new Entry(el, m_y));
  });
  //add a class to disabled cells for theming
  $('td', formtable).has('nobr input').not('.entry-cell').not('.project-cell').addClass('entry-cell disabled');



  //save the timesheet timers.
  function submitTimesheet(nosubmit) {
    //get all our timers that are running
    var e = entries.length;
    while(e--) {
      var entry = entries[e];

      if (entry.timer) {
        entry.saveTimer();
      }
      entry.saveNotes();
    }
    if (!nosubmit) {
      $('input[name="_save_grid"]').click();
    }
  }

  $(window).bind('beforeunload', function(e) {
    submitTimesheet(true);
  });


  //allow users to configure autosave
  $('.date-row td').append('<div class="autosave-wrapper"><input id="autosave" type="checkbox" /><label for="autosave">AutoSave after 15m of inactivity.</label></div>');
  if (typeof(localStorage['autosave']) == 'undefined') {
    localStorage['autosave'] = true;
  }


  //save the form every 15m
  time = setTimeout(submitTimesheet, 900000);
  var resetTimeout = function() {
    clearTimeout(time);
    if (localStorage['autosave']) {
      time = setTimeout(submitTimesheet, 900000);
    }
  }

  //reset the timeout if the page is in use
  $('body').mousemove(function(){
    resetTimeout();
  });

  var check = localStorage['autosave'] && localStorage['autosave'] != 'false';
  $('#autosave').prop('checked', check).click(function(e){
    localStorage['autosave'] = this.checked;
    resetTimeout();
  });
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant