How to make a stopwatch continue counting after closing the page?

13

I need to create a timer that is started through a play , but I wanted a solution, other than just SESSION , to let even if the client closes the window or drops his internet, that is, it is not dependent on cache or front .

How to do this with PHP, JavaScript and MySQL?

    
asked by anonymous 08.05.2015 / 20:30

6 answers

13

Since the timer must store the current situation, I propose the following structure:

  • When accessing timer.html , an AJAX request for get_timer.php takes the value of the timer and the situation (paused / running);
  • The "Start" and "Pause" buttons send an AJAX request to grava_acao.php which writes the timestamp and the current action based on the last recorded action;
  • It will be possible to generate reports with the intervals stopped and running;

database

CREATE TABLE 'timer' (
    'id' INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    'user_id' INT(10) UNSIGNED  NOT NULL,
    'action' VARCHAR(5) NOT NULL,
    'timestamp' INT(10) UNSIGNED NOT NULL,
    PRIMARY KEY('id')
);

timer.html

<script src="http://code.jquery.com/jquery.js"></script><script>varformat=function(seconds){vartempos={segundos:60,minutos:60,horas:24,dias:''};varparts=[],string='',resto,dias;for(varunidintempos){if(typeoftempos[unid]==='number'){resto=seconds%tempos[unid];seconds=(seconds-resto)/tempos[unid];}else{resto=seconds;}parts.unshift(resto);}dias=parts.shift();if(dias){string=dias+(dias>1?'dias':'dia');}for(vari=0;i<3;i++){parts[i]=('0'+parts[i]).substr(-2);}string+=parts.join(':');returnstring;};$(function(){vartempo=0;varinterval=0;vartimer=function(){$('.timer').html(format(++tempo));};$.post('get_timer.php',function(resp){$('button').text(resp.running?'Pausar':'Iniciar');tempo=resp.seconds;timer();if(resp.running){interval=setInterval(timer,1000);}});$('button').on('click',function(){varbtn=this;btn.disabled=true;$.post('grava_acao.php',function(resp){btn.disabled=false;$(btn).text(resp.running?'Pausar':'Iniciar');if(resp.running){timer();interval=setInterval(timer,1000);}else{clearInterval(interval);}});});});</script><divclass="timer">0 segundos</div>
<button>Iniciar</button>

get_timer.php

<?php
// definir no seu código
$pdo = new PDO($dsn, $username, $passwd);
$user_id = 1; // $_SESSION['user_id'];

$params = array(':user' => $user_id);
$stt = $pdo->prepare('SELECT 'action', 'timestamp' FROM 'timer' WHERE 'user_id' = :user ORDER BY 'id'');
$stt->execute($params);
$tempos = $stt->fetchAll(PDO::FETCH_ASSOC);
$seconds = 0;
$action = 'pause'; // sempre inicia pausado
foreach ($tempos as $tempo) {
    $action = $tempo['action'];
    switch ($action) {
        case 'start':
            $seconds -= $tempo['timestamp'];
            break;
        case 'pause':
            // para evitar erro se a primeira ação for pause
            if ($seconds !== 0) {
                $seconds += $tempo['timestamp'];
            }
            break;
    }
}
if ($action === 'start') {
    $seconds += time();
}
header('Content-Type: application/json');
echo json_encode(array(
    'seconds' => $seconds,
    'running' => $action === 'start',
));

grava_acao.php

<?php
// definir no seu código
$pdo = new PDO($dsn, $username, $passwd);
$user_id = 1; // $_SESSION['user_id'];

$params = array(':user' => $user_id);
$stt1 = $pdo->prepare('SELECT 'action' FROM 'timer' WHERE 'user_id' = :user ORDER BY 'id' DESC LIMIT 1');
$stt1->execute($params);
$newAction = 'start';
if ($stt1->rowCount() && $stt1->fetchColumn() === 'start') {
    $newAction = 'pause';
}
$stt2 = $pdo->prepare('INSERT INTO 'timer' ('user_id', 'action', 'timestamp') VALUES (:user, :action, :time)');
$params[':action'] = $newAction;
$params[':time'] = time();
$stt2->execute($params);
header('Content-Type: application/json');
echo json_encode(array(
   'running' => $newAction === 'start',
)); // para atualizar status, no caso de concorrência
    
11.05.2015 / 19:11
12

You can implement a front-end timer by JavaScript where clicking Play starts the timer when the user clicks Pause or close the window you should then send an Ajax to PHP to update the current value of the timer in the database. The next time the screen is opened, an Ajax request is then made to PHP so that it returns the current value of the timer that is saved in the database. >

HTML:

<body onload="restore_timer();">
    <form name="form">
        <input type="text" name="cronometro" value="00:00:00" />
        <br />
        <button type="button" onclick="timer(this); return false;">Play</button>
    </form>
</body>

JavaScript:

$(document).ready(function() {
    var t = form.cronometro.value;
    var hora = t.substring(0, 2);
    var minuto = t.substring(3, 5);
    var segundo = t.substring(6, 8);

    restore_timer = function() {
        send_ajax("get_time");
    }

    var interval;
    send_ajax = function(opt) {
        $.ajax({
            method: "POST",
            async: "false",
            url: "timer.php",
            data: {"opt": opt}
        }).done(function(msg) {
            if (opt == "get_time") {
                if (msg.status == "started") {
                    $('button').html('Pause');
                    interval = setInterval('tempo()', 983);
                }
                form.cronometro.value = msg.time;
                t = msg.time;
                hora = t.substring(0, 2);
                minuto = t.substring(3, 5);
                segundo = t.substring(6, 8);
            }
        });
    }

    timer = function(obj) {
        var html = $(obj).html();
        if (html == 'Play') {
            interval = setInterval('tempo()', 983);
            send_ajax("start_timer");
            $(obj).html('Pause');
        } else {
            clearInterval(interval);
            send_ajax("save_time");
            $(obj).html('Play');
        }
    }

    tempo = function(){ 
       if (segundo < 59) {
          segundo++;
          if (segundo < 10)
             segundo = "0" + segundo;
       } else if (segundo == 59 && minuto < 59) {
          segundo = 0 + "0";
          minuto++;
          if (minuto < 10)
             minuto = "0" + minuto;
       }
       if (minuto == 59 && segundo == 59) {
          segundo = 0 + "0";
          minuto = 0 + "0";
          hora++;
          if (hora < 10)
             hora = "0" + hora;
       }
       form.cronometro.value = hora + ":" + minuto + ":" + segundo;
    }
});

PHP:

if ($_POST) {
    $opt = $_POST['opt'];

    $servername = "localhost";
    $username = "USUARIO";
    $password = "SENHA";
    $dbname = "BANCO_DE_DADOS";

    $conn = new mysqli($servername, $username, $password, $dbname);
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }

    if ($opt == "get_time") {
        $sql = "SELECT * FROM time_stamps WHERE id = 1";
        $result = $conn->query($sql);

        $old_time_stamp;
        header('Content-Type: application/json');
        if ($result->num_rows > 0) {
            while($row = $result->fetch_assoc()) {
                $old_time_stamp = $row["time_stamp"];
            }

            $date = new DateTime();
            $time_stamp = $date->getTimestamp();

            echo json_encode(array("status" => "started", "time" => date('H:i:s', ($time_stamp - $old_time_stamp))));
        } else {
            $sql = "SELECT * FROM timer WHERE id = 1";
            $result = $conn->query($sql);

            if ($result->num_rows > 0) {
                while($row = $result->fetch_assoc()) {
                    echo json_encode(array("time" => $row["time"]));
                }
            }
        }
    } elseif ($opt == "start_timer") {
        $date = new DateTime();
        $time_stamp = $date->getTimestamp();
        $sql = "INSERT INTO time_stamps VALUES (1, '{$time_stamp}')";
        $result = $conn->query($sql);
    } elseif ($opt == "save_time") {
        $sql = "SELECT * FROM time_stamps WHERE id = 1";
        $result = $conn->query($sql);

        $old_time_stamp;
        if ($result->num_rows > 0) {
            while($row = $result->fetch_assoc()) {
                $old_time_stamp = $row["time_stamp"];
            }
        }

        $sql = "DELETE FROM time_stamps WHERE id = 1";
        $result = $conn->query($sql);

        $date = new DateTime();
        $time_stamp = $date->getTimestamp();

        $new_time = explode(":", date('H:i:s', ($time_stamp - $old_time_stamp)));

        $sql = "SELECT * FROM timer WHERE id = 1";
        $result = $conn->query($sql);

        $old_time;
        if ($result->num_rows > 0) {
            while($row = $result->fetch_assoc()) {
                $old_time = explode(":", $row["time"]);
            }
        }

        $hour = intval($old_time[0] + $new_time[0]);
        $minute = intval($old_time[1] + $new_time[1]);
        $second = intval($old_time[2] + $new_time[2]);
        while ($second >= 60) {
            $second = $second - 60;
            $minute++;
        }
        while ($minute >= 60) {
            $minute = $minute - 60;
            $hour++;
        }
        if ($second < 10)
            $second = "0" . strval($second);
        if ($minute < 10)
            $minute = "0" . strval($minute);
        if ($hour < 10)
            $hour = "0" . strval($hour);

        $time = "{$hour}:{$minute}:{$second}";

        $sql = "UPDATE timer SET time = '{$time}' WHERE id = 1";
        $result = $conn->query($sql);
    }
    $conn->close();
}

DataBase:

CREATE TABLE timer (
    id INT PRIMARY KEY,
    time VARCHAR(10) NOT NULL
);

CREATE TABLE time_stamps (
    id INT PRIMARY KEY,
    time_stamp VARCHAR(255) NOT NULL
);

INSERT INTO timer values (1, "00:00:00");
  

In this example I only have one record in the timer table, if you   need to change logic a bit, but I   it will not be a problem.

Follow example working .

Attention: I have not made any validation of the value passed through the requests, if the user changes the HTML on hand and Pause will send to the bank and save normally.     

11.05.2015 / 17:38
4

Developed a solution with persistent data stored in file and using localStorage. I think this solution will suit your case.

The solution is simple, the code is great because I created a manager to test the operation.

If you want to store a database, simply change the persistence functions of the class.

<?php

//
// Lib 
//

const CHRON_PATH  = '\path\to\data\';

const CHRON_STOP  = 0x00;
const CHRON_PAUSE = 0x01;
const CHRON_PLAY  = 0x03;

class PersistentChronometer {
    // fields

    private $_id;
    private $_elapsed;
    private $_status;
    private $_statustime;

    // construtor privado

    private function __construct() {}

    // propriedades

    function __get($prop) {
        switch($prop) {
        case 'id':
            return $this->_id;
        case 'status':
            return $this->_status;
        case 'elapsed':
            $elapsed = $this->_elapsed;

            if ($this->_status == CHRON_PLAY)
                $elapsed += max(microtime(true) - $this->_statustime, 0);

            return $elapsed;
        }
    }

    // comandos

    public function play() {
        if($this->_status == CHRON_PLAY)
            return;

        $this->_statustime = microtime(true);
        $this->_status = CHRON_PLAY;
    }

    public function pause() {
        if($this->_status != CHRON_PLAY)
            return;

        $time = microtime(true);

        $this->_elapsed += max($time - $this->_statustime, 0);

        $this->_statustime = microtime(true);
        $this->_status = CHRON_PAUSE;
    }

    public function stop() {
        if($this->_status == CHRON_STOP)
            return;

        $this->_statustime = microtime(true);
        $this->_elapsed = 0;
        $this->_status = CHRON_STOP;
    }

    // persistência

    public static function create() {
        $chron = new PersistentChronometer();
        $chron->_statustime = microtime(true);
        $chron->_elapsed = 0;
        $chron->_status = CHRON_STOP;

        $i = 0;
        while (true) {
            $chron->_id = ((int)$chron->_statustime) . '$' . $i++;

            $fname = CHRON_PATH . $chron->_id . '.chron';
            if(file_exists($fname)) 
                continue;
            $f = fopen($fname, 'w');
            fwrite($f, $chron->serialize());
            fclose($f);
            break;
        } 

        return $chron;
    }

    public static function load($id) {
        $fname = CHRON_PATH . $id . '.chron';

        if(!file_exists($fname))
            return false;
        $f = fopen($fname, 'r');
        $chron = PersistentChronometer::unserialize(fread($f, 255));
        fclose($f);

        return $chron;
    }

    public function save() {
        $fname = CHRON_PATH . $this->_id . '.chron';
        $f = fopen($fname, 'w');
        ftruncate($f, 0);
        fwrite($f, $this->serialize());
        fclose($f);
    }

    public function delete() {
        $fname = CHRON_PATH . $this->_id . '.chron';
        unlink($fname);
    }

    public function serialize() {
        return json_encode(array(
            'id' => $this->_id,
            'elapsed' => $this->_elapsed,
            'status' => $this->_status,
            'statustime' => $this->_statustime
        ));
    }

    public static function unserialize($string) {
        $data = json_decode($string);

        $chron = new PersistentChronometer();
        $chron->_id = $data->id;
        $chron->_elapsed = $data->elapsed;
        $chron->_status = $data->status;
        $chron->_statustime = $data->statustime;

        return $chron;
    }
}

//
// Comandos
//

if(isset($_GET['action'])) {
    switch($_GET['action']) {
    case 'play':
        if(isset($_GET['id'])
          && (($chron = PersistentChronometer::load($_GET['id'])) !== false)) {
            $chron->play();
            $chron->save();
        }
        break;
    case 'pause':
        if(isset($_GET['id'])
          && (($chron = PersistentChronometer::load($_GET['id'])) !== false)) {
            $chron->pause();
            $chron->save();
        }
        break;
    case 'stop':
        if(isset($_GET['id'])
          && (($chron = PersistentChronometer::load($_GET['id'])) !== false)) {
            $chron->stop();
            $chron->save();
        }
        break;
    case 'new':
        PersistentChronometer::create();
        break;
    case 'delete':
        if(isset($_GET['id'])
          && (($chron = PersistentChronometer::load($_GET['id'])) !== false)) {
            $chron->delete();
        }
        break;
    case 'get':
        if(isset($_GET['id'])
          && (($chron = PersistentChronometer::load($_GET['id'])) !== false)) {

            $data = new stdClass();
            $data->id = $chron->id;
            $data->elapsed = $chron->elapsed;
            $data->status = $chron->status;
            $data->result = true;

            echo json_encode($data);
        } else {
            echo '{"result": false}';
        }

        return;
    }
}

//
// Output
//

?>
<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript" src="//code.jquery.com/jquery-2.1.3.min.js"></script>
    <script>
        $(function(){
            var cEl = $('#chron');
            if(!window.localStorage)
                cEl.text('Para esta solução é necessário o uso de localStorage. Mas também podem ser utilizados cookies ou variaveis de sessão!!!');
            else {
                var id = localStorage.getItem("chron-id");

                if(!id)
                    cEl.text('Nenhum cronômetro definido!');
                else {
                    loadChronometer(id);
                    cEl.text('Aguarde...');
                }
            }
        });

        function setChronometer(id) {
            localStorage.setItem("chron-id", id);
            loadChronometer(id);
        }

        function loadChronometer(id) {
            var status = {
                <?php echo CHRON_STOP ?>: 'Parado',
                <?php echo CHRON_PLAY ?>: 'Executando',
                <?php echo CHRON_PAUSE ?>: 'Interrompido'
            };
            var cEl = $('#chron');
            $.getJSON('?action=get&id=' + id, null, function(data) {
                if(data.result)
                    cEl.text(
                        'ID: ' + data.id + "\n" +
                        'Estado: ' + status[data.status] + "\n" +
                        'Tempo (seg): ' + data.elapsed + "\n"
                    ).append(
                        '<a href="javascript:setChronometer(\'' + data.id + '\')">Atualizar</a> | ' +
                        (data.status == <?php echo CHRON_STOP ?> ? '' : '<a href="?action=stop&id=' + data.id + '">Parar</a> | ') +
                        (data.status == <?php echo CHRON_PLAY ?> ? '' : '<a href="?action=play&id=' + data.id + '">Executar</a> | ') +
                        (data.status != <?php echo CHRON_PLAY ?> ? '' : '<a href="?action=pause&id=' + data.id + '">Interromper</a> | ') +
                        '<a href="javascript:setChronometer(null)">Remover</a>' 
                    );
                else
                    cEl.text('Nenhum cronômetro definido!');
            });
        }
    </script>
</head>
<body>
<h1>Cronômetro padrão</h1>

<pre id="chron" style="white-space: pre"></pre>


<h1>Gerenciador</h1>
<table>
    <thead>
        <td>ID</td>
        <td>Estado</td>
        <td colspan="5">Comandos</td>
        <td>Tempo (seg)</td>
    </thead>
<?php

$files = scandir(CHRON_PATH);
$chrons = array();
foreach($files as $file) {
    if (substr($file, -6) == '.chron')
        $chrons[] = PersistentChronometer::load(substr($file, 0, -6));
}

$status = array(
    CHRON_STOP  => 'Parado',
    CHRON_PLAY  => 'Executando',
    CHRON_PAUSE => 'Interrompido'
);

foreach($chrons as $chron) {
    $td = array();
    $td[] = $chron->id;
    $td[] = $status[$chron->status];

    $td[] = $chron->status == CHRON_STOP ? '' : '<a href="?action=stop&id='. $chron->id .'">Parar</a>';
    $td[] = $chron->status == CHRON_PLAY ? '' : '<a href="?action=play&id='. $chron->id .'">Executar</a>';
    $td[] = $chron->status != CHRON_PLAY ? '' : '<a href="?action=pause&id='. $chron->id .'">Interromper</a>';
    $td[] = '<a href="?action=delete&id='. $chron->id .'">Destruir</a>';
    $td[] = '<a href="javascript:setChronometer(\''. $chron->id .'\')">Padrão</a>';
    $td[] = $chron->elapsed;
    echo '<tr id="cron-'. $chron->id .'"><td>'. implode('</td><td>', $td) .'</td></tr>';
}

?>
</table>
<a href="?action=new">Novo cronômetro</a>
</body>
</html>
    
11.05.2015 / 21:41
2

I believe that if you can use the localStorage () attributes to come up with a solution! LocalStorage is a function implemented in more up-to-date browsers (other than IE) where the browser saves information about that domain accessed within a variable, so you can pick up and save information in the variable when the page is closed and when you open the the initial value of the click and based on this continue counting the timer. Variable records are only deleted when rewritten or when the browser cache is cleared.

    
11.05.2015 / 19:57
1

You could store in the cookie or via SQL Web the initial value, the timestamp where you started your timer. When leaving the page the value would be saved in the user's browser, and if it already has some data recorded in the browser it would continue where it left off. It would also be interesting to store the status of your timer (running, paused).

    
11.05.2015 / 19:13
1

If you do not have to worry about the user changing their variables in js I advise following the @LucasPolo solution, otherwise it would do something like this:

When starting the timer, js executes a request to the server that saves the current timestamp (obtained through php, not js) in the database.

js continues to run the timer and at any given interval of time, or when the user returns to the browser (after having closed it) 0 js makes a request to the server to get the time elapsed from the beginning.

    
11.05.2015 / 19:49