Как создать серверный индикатор прогресса в JavaScript? - PullRequest
2 голосов
/ 14 марта 2010

Я хочу создать раздел на своем сайте, где у пользователя есть несколько простых кнопок update.

Каждая из этих update кнопок будет отправляться на сервер и будет долго обрабатывать сцену.

Пока сервер обрабатывает данные, я хочу, чтобы у пользователя был какой-то индикатор прогресса, например индикатор выполнения или текстовый процент.

Я использую jQuery в качестве библиотеки JavaScript и CodeIgniter (PHP) в качестве серверной инфраструктуры, если это важно ...

То, о чем я думал, - это использование PHP flush() функции для сообщения о состоянии процесса в jQuery, но я не уверен, что функции Ajax jQuery читают вывод до его завершения ...

Так что любой совет / объяснение будет полезным и полезным!

Ответы [ 3 ]

3 голосов
/ 22 марта 2010

Я приведу пример использования WebSync On-Demand , но тот же подход будет работать независимо от вашего выбора сервера.

Вот что ты делаешь. Во-первых, начните длительную операцию как-то; ваш пользователь нажимает кнопку, чтобы начать этот процесс (я собираюсь принять вызов Ajax, но все, что работает), и вы возвращаете им какой-то идентификатор, мы назовем его «myId», присвоив ему значение « 1' . Делаете ли вы это, вызывая какой-то процесс и т. Д., Решать только вам.

Затем, в вашем обратном вызове из этого вызова вы написали бы что-то вроде этого:

var myId = 1; // this would be set somewhere else
client.initialize('api key');
client.connect();
client.subscribe({
  channel: '/tasks/' + myId,
  onReceive: function(args){
    // update the progress bar
    myProgressBar.update(args.data.progress);
  }
});

Что нужно сделать, так это подписать клиента на получение уведомлений об обновлениях задачи, так что все, что осталось, это отправить обновления, что вы будете делать в любом процессе, который фактически выполняет задачу. Это будет выглядеть (в PHP с использованием SDK):

$publisher = new Publisher(
    "11111111-1111-1111-1111-111111111111", // your api key again
    "mydomain.com" // your domain
);

// publish data
$response = $publisher->publish(array(
    array(
        'channel' => '/tasks/' . $myId, //comes from somewhere
        'data' => (object) array(
            'progress' => '45' //45% complete
        )
    )
));

// success if empty (no error)
$success = empty($response); 

Вот и все; по мере появления обновлений они будут отправляться на ваш клиент в режиме реального времени.

2 голосов
/ 15 марта 2010

Довольно сложно понять это правильно. То, что мы установили для нашей системы, это «фальшивый» индикатор выполнения - он просто анимирует снова и снова (что, поскольку это анимированный GIF, вы можете ожидать!).

Альтернативой может быть отправка одного сценария и обработка в фоновом режиме (и вывод прогресса в файл) при выполнении Ajax-запроса к другому сценарию, единственная обязанность которого - прочитать этот файл прогресса и узнать, как далеко процесс, которым вы являетесь. Это сработало бы - это выглядит немного глупо, но, по крайней мере, решило бы вашу непосредственную проблему.

Я очень мало знаю о комете или подобных вещах, так что она основана исключительно на моем нынешнем понимании.

0 голосов
/ 16 мая 2013

3 года с опозданием, но вот решение, которое я придумал. Бонус: работает в IE7 +

Использование:

Таблица событий:

create table updates(
    evt_id int unsigned not null auto_increment,
    user_id int unsigned not null,
    evt_type enum('start','update','finish') not null,
    evt_msg varchar(255) not null,
    primary key (evt_id)
)

HTML:

<?php
include 'libconfig.php';
session_write_close();
if(count($_POST)){
    $db=db_get_connection();
    $stm=new PDOStatementWrapper(db_prepare($db,'INSERT INTO bupdates VALUES (:event_id,:user_id,:type,:message)'));
    if($stm->run(array(
        ':event_id'=>0,
        ':user_id'=>App::user()->getId(),
        ':type'=>$_POST['type'],
        ':message'=>$_POST['message']
    )))echo 'Inserted';
    return;
}
?>
<!doctype html>
<html>
<head>
<title>tester</title>
<link rel=stylesheet href="s/jquery-ui-1.10.3.custom.min.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="js/jquery-ui-1.10.3.custom.min.js"></script>
<script src="js/eventsource.js"></script>
<script src="js/json2.js"></script>
<script>
var MixerStatusMonitor=(function(){
    var _src=null,
    _handler={
        onStart:function(e){
            MixerStatus.setMax(parseInt(e.data));
        },
        onUpdate:function(e){
            var data=JSON.parse(e.data);
            MixerStatus.setValue(parseInt(data.progress));
            MixerStatus.setStatus(data.message);
        },
        onFinish:function(e){
            //var data=JSON.parse(e.data);
            MixerStatus.hide();
            _src.close();
        }
    };
    return {
        init:function(){
            if(_src)_src.close();
            _src=new EventSource('/daemon/updates.php?type=b');
            _src.addEventListener('update',_handler.onUpdate,false);
            _src.addEventListener('start',_handler.onStart,false);
            _src.addEventListener('finish',_handler.onFinish,false);
            MixerStatus.show();
        }
    };
})();
var MixerStatus=(function(){
        var dialog=null,pbar=null,text=null;
        return {
            init:function(){
                dialog=$('#buildStatus').dialog({autoOpen:false});
                pbar=$('#buildStatus .progress').progressbar({value:false});
                text=$('#buildStatus .text').progressbar();
            },
            setStatus:function(txt){
                text.html(txt);
            },
            setMax:function(val){
                pbar.progressbar('option','max',val);
            },
            setValue:function(val){
                pbar.progressbar('option','value',val);
            },
            show:function(){
                dialog.dialog('open');
            },
            hide:function(){
                dialog.dialog('close');
            }
        };
})();
$(document).ready(function(){
    MixerStatus.init();//build the UI
    $('#updater').on('submit',function(){
        $.ajax({
            type:'post',
            url:'test-updates.php',
            data:$('#updater').serialize(),
            beforeSend:function(){
                if($('#updater select[name=type]').val()=='start'){
                    MixerStatusMonitor.init();
                }
            }
        });
        return false;
    });
});
</script>
</head>
<body>
<p>Start event sets the max
<p>update event: {"progress":"","message":""}
<p>finish event: {"progress":"","message":""}
<form id=updater>
message: <input type=text name=message value="15"><br>
event type: <select name=type>
<option value=start>start</option>
<option value=update>update</option>
<option value=finish>finish</option>
</select><br>
<button>send message</button>
</form>
<div id=buildStatus title="Building">
<div class=text></div>
<div class=progress></div>
</div>
<div id=messages></div>
</body>
</html>

PHP:

<?php
header('Content-Type: text/event-stream');
define('TYPE_BROADCAST','b');
define('MAX_FAILURES',30);//30 seconds
define('MAX_WAIT',30);//30 seconds
define('MAX_START_WAIT',6);//30 seconds
/*
 * URL arguments:
 * type
 */
include '../libconfig.php';
session_write_close();
if(!App::loggedIn() || !App::user()){
    printEvent(0,'finish','Login session has expired.');
}
if($_GET['type']==TYPE_BROADCAST){//not needed;specific to the app I am creating
    $db=db_get_connection();
    $stm=new PDOStatementWrapper(db_prepare($db,'SELECT * FROM updates WHERE user_id=:user_id AND evt_id>:last_id'));
    $args=array(':user_id'=>App::user()->getId(),':last_id'=>0);
    $stm->bindParam(':user_id',$args[':user_id'],PDO::PARAM_INT);
    $stm->bindParam(':last_id',$args[':last_id'],PDO::PARAM_INT);
    $failures=0;
    $nomsg=0;
    if(!isset($_SERVER['HTTP_LAST_EVENT_ID'])){
        $start=new PDOStatementWrapper(db_prepare($db,'SELECT * FROM updates WHERE user_id=:user_id ORDER BY evt_id DESC'));
        $start->bindValue(':user_id',$args[':user_id'],PDO::PARAM_INT);
        $startwait=0;
        while(1){
            if($startwait>MAX_START_WAIT){
                printEvent(0,'finish','Timed out waiting for the process to start.');
                return;
            }
            sleep(5);
            $startwait++;
            if(!$start->run()){
                printEvent(0,'finish','DB error while getting the starting event.');
                return;
            }
            while($start->loadNext()){
                if($start->get('evt_type')=='finish')continue 2;
                if($start->get('evt_type')=='start')break;
            }
            if($start->get('evt_type')=='start'){
                $args[':last_id']=$start->get('evt_id');
                printEvent($start->get('evt_id'),'start',$start->get('evt_msg'));
                break;
            }
        }
    }else
        $args[':last_id']=$_SERVER['HTTP_LAST_EVENT_ID'];
    if($args[':last_id']===0){
        printEvent(0,'finish','ll');
        exit;
    }
    while(1){
        sleep(1);
        if(!$stm->run()){
            $failures++;
            if($failures>MAX_FAILURES){
                printEvent(0,'finish','Max failures reached.');
                break;
            }
        }
        if($stm->loadNext()){
            $failures=0;
            $nomsg=0;
            do{
                if($stm->get('evt_type')=='finish')break;
                $args[':last_id']=$stm->get('evt_id');
                printEvent($stm->get('evt_id'),$stm->get('evt_type'),$stm->get('evt_msg'));
            }while($stm->loadNext());
            if($stm->get('evt_type')=='finish'){
                printEvent($args[':last_id'],'finish',$stm->get('evt_msg'));
                break;
            }
        }else{
            $nomsg++;
            if($nomsg>MAX_WAIT){
                exit;//TODO: test
            }
        }
    }
}else{
    printEvent(0,'close','Unknown event type.');
}

function printEvent($id,$name,$data){
    echo "id: $id\nevent: $name\n";
    if(is_array($data)){
        foreach($data as $datum)
            echo "data: $datum\n";
        echo "\n";
    }else
        echo "data: $data\n\n";
    flush();
    if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
        $_SERVER['HTTP_X_REQUESTED_WITH']=='XMLHttpRequest')exit;//ajax request. Need to kill the connection.
}

В случае, если вам интересно узнать о PDOStatementWrapper, источник для него здесь . Извините, он не содержит ничего интегрированного с CodeIgniter.

...