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!
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.
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.
3 years late, but here's a solution I came up with. Bonus: It works in IE7+
Uses:
jQuery 1.9.1
jQuery UI 1.10(quick dialog box and progress bar)
Remy's EventSource Polyfill
JSON2 polyfill
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.
Related
I'll try to give a brief summary of the project and then the current code approach.
I have a RPi all set up with the latest Stretch OS.
I have installed MySQL, PHP, APahce and PhpMyAdmin and it working correctly.
I have it booting up to the default (locally hosted) webpage and in full screen (KIOSK) mode.
DB created, tables populated, queries in place.. and the webpage (drink menu) is displaying correctly as expected.
I have my Arduino UNO connected to the RPI via USB
The webpage displays a bunch of menu options.. each with its own 'order' button.
When any order button is clicked.. I save this button data to a hidden field, and use jQuery to submit/post the form (to itself)
Upon $_POST I grab this submitted data, and send it out via PHP over serial comm 1.
And here is where I am currently.
Since my Arduino is connected via USB to the RPi. I can not use the serial monitor to debug things.... do I have any other options here?
When I submit the webpage.. I -do- see the RX/TX lights on the Arduino blinking (leading me to believe it is receiving the serial data).. I can/will check if it is correct tonight by hooking up the stepper motor again and see if ti moves to the correct position(s)...
Here is where I am stuck/stumped a bit.. and could use some discussion to get me on the right path.
So after the Arduino 'does its magic'.. it is supposed to send a confirmation message back to the RPi.. saying the drink is complete.. and I can go back to the main drink menu awaiting another order
Because the webpage has already $_POSTed..and the serial data sent to the connected Arduino.. .. I then leave the page displaying a 'please wait' message... but because the page is already parse on the server side of things, I am left with the need on how to 'listen' to the serial posrt via PHP now.
I figured.. I could use some AJAX to call/load an external php script.. that will just wait/listen to the serial port.. and return the data to the AJAX 'success' callback.
But as I have never done this before.. I'm kind of leery if this will work.. or if this is even the correct way to do so.
Also random questions about the best place(s) to open and close the port.. especially if there is 2 separate scripts? (ie: can I open the port in one script.. and still access it in another? or does that file accessing it.. need to be the one that opens it?)
Here is my current snippet to handle the sending and waiting/listening of the serial port:
[code]
<?
if ($mode == 'submit') {
//grab posted data (save to var)
$drinkRecipe = $_POST['selectedDrink'];
//set-up com port
exec("mode /dev/ttyACM0 BAUD=9600 PARITY=N data=8 stop=1 xon=off");
//saw this on several RPi posts? (but not sure of the difference? or why one would be used over the other?)
//stty -F /dev/ttyACM0 cs8 9600 ignbrk -brkint -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts
//open serial port
$fp = fopen("/dev/ttyACM0", "w+"); //w = write w+ = read/write
//check if open
if (!$fp) {
echo "Not open";
//die();
} else {
//if open send data (via PHP) to connected Arduino on serial comm port 1 (ttyACM0)
fwrite($fp, '<' . $drinkRecipe . '>');
//arduino takes serial data, parsed it.. does it duty,
//and is supposed to reply back via serial to the awaiting (listening)
//PHP script executed via AJAX, since the front end needs to display
//a 'waiting' type message.. and the callback 'success' will
//re-direct back to menu/initial state
?>
<script type="text/JavaScript" language="JavaScript">
$.ajax({
//async: false,
//type: "POST",
url: "serial_listener.php",
//define success handler actions
success: function(response) {
//alert("PHP RETURN CHECK: "+response);
if($.trim(response) == 'complete'){
alert("Drink making is complete... return to main menu");
//do redirect here
}else{
alert("Some value other than 'complete' was returned... look into it!");
//not sure what to do? (back to main menu anyways?)
}
},
//define error handler actions
error: function(response) {
alert("PHP SERIAL READ FAIL: "+ 'Ready State: '+ response.readyState + ' Status: ' + response.status);
}
});
</script>
<?
//close connection
fclose($fp); //needed? //should this go in the external php script instead now?
}
}
Not even sure what should go into the: serial_listener.php script yet... just a while loop or something? Waiting for data? or end of file or something? (not sure how that works using fread() on serial port?)
Any suggestions to try and wrap my head around this is appreciated.
Update: I'm not sure if I am not explaining things correctly/clearly?
But when the page submits (to itself).. that is when the OUTGOING serial data is sent to the connected (via USB to the RPi) Arduino....
When the page 'posts' it sends the above data OUT.. and then displays a 'please wait' type of message.
At this point (as far as I understand it).. the server side script/parse is now COMPLETE... and I am left with a page saying 'please wait'...
There is no more parsing/server side ANYTHING going at this point..
That is why I thought/brought up the use an AJAX call to an external script that can sit and 'wait' (listen) to the serial port (un-clear as to how best going about this... a while() loop or something?)...
and then when the data eventually comes back...
*
(which there is no telling how long it will take for this 'serial
feedback' from the Arduino.. as each drink takes different amounts of
time to create).........
*
it will use the AJAX 'success' callback function to then update the page.. and ultimately just re-direct it back to the main drink menu page again.. to start all over.
I dont feel the use of timeout() or delay() on the Arduino is not only bad advice (ie: never use delay() if can help it)..... but I dont even see where/why that makes any sense?
Update:
And the contents of the serial_listener.php script: (script the AJAX snippet calls)
//set-up com port
exec("mode /dev/ttyACM0 BAUD=9600 PARITY=N data=8 stop=1 xon=off");
//open serial port
$fp = fopen("/dev/ttyACM0", "w+"); //w = write w+ = read/write
//check if open
if (!$fp) {
echo "Not open";
//die();
} else {
while(!feof($fp)){
$response = fread($fp, 10);
}
echo $response;
fclose($fp);
}
Final update:
I re-wrote things to use the AJAX call to SEND my data.. and also wait for the response.
The external php script the AJAX call executes is the ONLY place the port gets opened now (and I am not closing it)
Here is the SUBMIT state of the PHP form that has the AJAX call:
The sending of data works 100% of the time.. but I can NOT read my response.
if ($mode == 'submit') {
//grab posted data (save to var)
$drinkRecipe = $_POST['selectedDrink'];
?>
<div id="waitingContainer">
<p>Please wait, your brink is being made.</p>
</div>
<script type="text/JavaScript" language="JavaScript">
console.log("ajax routine hit");
//var drinkRecipe = "<?php echo $drinkRecipe ?>";
var drinkRecipe = "<?=$drinkRecipe?>";
var xhr = $.ajax({
//async: false,
type: "POST",
url: "serial_listener.php",
//datatype: "html",
datatype: "text",
data:({"drinkData":drinkRecipe}),
//define success handler actions
success:function(response) {
//alert("PHP RETURN CHECK: "+response);
if($.trim(response) == 'complete'){
console.log("Drink making is complete... return to main menu");
//do redirect here
}else{
console.log("Some value other than 'complete' was returned... look into it!");
console.log("RESPONSE SENT WAS: " + response);
//not sure what to do? (back to main menu anyways?)
}
//kill the request
xhr.abort();
},
//define error handler actions
error: function(response) {
console.log("PHP SERIAL READ FAIL: "+ 'Ready State: '+ response.readyState + ' Status: ' + response.status);
//kill the request
xhr.abort();
}
});
</script>
<?
}
Here are the contents of the serial_listener.php script the AJAX call executes:
//data sent from AJAX call (works)
$drinkData = $_POST['drinkData'];
//open serial port
$fp = fopen("/dev/ttyACM0", "w+"); //w = write w+ = read/write (works)
//$fp = fopen("/dev/ttyUSB0", "w+"); //w = write w+ = read/write //tried with USB-TTL cable too.. couldnt even send data)
//check if open
if (!$fp) {
echo "Not open";
//die();
} else {
if($drinkData != ''){
//send drink data
fwrite($fp, '<' . $drinkData . '>');
//wait for response
$chars = "";
do{
$char = fread($fp, 1);
$chars .= $char;
}while(strlen($char) < 1);
echo $char;
}else{
echo 'drink recipe is empty';
}
}
Can't comment to make a suggestion (no reputation)
I have no knowledge on Ajax , jquery, Raspberry Pi or PHP but...
If you have a Serial to USB TTL device like this you can use the SoftwareSerial library. Set up SoftwareSerial on a couple of unused digital pins on the arduino and send debugging info out there.
Not an answer, but a suggestion.
*********Edit***********
Come to think of it, doesn't the Raspberry Pi have a serial port?
If so you don't need the USB converter. Just set up a softwareSerial port on the Arduino and use that to connect to the Pi. Taht way you can use the USB port to send debug coms to your computer.
First, I dont have expirience with Raspberry nor Arduino, but do have played with serial communication in the past.
You are making things waay too complicated. Serial communication is not some strange animal from history channel - take it as a data layer. You would easely create needed front- and backend IF data would come from/go to database? Now, implement your system exactly same way, just instead of database connection use Serial communication.
Your serial_listener is simple:
create/configure port
send data
read data
close port
So, whole case:
frontend --post(d)--> serial_listener::COM1::send(d) ... COM1::read('OK') -->reply 'success'
PS.
with those small controllers... timeout() is your real friend ;)
And for serial communication I would suggest to use some library (e.g. php_serial.class)
I try to clarify with a code. I try to keep it as simple, just to illustrate the system as a whole.
Frontend, pure js/html and AJAX request:
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body onLoad="$('#loading-image').hide();">
<div width="100%" style="text-align: center;">
<div id="demo">
<h2>Content: forms/scripts/...</h2>
</div>
<div>
<button type="button"
onclick="makeSerialCall('serial.php')">order (send AJAX call to php script)
</button>
</div>
</div>
<img id='loading-image' src='https://media.tenor.com/images/011ebf9c192d1c9b26f242d526ee24bb/tenor.gif'></img>
<script>
var string = 'data which is collected from content above';
function makeSerialCall(url) {
$('#loading-image').show();
$.ajax({
type: "POST",
url: url,
data: string,
cache: false,
success: function(response){
$('#loading-image').hide();
console.log(JSON.stringify(response['info']));
//and whatever else you want to do afterwards...
}
});
}
</script>
</body>
Serverside:
<?php
if(!empty($_POST))
$data = $_POST["data"];
include "php_serial.class.php";
$serial = new phpSerial();
$serial->deviceSet("/dev/ttyUSB0");
$serial->confBaudRate(9600);
$serial->confParity("none");
$serial->confCharacterLength(8);
$serial->confStopBits(1);
$serial->confFlowControl("none");
$serial->deviceOpen();
$serial->sendMessage($data);
$read = $serial->readPort();
$serial->deviceClose();
header('Content-type: application/json');
$response_array['status'] = 'success';
$response_array['info'] = 'some additional info:'.$read;
echo json_encode($response_array);
?>
This way you have "loading.." image on the screen until response. Initial rendered page is there all the time. Ofcourse, you need alot of (error)checking, solution when there's no response from serial etc.etc. But basic system is not "rocket science".
Guys m working on my first live project and i am stuck at a point, where i need help with ajax jquery. i can do this with PHP but i wanna do this with ajax.
Here if user enter a product code ,so i want to compare this product code value into my database and show product name in my other form ,which will open after user input value:
Here in first field i want product name:
Here in my table you can see product code and product name:
ok so here is my html code in last option when user enter product code
Here is jquery i am sending user data to 8transectiondata.php to compare
And this is php file and i want $data['product_name']; to show
Here's a generic answer.
JS FILE:
$(document).ready(function () {
$('#myButtonId').on('click', function () {
var code = $('#myCodeInputId').val();
if (code !== '') { // checking if input is not empty
$.ajax({
url: './my/php/file.php', // php file that communicate with your DB
method: 'GET', // it could be 'POST' too
data: {code: code},
// code that will be used to find your product name
// you can call it in your php file by "$_GET['code']" if you specified GET method
dataType: 'json' // it could be 'text' too in this case
})
.done(function (response) { // on success
$('#myProductNameInput').val(response.product_name);
})
.fail(function (response) { // on error
// Handle error
});
}
});
});
PHP FILE:
// I assumed you use pdo method to communicate with your DB
try {
$dbh = new PDO('mysql:dbname=myDbName;host=myHost;charset=utf8', 'myLogin', 'myPassword');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e) {
exit('ERROR: ' . $e->getMessage());
}
$sql = "SELECT `product_name` FROM `products` WHERE `product_code` = :code";
$result = $dbh->prepare($sql);
$result->bindValue('code', $_GET['code'], PDO::PARAM_INT);
$result->execute();
if($result->rowCount()) { // if you got a row from your DB
$row = $result->fetchObject();
echo json_encode($row, JSON_UNESCAPED_UNICODE); // as we use json method in ajax you've got to output your data this way
// if we use text method in ajax, we simply echo $row
}
else {
// handle no result case
}
I know what you want to do, but without specific code the best I can do is give you a generalized answer.
When a user fills out a field, you want to post that field to the server, look up a product and return some stuff.
The basics are going to look like this.
$(document).ready( function(){
//rolling timeout
var timeout;
$('#field').on('keyup', function(e){
if(timeout) clearTimeout(timeout);
timeout = setTimeout( function(){
var data = {
"field" : $('#field').val()
};
$.post( '{url}', data, function(response){
if(response.debug) console.log(response.debug);
if(response.success){
//open other form
$('{otherFormProductField}').val(response.product);
}
}); //end post
},450); //end timeout
});//end onKeyup
}); //end onReady
Then in PHP, you have to process the request. Pull the field from the $_POST array, look it up in the Database. Then build a response array and send it back to the client as JSON. I like to build responses in a structure something like this.
{
success : "message", //or error : "message"
debug : "",
item : ""
}
Then in PHP I will do this.
ob_start();
..code..
$response['debug'] = ob_get_clean();
header("Content-type:application/json");
echo json_encode($response);
This way, you can still print out debug info (in side the output buffer calls ) when developing it and don't have to worry about it messing up the Json or the header call.
-note- Use a timeout, that you reset on each key press (a rolling timeout). What it does is reset the previous timeout each time the key is released. That way it only sends the request once the user quits typing (instead of sending request on every keypress). I have found 450 milliseconds to be about the perfect value for this. Not too long not too short. Basically once they stop typing for 450ms it will trigger the $.post
I have implemented custom search functionality for my angular js application.
For that I called an Ajax request to fetch the data.
This call happens on change event and that is why it calls my Ajax multiple times.
Please suggest as I am new in angular js.
You can use delay(idle time) into consideration. Like suppose I am typing in search text box If I am idle for 200ms, 400ms or whatever time you want, You can call an AJAX request.
If I type salman it will call api for 6 times. but suppose we will have idle time. we will call when user idle for that specific time.
To implement it in angular, you can use. $watch or bootstrap directives
So your best bet is to give yourself a bit of delay. As noted by Akash, you have to choose the delay time you find acceptable. You also need to make sure that the request is only made after the delay.
Here is one way to do it:
//In your controller
var _timeout;
$scope.fetchSearchResults = function(){
//We will clear the previous timeout because a key has been pressed
clearTimeout(_timeout);
//Set the timeout - if no key is pressed, it will execute. Else the line above will clear it.
_timeout = setTimeout(function(){
var keyword = $scope.searchKeyword.name;
//Do your AJAX request here
//We have delayed the request by 400ms - but you can change it as you please.
}, 400);
}
Your HTML:
<!-- Then in your HTML something similar to: -->
<input ng-model="searchKeyword.name" ng-keyup="fetchSearchResults()" />
Edit:
If you want to go the 'pure' angular way, you'd do it like this:
//In your controller
//NOTE: make sure you've injected $timeout into your controller
var _timeout;
$scope.fetchSearchResults = function(){
//We will clear the previous timeout because a key has been pressed
$timeout.cancel(_timeout);
//Set the timeout - if no key is pressed, it will execute. Else the line above will clear it.
_timeout = $timeout(function(){
var keyword = $scope.searchKeyword.name;
//Do your AJAX request here
//We have delayed the request by 400ms - but you can change it as you please.
}, 400);
}
I definitely recommend #jeanpaul's answer for "debouncing".
In addition to that, when you have potential for multiple concurrent AJAX requests and you want to handle the most recent one, it can be necessary to verify which request it is in your response handler. This is especially important when the responses don't always come in the same order they were requested (ie. an earlier request takes longer to respond than a later one)
A way to solve this is something like:
var activeRequest;
function doRequest(params){
// reqId is the id for the request being made in this function call
var reqId = angular.toJson(params); // I usually md5 hash this
// activeRequest will always be the last reqId sent out
activeRequest = reqId;
$http.get('/api/something', {data: params})
.then(function(res){
if(activeRequest == reqId){
// this is the response for last request
}
else {
// response from previous request (typically gets ignored)
}
});
}
You already accepted the answer but I want to share some code with you.
myapp.factory('formService', ['$http', '$filter', '$q', '$timeout', function ($http, $filter, $q, $timeout) {
var service = {};
service.delayPromise = null;
service.canceler = null;
service.processForm = function (url, formData, delay) {
if (service.delayPromise)
$timeout.cancel(service.delayPromise);
if (service.canceler)
service.canceler.resolve();
service.canceler = $q.defer();
service.delayPromise = $timeout(function (service) {
return service;
}, delay, true, service);
return service.delayPromise.then(function (service) {
service.delayPromise = null;
return $http({
method : 'POST',
url : url,
timeout : service.canceler.promise,
data : formData
});
})
}
return service;
]);
What does this do. It provides formService having processForm function, with accepts url, formData and delay.
processForm function delays submitting with $timeout service. If there are already exist delayed or pending submission it just cancels it.
And in your controller.
myapp.controller('myCtrl', ['formService', function (formService) {
$scope.formData = {};
$scope.pageData = {};
$scope.$watchCollection('formData', function (formData, oldData, scope) {
if (!angular.equals(formData, oldData)) {
var event;
formService.processForm(formData, event, 500).then(function (response) {
if (response.data instanceof Object)
angular.copy(response.data, scope.pageData);
});
}
});
}]);
My problem is that I need a PHP script to continously provide ouput at certein points of it's execution. I have workng AJAX to fetch the html but it only echos once the script is complete. Here is an example:
<?php
class test {
function test() {
echo '1';
sleep(20);
echo '2'
sleep(5);
}
}
I need some way to have the echo's actually displayed in the browser even though the function is still continuing. I have tried using global variables and session variables to no avail.
Thanks for the help!
I've seen many questions where people are trying to do this type of thing, but as far as I know it really isn't possible to do it the way you're trying to do it using only PHP and AJAX. In order for this to work using just AJAX rather than websockets or some other approach, you can break your script into separate pieces at the points where it needs to provide output, and provide the output as responses to separate AJAX requests. Here is a basic example of what I mean. The script on your page can start the process and makes repeated calls to step through the process until it's complete.
<button id="begin_process">Start</button>
<script type="text/javascript">
function step(step_number) {
$.get('test.php', {'step_number': step_number}, function(response){
console.log(response.message);
if (response.next_step) {
step(response.next_step);
}
}, 'json');
}
$(document).ready(function(){
$('#begin_process').click(function(){
console.log('go!');
step(1);
});
});
</script>
test.php:
<?php
class Test {
function step($step_number) {
switch ($step_number) {
case 1:
return array('next_step' => 2, 'message' => 'step 1 complete');
case 2:
sleep(20);
return array('next_step' => 3, 'message' => 'step 2 complete');
case 3:
sleep(5);
return array('message' => 'finished');
}
}
}
$test = new Test();
echo json_encode($test->step($_GET['step_number']));
I'm playing a bit around with push notifications, and want to update a page whenever there's a change in the database.
I have this from http://www.screenr.com/SNH:
<?php
$filename = dirname(__FILE__).'/data.php';
$lastmodif = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
$currentmodif = filemtime($filename);
while ($currentmodif <= $lastmodif) {
usleep(10000);
clearstatcache();
$currentmodif = filemtime($filename);
}
$response = array();
$response['msg'] = file_get_contents($filename);
$response['timestamp'] = $currentmodif;
echo json_encode($response);
?>
My data.php is a script getting data from a JSON file:
<script>function itnews_overview() {
$.getJSON('/ajax.php?type=itnews_overview', function(data) {
$.each(data.data, function(option, type) {
$('.bjqs').append('<li><span class="date">'+ type.submitted +'<br />'+ type.time +'</span><h2>' + type.title + '</h2><p>' + type.content + '</p></li>');
});
});
}
</script>
<script>
itnews_overview();
</script>
<div id="news">
<ul class="bjqs"></ul>
</div>
UPDATE: Code from index.php:
<script type="text/javascript">
var timestamp = null;
function waitForMsg() {
$.ajax({
type: "GET",
url: "getData.php?timestamp=" + timestamp,
async: true,
cache: false,
success: function(data) {
var json = eval('(' + data + ')');
if(json['msg'] != "") {
$(".news").html(json['msg']);
}
timestamp = json['timestamp'];
setTimeout('waitForMsg()',1000);
},
error: function(XMLHttpRequest, textStatus, errorThrown){
setTimeout('waitForMsg()',15000);
}
});
}
$(document).ready(function(){
waitForMsg();
});
</script>
As this file isn't saved when I add something to the database, filemtime won't work — is there another way I can check if new rows has been added to the table?
UPDATE: Trying to solve this with SSE.
I have two files, index.php and send_sse.php (inspiration from http://www.developerdrive.com/2012/03/pushing-updates-to-the-web-page-with-html5-server-sent-events/)
index.php:
<div id="serverData">Content</div>
<script type="text/javascript">
//check for browser support
if(typeof(EventSource)!=="undefined") {
//create an object, passing it the name and location of the server side script
var eSource = new EventSource("send_sse.php");
//detect message receipt
eSource.onmessage = function(event) {
//write the received data to the page
document.getElementById("serverData").innerHTML = event.data;
};
}
else {
document.getElementById("serverData").innerHTML="Whoops! Your browser doesn't receive server-sent events.";
}
</script>
send_sse.php:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$url = "content.json";
$str = file_get_contents($url);
$data = json_decode($str, TRUE);
//generate random number for demonstration
//echo the new number
echo "data: " . json_encode($data);
ob_flush();
?>
This, however, doesn't seem to work, which is probably because SSE needs plain text data. I just can't figure out how to do that and then wrap that content in a couple of HTML tags.
UPDATE: Okay, so now it's sort of working with SSE, thanks to VDP. I have the following:
$sql= "SELECT title, content, submitted FROM `flex_itnews` where valid = 1 order by submitted desc";
$query= mysql_query($sql);
setlocale(LC_ALL, 'da_DK');
while($result = mysql_fetch_array($query)){
echo "data: <li><span class='date'>". strftime('%e. %B', strtotime($result['submitted'])) ."<br />kl. ". strftime('%H.%M', strtotime($result['submitted'])) ."</span><h2>" . $result['title']. "</h2><p>" . $result['content'] ."</p></li>\n";
}
However, when I add anything new, it just echoes data: data: data. If I refresh the page, it displays correctly.
UPDATE: Using livequery plugin:
<script>
var source = new EventSource('data2.php');
source.onmessage = function (event) {
$('.bjqs').html(event.data);
};
$('#news').livequery(function(){
$(this).bjqs({
'animation' : 'slide',
'showMarkers' : false,
'showControls' : false,
'rotationSpeed': 100,
'width' : 1800,
'height' : 160
});
});
</script>
UPDATE: Trying to use delegate()
<script>
$("body").delegate(".news", "click", function(){
$("#news").bjqs({
'animation' : 'slide',
'showMarkers' : false,
'showControls' : false,
'rotationSpeed': 100,
'width' : 1800,
'height' : 160
});
var source = new EventSource('data2.php');
source.onmessage = function (event) {
$('.bjqs').append(event.data);
};
});
</script>
Yes! There are multiple (better) ways:
websocket (the best solution but not supported on older or mobile browsers)
Server sent events (SSE) (sort of polling but optimized just for the task you ask for)
Long polling (like you are doing)
Flash sockets
other plugin based socket stuff
ajax polling
I've posted another answer with examples about it before
I listed several transport methods. websockets being the ideal (because it's the only 2 way communication between server and client), SSE being my second choice. You won't need the $.getJSON method. The overall idea will be the same.
On the server side (php in your case) you query your database for changes. You return the data as JSON (json_encode(data) can do that). On the client side you decode the JSON (JSON.parse(data) can do that). With the data you received you update your page.
Just the polling like you where doing causes more overhead because you are doing lots of request to the server.
SSE is more "I want to subscribe to a stream" and "I want to stop listening". => less overhead
Websockets is more: "I set up a connection. I talk server listens. Server talks client listens" A full duplex connection. => least overhead
SSE Code example
The page the client goes to (for example index.html or index.php)
It's just a normal html page containing this javascript:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script>
//javascript:
var source = new EventSource('data.php');
source.onmessage = function (event) {
//here you do the stuff with the received messages.
//like log it to the console
console.log(event.data);
//or append it to div
$('#response').append(event.data);
};
</script>
</head>
<body>
<div id="response"></div>
</body>
</html>
The 'data.php' page:
<?php
/* set the header first, don't echo/print anything before this header is set! Else the default headers will be set first then this line tries to set the headers and results in an error because the header is already set. */
header("Content-Type: text/event-stream\n\n");
//query the database
$sql= "SELECT COUNT(*) FROM `messages`";
$query= mysql_query($sql);
$result = mysql_fetch_array($query);
$count = $result[0];
//return the data
echo "data: " . $count. "\n";
?>
So you only need those 2 pages.
UPDATE:
I had only seen your comments not the updates.. sorry ;)
if you use .delegate() you shouldn't use body but try a selector as high up the tree as possible (.bjqs in your case).
In you're case you don't even need live,delegate,on or all that! Just apply the bjqs again afther the content is updated.
var source = new EventSource('data2.php');
source.onmessage = function (event) {
$('.bjqs').html(event.data);
$("#news").bjqs({
'animation' : 'slide',
'showMarkers' : false,
'showControls' : false,
'rotationSpeed': 100,
'width' : 1800,
'height' : 160
});
};
This will give you issues too because you are constantly re-initializing bjqs and it isn't written to handle dynamically updating content. What you can do is send only data (with php) if there is new data. Check if the call returns empty, if not update:
var source = new EventSource('data2.php');
source.onmessage = function (event) {
if(event.data !=""){
$('.bjqs').html(event.data);
$("#news").bjqs({
'animation' : 'slide',
'showMarkers' : false,
'showControls' : false,
'rotationSpeed': 100,
'width' : 1800,
'height' : 160
});
}
};
You can count number of rows in a table, and than check if the number of rows is changed.
Without digging into your code too much im answering the question in the title:
add last modified column to your table, this has a built in mysql trigger that updates whenever the row is added or changes:
ALTER TABLE `yourTable`
ADD COLUMN `last_modified` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
ADD INDEX (`last_modified`);
and then query it like,
SELECT * FROM yourTable where last_modified > ?
("?" is the pdo placeholder you replace with last queried timestamp)