问题
Am making an application that will enable remote viewing of records and reports via a browser. I have used cakePHP to make the application and its working fine,but i have one little problem,since the application does not do any inserts its just reading the data,i want when a user has opened a view and a record has been inserted on the table,it should update all open clients, instead of the user refreshing the page to get the new records. Is there a cakePHP websocket plugin that actually works? Our webhost doesnt allow installing programs or adding apache modules so nodejs or similar solutions wont be applicable here.
Am looking for a purely php and javascript implementation where you just upload your application files to the webserver and everything runs. You dont have to run, install extras or do any configuration on apache or stuff... afteruploading your files
here is a function in one of my controllers(BooksController.php) that retrieves the data to the view
public function list_books()
{
$this->Books->recursive = 0;
$this->paginate = array('order' => array('Serial_No' => 'DESC'));
$this->set('All_Books', $this->paginate());
}
and here is one of my views(list_books.ctp) that displays the data in a table paginated.
<div class="row-fluid">
<div class="span12">
<?php echo $this->Session->flash() ?>
<h4><?php echo __('All Books') ?></h4>
<hr>
<table class="table table-bordered">
<thead>
<tr> <th><?php echo __('Serial No') ?></th>
<th><?php echo __('Title') ?></th>
<th><?php echo __('Author') ?></th>
<th><?php echo __('Publisher') ?></th>
<th><?php echo __('Category') ?></th>
<th><?php echo __('Section') ?></th>
<th><?php echo __('Available') ?></th>
</tr>
</thead>
<tbody>
<?php foreach( $All_Books as $book ){ ?>
<tr>
<td><?php echo $this->Html->link(__($book['Book']['Serial_No']),'/books/view/'.$book['Book']['Serial_No']) ?></td>
<td><?php echo $book['Book']['Title'] ?></td>
<td><?php echo $book['Book']['Author'] ?></td>
<td><?php echo $book['Book']['Publisher'] ?></td>
<td><?php echo $book['Book']['Category'] ?></td>
<td><?php echo $book['Book']['Section'] ?></td>
<td><?php echo $book['Book']['Available'] ?></td>
</tr>
<?php } ?>
</tbody>
</table>
<?php echo $this->Paginator->prev('« Previous', null, null, array('class' => 'disabled'));
echo $this->Paginator->numbers();
echo $this->Paginator->next('Next »', null, null, array('class' => 'disabled'));
echo $this->Paginator->counter(array(
'format' => 'Page {:page} of {:pages}, showing {:current} records out of
{:count} total, starting on record {:start}, ending on {:end}'
));
?>
</div>
</div>
What can i add on my view or controller or model, to make the view auto updating? Can this be achieved using ajax?
回答1:
You can use an AJAX poller, or (HTML5) websockets (using Pusher for instance) for push notification.
回答2:
You've already mentioned it, AJAX. That's the easiest way to accomplish something like that, simply do a check in the background via AJAX, and if necessary reload the page, or update only the affected parts by again using an AJAX request.
Depending on the amount of data you could of course simply load the data directly instead of checking for updates first.
Update I've misunderstood the question, in case the inserts/udpates are made from an external source that you have no direct control over as described in the comments, the only options I could think of for checking whether updating the view is necessary, would be checking the UPDATE_TIME
information schema (works on MyISAM only), using triggers for updating a custom information schema that could be checked, or counting the rows, however the latter would only cover inserts.
All methods would fetch the comparison value (update time or row count) in the controller action of the specific view, and pass that value to the view where it's then used in the AJAX call. The AJAX call invokes a controller method where the passed value is compared to the current time/count value in order to determine whether an update is necessary.
Please note that the examples are untested!
Information Schema
The easiest method would be checking the UPDATE_TIME
information schema, however as mentioned this only works for MyISAM tables:
Model:
public function getUpdateTime()
{
$db = $this->getDataSource();
$result = $db->fetchAll('
SELECT
UNIX_TIMESTAMP(UPDATE_TIME) AS update_time
FROM
information_schema.tables
WHERE
TABLE_SCHEMA = ?
AND
TABLE_NAME = ?',
array
(
$db->config['database'],
$this->table
)
);
if(empty($result) || !isset($result[0][0]['update_time']))
{
return false;
}
return (int)$result[0][0]['update_time'];
}
Controller:
public function list_books()
{
$this->set('lastUpdate', $this->Books->getUpdateTime());
$this->Books->recursive = 0;
$this->paginate = array('order' => array('Serial_No' => 'DESC'));
$this->set('All_Books', $this->paginate());
}
public function checkForUpdate($time)
{
$updateNecessary = $this->Books->getUpdateTime() > (int)$time;
return new CakeResponse(array('body' => json_encode($updateNecessary)));
}
View:
<script>
jQuery(function($)
{
var lastUpdate = <?php echo $lastUpdate; ?>;
function checkForUpdate()
{
$.get('/books/checkForUpdate', {time: lastUpdate}, function(updateNecessary)
{
if(updateNecessary === true)
{
alert('update necessary');
// now reload the page or use an additional AJAX request for updating the content
}
else
{
queueUpdateCheck();
}
},
'json');
}
function queueUpdateCheck()
{
setTimeout(checkForUpdate, 5000);
}
queueUpdateCheck();
});
</script>
Using Triggers
Another option would be using triggers. You'd need an additional table that connects tables and time values using for example the table name, and two triggers, one for inserts, one for updates. These triggers could then update the custom information table.
Information Table
CREATE TABLE IF NOT EXISTS `table_stats` (
`table_name` varchar(255) NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`table_name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `table_stats` (`table_name`, `update_time`)
VALUES ('books', NOW());
Triggers
CREATE TRIGGER `update_time_after_insert` AFTER INSERT ON `books`
FOR EACH ROW
UPDATE `table_stats` SET `update_time` = NOW() WHERE `table_name` = 'books';
CREATE TRIGGER `update_time_after_update` AFTER UPDATE ON `books`
FOR EACH ROW
UPDATE `table_stats` SET `update_time` = NOW() WHERE `table_name` = 'books';
Model:
public function getUpdateTime()
{
$db = $this->getDataSource();
$result = $db->fetchAll('
SELECT
UNIX_TIMESTAMP(update_time) AS update_time
FROM
`table_stats`
WHERE
`table_name` = ?',
array
(
$this->table
)
);
if(empty($result) || !isset($result[0][0]['update_time']))
{
return false;
}
return (int)$result[0][0]['update_time'];
}
Controller and View would be the same as in the previous example.
Counting rows
Now the last option would be comparing the row count, which would of course only work for inserts. In this example the Model would stay untouched.
Controller:
public function list_books()
{
$this->set('rowCount', $this->Books->find('count'));
$this->Books->recursive = 0;
$this->paginate = array('order' => array('Serial_No' => 'DESC'));
$this->set('All_Books', $this->paginate());
}
public function checkForUpdate($rowCount)
{
$updateNecessary = $this->Books->find('count') != (int)$rowCount;
return new CakeResponse(array('body' => json_encode($updateNecessary)));
}
View:
<script>
jQuery(function($)
{
var rowCount = <?php echo $rowCount; ?>;
function checkForUpdate()
{
$.get('/books/checkForUpdate', {rowCount: rowCount}, function(updateNecessary)
{
if(updateNecessary === true)
{
alert('update necessary');
// now reload the page or use an additional AJAX request for updating the content
}
else
{
queueUpdateCheck();
}
},
'json');
}
function queueUpdateCheck()
{
setTimeout(checkForUpdate, 5000);
}
queueUpdateCheck();
});
</script>
Retrieving data together with the update check
Of course you could also submit possible data together with the update check, in order to avoid additional requests. For example:
Model
public function checkForUpdate($time)
{
$data = '';
$updateAvailable = $this->Books->getUpdateTime() > (int)$time;
if($updateAvailable)
{
$this->set('books', $this->Books->find('all'));
// render /Elements/books.ctp in ajax.ctp layout and grab the rendered content
$this->viewPath = 'Elements';
$view = $this->render('books', 'ajax');
$data = $view->body();
}
$body = compact('updateAvailable', 'data');
return new CakeResponse(array('body' => json_encode($body)));
}
View
<script>
jQuery(function($)
{
var lastUpdate = <?php echo $lastUpdate; ?>;
function checkForUpdate()
{
$.get('/books/checkForUpdate', {time: lastUpdate}, function(response)
{
if(response.updateAvailable === true)
{
// the data is already available, so directly update the content
$('#content').html(response.data);
}
queueUpdateCheck();
},
'json');
}
function queueUpdateCheck()
{
setTimeout(checkForUpdate, 5000);
}
queueUpdateCheck();
});
</script>
来源:https://stackoverflow.com/questions/17906697/update-cakephp-view-each-time-a-record-is-inserted-in-the-db-table