如何在 JavaScript 中创建服务器端进度指示器?

发布于 2024-08-25 01:30:53 字数 346 浏览 3 评论 0原文

我想在我的网站中创建一个部分,用户可以在其中使用一些简单的更新按钮。

每个更新按钮都会发送到服务器,并在后台进行长时间的处理。

当服务器处理数据时,我希望用户有某种进度指示器,例如进度条或文本百分比。

我使用 jQuery 作为我的 JavaScript 库,使用 CodeIgniter (PHP) 作为服务器端框架,如果它很重要的话......

我正在考虑使用 PHP 的 flush() 函数来报告进度状态到 jQuery,但我不确定 jQuery 的 Ajax 函数是否在完成之前读取输出......

所以任何建议/解释都会有用且有帮助!

I want to create a section in my site, where a user has a few simple update buttons.

Each of these update buttons will be going to the server, and will do a long crunching behind the scene.

While the server crunches data, I want the user to have a some kind of progress indicator, like progress bar or textual percentage.

I'm using jQuery as my JavaScript library, and CodeIgniter (PHP) as the server-side framework, if it's important...

What I was thinking about is using PHP's flush() function to report progress status to jQuery, but I'm not sure that jQuery's Ajax functions are reading the output before it's complete...

So any advice/explanation would be useful and helpful!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

梨涡 2024-09-01 01:30:53

我将为您提供一个使用 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); 

就是这样;当更新发生时,它们会实时推送给您的客户。

I'm going to give you an example using WebSync On-Demand, but the same approach would work regardless of your choice of server.

Here's what you do. First, kick off the long-running operation somehow; your user clicks the button to start this process (I'm going to assume an Ajax call, but whatever works), and you return to them some sort of identifier, we'll call that 'myId', give it a value of '1'. Whether you do that by invoking a process of some sort, etc, is up to you.

Then, in your callback from that invocation, you would write something like so:

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);
  }
});

What that'll do is subscribe your client to receive notification about updates to the task, so all that's left is to push out the updates, which you'd do in whatever process is actually running the task. That would look like (in PHP, using the 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); 

That's it; as updates occur, they'll push out to your client in real-time.

揪着可爱 2024-09-01 01:30:53

要做到这一点非常困难。我们为我们的系统选择的是一个“伪造的”进度条——它只是一遍又一遍地动画(因为它是一个动画 gif,你可能会想到!)。

另一种方法是提交到一个脚本,并在后台进行该处理(并将进度输出到文件),同时向另一个脚本发出 Ajax 请求,该脚本的唯一责任是读取该进度文件并返回您所完成的进程的进度。是。这会起作用 - 感觉有点笨拙,但它至少可以解决你眼前的问题。

我对Comet之类的了解很少,所以这纯粹是基于我目前的理解。

It's pretty hard to get this right. What we've settled on for our system is a "faked" progress bar - it just animates over and over (which since it is an animated gif, you might expect!).

An alternative would be to submit to one script, and have that processing in the background (and outputting progress to a file) while making an Ajax request to another script whose only responsibility is to read that progress file and return how far through the process you are. This would work - it feels a little bit kludgy, but it would at least solve your immediate problem.

I know very little about Comet or the likes, so this is purely based on my current understanding.

寄意 2024-09-01 01:30:53

晚了三年,但这是我想出的解决方案。额外奖励:它适用于 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,它的来源是 < a href="https://github.com/knyri/simple-php-library/blob/master/lib/lib/db/pdo_database.functions.php" rel="nofollow">此处。抱歉,它不包含与 CodeIgniter 集成的任何内容。

3 years late, but here's a solution I came up with. Bonus: It works in IE7+

Uses:

The event table:

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)
)

The 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>

The 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.
}

In case you were wondering about PDOStatementWrapper the source for it is here. Sorry it doesn't include anything integrated with CodeIgniter.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文