// В этом файле содержится общий код таймера, который можно применять в любом месте. // Код не привязан к DOM и реализует общий алгоритм работы таймера. // Для обновления DOM в каждом случае будет разный код. // Для получения значений таймера см. список методов объекта. // Свойства, имена которых начинаются с подчеркивания, являются служебными и // не должны использоваться как API. // Лимит таймера - 12 часов var MAX_ALLOWED_VALUE = 60 * 60 * 12; // Получаем GET-параметр id из адресной строки. // Если параметра нет, или он не является числом, то возвращаем undefined. function getIdFromUrl(){ var id = window.location.search.split('id='); if (id[1]) { id = id[1].split('&'); id = parseInt(id[0]); return isNaN(id) ? undefined : id; } return undefined; } // Конструктор таймера // id - некий уникальный идентификатор, например, номер баги или тикета // onUpdate - функция, вызываемая при обновлении таймера. Опциональна. function Timer(id, onUpdate, limit){ this.limit = limit || MAX_ALLOWED_VALUE; this._storageKey = id; this.onUpdate = onUpdate; var storedValue = parseInt(Timer.storage.get(this._storageKey)); var storedPauseValue = parseInt(Timer.storage.get(this._storageKey + Timer.PAUSE_SUFFIX)); var startTime, pauseStartTime; // Определяем геттеры/сеттеры, чтобы при записи свойства автоматически обновлялся storage Object.defineProperty(this, '_startTime', { get: function(){ return startTime; }, set: function(value){ startTime = value; if(value === null){ Timer.storage.remove(this._storageKey); } else { Timer.storage.set(this._storageKey, value); } } }); Object.defineProperty(this, '_pauseStartTime', { get: function(){ return pauseStartTime; }, set: function(value){ pauseStartTime = value; if(value === null){ Timer.storage.remove(this._storageKey + Timer.PAUSE_SUFFIX); } else { Timer.storage.set(this._storageKey + Timer.PAUSE_SUFFIX, value); } } }); // Если есть сохраненные значения, то используем их if(!isNaN(storedValue)){ startTime = storedValue; pauseStartTime = isNaN(storedPauseValue) ? null : storedPauseValue; } else { // Если сохраненного значения нет, то принимаем текущее время за начало работы this._startTime = new Date().getTime(); this._pauseStartTime = null; } this.update(); } // Очищаем сохраненные значения // Используем, когда делаем submit задачи Timer.prototype.clear = function(){ Timer.storage.remove(this._storageKey); Timer.storage.remove(this._storageKey + Timer.PAUSE_SUFFIX); } // Возвращает количество секунд с момента старта с вычетом пауз. // Не изменяет состояния таймера, лишь считывает текущее значение Timer.prototype.getElapsedSeconds = function(){ if(this.isPaused()){ return Math.floor((this._pauseStartTime - this._startTime)/1000); } else { return Math.floor((new Date() - this._startTime)/1000); } } // Возвращает отформатированную строку, которую можно выводить на экран // Не изменяет состояния таймера, лишь считывает текущее значение Timer.prototype.getFormattedString = function(){ var secondsFull = this.getElapsedSeconds(); var minutesFull = Math.floor(secondsFull/60); var hours = Math.floor(minutesFull/60); var minutes = minutesFull - hours*60; var seconds = secondsFull - minutesFull*60; if (seconds < 10) { seconds = "0"+seconds; } if (minutes < 10) { minutes = "0"+minutes; } if (hours < 10) { hours = "0"+hours; } return hours + ":" + minutes + ":" + seconds; } // Таймер на паузе? Timer.prototype.isPaused = function(){ return this._pauseStartTime !== null; } // Сброс таймера. Если таймер стоял на паузе, то после сброса он останется на паузе. // Новые значения автоматически записываются в storage. Timer.prototype.reset = function(){ this._startTime = new Date().getTime(); // Если таймер был на паузе, то после сброса оставляем его на паузе this._pauseStartTime = this.isPaused() ? new Date().getTime() : null; this.update(); } // Включить автообноление с заданным интервалом // Функция onUpdate будет вызываться при каждом обновлении Timer.prototype.setAutoUpdate = function(interval){ interval = interval || 1000; if(this._updateIntervalId){ clearInterval(this._updateIntervalId); } this._updateIntervalId = setInterval(this.update.bind(this), interval); } // Изменяет состояние паузы. Если нет изменений, то ничего не произойдет. // При изменении состояния, новые значения автоматически запишутся в storage. Timer.prototype.setPause = function(state){ // Ничего не делаем, если нет изменений if(state === this.isPaused()) return; if(state){ this._pauseStartTime = new Date().getTime(); } else { this._startTime += (new Date().getTime() - this._pauseStartTime); this._pauseStartTime = null; } this.update(); } // Выключает автообновление, если включено Timer.prototype.stopAutoUpdate = function(){ if(this._updateIntervalId){ clearInterval(this._updateIntervalId); this._updateIntervalId = null; } } // Включает/выключает паузу Timer.prototype.togglePause = function(){ this.setPause(!this.isPaused()); } // Вызывает onUpdate, если определена. // Используется при автообнолении и при изменении состояния таймера Timer.prototype.update = function(){ if(this.getElapsedSeconds() > this.limit){ this.reset(); return; } if(typeof this.onUpdate === 'function'){ this.onUpdate(); } } // Суффикс, с которым сохраняется время паузы Timer.PAUSE_SUFFIX = 'p'; // Не используется, чтобы не ломать обратную совместимость с уже сохраненными сессиями Timer.PREFIX = 'timer_'; // Обертка над sessionStorage // При необходимости, можно будет заменить на другое хранилище Timer.storage = { get: function(key){ if (!window.sessionStorage) { // Ведем себя, будто ключ не найден return null; } else { return sessionStorage.getItem(key); } }, remove: function(key){ if(!window.sessionStorage){ return false; } else { sessionStorage.removeItem(key); return true; } }, set: function(key, data) { if (window.sessionStorage) { try { sessionStorage.setItem(key, data); return true; } catch(e) { return false; } } return false; } };