I got a project require angularjs datatable (i-lin) to populate large amount of data. Therefore, i decided to use server side processing and Ajax call to get my PHP data populate into the angularjs datatable. Currently, i could show the static columns and data perfectly however.......
Problem
The Ajax(api call) needs sometime to request the data therefore now my page will show the static data columns first. A few seconds later, page will refresh again then append the new columns.
Which means, user will see the static datatable columns first, After 1 second will see another datatable. It looks very weird for user.
Target
Instead of show the static columns, I would like ensure all data has been returned from Ajax then only show the complete table.
Code
html
<table id="saleReport"
class="report-table table-striped table table-bordered
table-condensed table-hover data-table"
datatable="" dt-options="dtOptions" dt-columns="dtColumns"
dt-instance="dtInstance">
Angularjs
The Module.fetchSalesData are API call from the PHP
$scope.dtOptions = DTOptionsBuilder
.newOptions()
.withOption('ajax', function (data, callback, settings) {
// set dataSrc
settings.sAjaxDataProp = 'trans';
Module.fetchSalesData({
draw: data.draw,
length: data.length,
start: data.start,
search : data.search.value,
count_again : $scope.countAgain,
startdate: $filter('date')($scope.item.startdate, 'yyyy-MM-dd'),
enddate : $filter('date')($scope.item.enddate, 'yyyy-MM-dd'),
payee_id: $scope.item.payee_id,
staff_id: $scope.item.staff_id,
order: data.order
}).then(function(res) {
$scope.payment_methods = res.data;
callback(res);
$scope.countAgain = false;
})
})
.withOption('serverSide', true)
//This is static dtColumns, User will see this first
$scope.dtColumns = [
DTColumnBuilder.newColumn('invoice_date')
.withTitle('Date')
.withOption('sWidth', '10%'),
DTColumnBuilder.newColumn('ref_num').withTitle('Description')
];
//Set timout waiting server returned **$scope.payment_methods**
//in order to generate dtcolumns dynamically
setTimeout(function() {
angular.forEach($scope.payment_methods, function (value) {
$scope.dtColumns.push(
DTColumnBuilder.newColumn(value).withTitle(value)
.notSortable().withClass('text-right')
.withOption('stateSave', true)
.renderWith(function (data, type, full) {
data = typeof data === 'undefined' ? '0.00' : data;
return $filter('currency')(data, '');
}),
)
});
}, 1000);
Tried
What i have try is using destroy the first table and keep the latest table but it failed
var table = $('#saleReport').DataTable();
table.clear().destroy();
Appreciated of you guys could give me the ideas.
Related
I have a forum project with Laravel 9, and I have made this helper function.
if(!function_exists('new_question')){
function new_question($c) {
$quelist = \DB::table('questions')->get();
$quecount = $quelist->count();
if($quecount > $c){
return 'A new question is added.. please refresh the page..';
}
}
}
So it gets the number of current questions like this:
{{ new_question($queCnt); }}
And then, it will check if the $quecount equals $queCnt or not. And if not, then print the statement A new question is added.. please refresh the page... Therefore the user will understand if any new question is added. But I need to run this helper function after some periods of time (for example, 10 seconds). However, I don't know how to call a function after a custom amount of time.
to run any function after a specific time, you have set the interval for example
// Call the new_question function every 10 seconds
setInterval(new_question, 10000);
// Use an AJAX request to call the new_question function on the
// server
function new_question(){
$.ajax({
url: '{{ url('/new_question') }}?c=10',
success: function(response) {
// Handle the response from the server
console.log(response);
}
});
}
</script>
// to receive get value update helper function
if(!function_exists('new_question')){
function new_question() {
// Get the value of the c parameter from the query string
$c = isset($_GET['c']) ? $_GET['c'] : 0;
// Your code here...
}
}
First, you have to figure out if the number of "content" has changed. Using Laravel, create a function that is accessible through a route, this function would return the number of posts, then, using javascript, you will call that function in an interval (example is 5 seconds) and if the number has changed since the last call, then there's new posts, so you should do some DOM manipulation to update your page to alert the user.
Your server side function would be simple, something like this:
function count_questions() {
$quelist = DB::table('questions')->get();
$quecount = $quelist->count();
$response = array('quecount' => $quecount);
echo json_encode($response);
}
Then, identify how to reach this function through your routing table, and use the below jquery functions:
var quecount = 0;
$(document).ready(function() {
setInterval(function() {
$.ajax({
// change this URL to your path to the laravel function
url: 'questions/count',
type: 'GET',
dataType: 'json',
success: function(response) {
// if queuecount is 0, then set its initial value to quecount
if(quecount == 0){
quecount = response.quecount;
}
if(response.quecount > quecount){
quecount = response.quecount;
new_question_found();
}
}
});
}, 5000);
});
function new_question_found(){
$("#new_questions").html("New questions found");
}
The solution that is coming to my mind is may be too advance or too complex.
This solution need
Laravel scheduler and queue (Jobs)
and web push notification (ex : one-signal)
To reduce the traffic in the back-end you can have job to run like every 10 seconds in the back-end (Laravel scheduler and Queue).
If the question count get increased. you can call a api in the push notification and you can say there is a new question added.
the above work-flow is not explained well but this is in very simple term.
For example:
on frontend side:
const check_new_questions = function() {
const message =
fetch('http://yourserver.com/new_questions_endpoint');
if (message) {
// show message
}
};
// call each 10 seconds.
setInterval(check_new_questions, 10000);
then on backend side:
create a route new_questions_endpoint which will call your function and return result as response.
But note, that receiving all the questions from the table each time could be expensive. Eloquent enables to make a count query without retrieving all the rows.
You can't have this behaviour happen without any form of javascript.
The main way you could do this is by setting an interval via front-end like others have said. If you have any familiarity with APIs and general HTTP protocol, I would recommend you make an API route that calls your helper function; I also recommend responding with an empty body, and using the http status code to determine whether a refresh is needed: 200 for success and no refresh, 205 for success and refresh needed.
So you simply set a fetch api call on timeout, don't even need to decode the body and just use the response status to determine whether you need to run location.reload().
To achieve your requirement as per the comment you need to create an ajax request to BE from FE to check the latest question and based on that response you need to do it.
setInterval(function()
{
$.ajax({
url: "{{url('/')}}/check/questions",
type: "POST",
dataType: "json",
data: {"action": "loadlatest", "id": id},
success: function(data){
console.log(data);
},
error: function(error){
console.log("Error:");
console.log(error);
}
});
}, 10000);//time in milliseconds
the above js code will create an ajax request to Backend, below code will get the latest question 'id'.
$latest_id = DB::table('Questions')->select('id')->order_by('created_at', 'desc')->first();
so either check it in BE and return a corresponding response to FE or return the latest id to FE and check it there.
then show the prompt to refresh or show a toast and refresh after 5 sec
Why do you use function_exists() ? I don't think it's useful in this case.
The easiest way to do what you want is to use ajax and setInterval
Front side:
const check_new_questions = function() {
const data =
fetch('http://yourserver.com/new_questions_endpoint?c=current');
if (data) {
alert(your_message);
}
};
// call each 10 seconds.
setInterval(check_new_questions, 10000);
Back side:
function new_question() {
// Get the value of the c parameter from the query string
$c = isset($_GET['c']) ? $_GET['c'] : 0;
$quelist = \DB::table('questions')->get();
$quecount = $quelist->count();
return ($quecount > $c);
}
I suggest to use c as the last id and to not count questions but just to get the last question id. If they are different, one question or more was inserted.
Attention if you use this solution ( Ajax pulling ) you'll get two requests per 10 seconds per connected users. One for the ajax call and one for for the database call. If you have 10 users a day, it's ok but not if you have 10 thousands. A better but more complex approach is to use Mercure protocol or similar ( https://mercure.rocks/ ).
I am using jquery plugin DataTables for building nice table
var table = $('#example').DataTable({
"data": source
});
I would like that make an each for all rows in table
Unfortunately this way may be out of date and does't work with new version (it launchs an error)
$(table.fnGetNodes()).each(function () {
});
And this way only works only for visibles rows (10 first rows because other rows are paginated)
table.each( function ( value, index ) {
console.log( 'Data in index: '+index+' is: '+value );
} );
Do you known how to loop to all rows please?
I finally found:
var data = table.rows().data();
data.each(function (value, index) {
console.log(`For index ${index}, data value is ${value}`);
});
Datatables have an iterator for each row rows().every() with this referring to the context of the current row being iterated.
tableName.rows().every(function(){
console.log(this.data());
});
If you are using the legacy DataTables then you can get all the rows even the paginated ones, as shown below...
table.fnGetNodes(); // table is the datatables object.
So we can loop through the rows by using .each() method provided by jQuery.
jQuery(table.fnGetNodes()).each(function () {
// You can use `jQuery(this).` to access each row, and process it further.
});
for example, this data has three fields UserID, UserName and isActive and we want to show only active users
The following code will return all the rows.
var data = $('#myDataTable').DataTable().rows().data();
We will print only active users
data.each(function (value, index) {
if (value.isActive)
{
console.log(value.UserID);
console.log(value.UserName);
}
});
I'm a noobie of PHP and AngularJS.
I have a webpage that communicates to a web serves with PHP - AJAX. It queries a database, and echoes the result (a big table) in an html placeholder.
I want to print the content of that table in a downloadable PDF file when the user pushes a button.
I want to use PDFmake and now it works well for test purpose, but how can I pass that content of my table to AngularJS' app?
Maybe should I pass table's id to docDefinition content? In that case I don't know how to do that.
Note: Maybe my approach is uncorrent cause I have to relegate PHP to different tasks and use AngularJS to query the Database, but for now I want to mantain this approach.
Thank You
I suggest you use an angular service (as explained in the docs
)
var bigTableApp = angular.module('bigTable',[])
bigTableApp.factory('BigTableSrv', ['$resource',
function($resource) {
return $resource('URL_to_php_backend', {}, {
query: {
method: 'GET',
params: {param1: 'value 1', param2: 'value 2'},
isArray: true
}
});
}
]);
Then, you can use it in a controller to fetch data from the back-end and build a table structure in PDFmake's table format:
bigTableApp.controller('BigTableController', ['$scope', 'BigTableSrv',
function BigTableController($scope, BigTableSrv) {
$scope.bigTable = BigTableSrv.query();
$scope.pdfMakeTable = {
// array of column widths, expand as needed
widths: [10, *, 130],
body: []
};
$scope.printTable = function() {
pdfMakeTable.body = $scope.bigTable.map(el => {
// process each element of your "big table" to one line of the
// pdfMake table, size of return array must match that of the widths array
return [el.prop1, el.prop2, el.prop3]
});
// create the pdfMake document
let docDefinition = {
content: [ pdfMakeTable ]
}
// print your pdf
pdfMake.creatPdf(docDefinition).print()
}
}
]);
So currently I'm searching for 10 new posts and the Ajax searches in the same page from the page and I use $_GET['limits'] in my PHP query to scan the server for all requested data.
So what I'd like to do is if there is no new data to show the 'No More Posts' Div. I tried using t.length===0 with no luck, now I don't know if its because t isn't an array or whether I put it in the wrong place in my success.
var streams_stream_count=10;
function streams_stream_load(targetID){
$('#loadmorestreamoneajaxloadertarget').show();
$.ajax({
method: 'get',
url : 'stream2.php?limits='+streams_stream_count+'&targetID='+targetID,
dataType : 'text',
success: function (t) {
$('#streams_stream_container').fadeIn('slow').html(t);
$(document).scroll(function(){
if ($(window).scrollTop() + $(window).height() >= $(document).height()) {
streams_stream_count+=10;streams_stream_load(targetID);
}
});
},
complete: function(){
$('#loadmorestreamoneajaxloadertarget').hide();
}
});
}
And my hidden div to show if no new data.
<div id='nomoreposts' style='display:none;'>No more Posts</div>
UPDATE
I use $sqlLimit=mysqli_real_escape_string($mysqli,$_GET['limit'])
$call="SELECT * FROM streamdata ORDER BY streamitem_timestamp DESC LIMIT $sqlLimit";
You can also do like this :- Write code in backend (PHP function) and return html if no data found "No more Posts", also send one parameter true/false. On the bases of the parameter show html in your fronted.
I'm looking to find out how I can accomplish this next task. I have a controller that loads a view with a table that lists pages in my database. In every row that is made in the table there is a spot for a icon that when clicked will do one of two things.
If the user does not have javascript enabled:
Clicking on the icon will redirect to the delete function in the controller with id of the page as a paramter
Delete controller function will run delete function in model sending page id to delete model function
Delete function in model will delete the page from the database and when returned back to page controller delete function it will redirect back to index function to show list of pages table again.
After redirect it will display a title and message as to a success/fail.
If the user does have javascript enabled:
Send a post to the delete function in controller with jquery post
method with the data page id
Delete controller function will run delete function in model sending page id to delete model function
Delete function in model will delete the page from the database and when returned back to page controller delete function it create a message array for the json object to return to the success function of the post request.
A message with my pnotify plugin will create a message that is formed from that json object and display it to the user
What I would like to know is with doing this how to properly accommodate for these two scenarios? I have started attempting this but would like to some clarification if I have made a mistake so far.
<?php
// Controller delete function
public function delete($content_page_id)
{
if (isset($content_page_id) && is_numeric($content_page_id))
{
$content_page_data = $this->content_page->get($content_page_id);
if (!empty($content_page_data))
{
//update is ran instead of delete to accompodate
//for the soft delete functionality
$this->content_page->update('status_id', 3);
if ($this->input->is_ajax_request())
{
//return json data array
}
}
}
}
?>
Global JS file to be used for multiple tables with delete buttons
/* Delete Item */
$('.delete').click(function(event) {
event.preventDefault();
var item_id = $(this).attr('rel');
$.post(<?php echo current_url(); ?>'delete', { item_id : item_id }, function(data)
{
if (data.success)
{
var anSelected = fnGetSelected( oTable );
oTable.fnDeleteRow( anSelected[0] );
}
}, 'json');
});
I think that you should have two functions in PHP:
public function delete($content_page_id) {
// Your delete code
return "a string without format";
}
public function deleteAjax($content_page_id) {
return json_encode($this->delete($content_page_id));
}
So, when the user has JS enabled, you call deleteAjax passing a parameter in your $.post function to let PHP know that JS is enabled:
$.post(<?php echo current_url(); ?>'delete', { item_id : item_id, js: 1 }, function(data)
{
if (data.success)
{
var anSelected = fnGetSelected( oTable );
oTable.fnDeleteRow( anSelected[0] );
}
}, 'json');
And if JS is disabled, call the other function. You should use an AJAX specialized controller instead a function in the same class.
1) As far as "displaying a message" - the view-itself could be ready for a 'message' if one exists. Bringing us back to...
2) Can you have your delete function return the message you want displayed? Your AJAX approach will ignore this message while your View will display it...
3) I agree that your 'Controller delete function' should 'finish' with different outcomes based on whether the request is AJAX or not. I like what #Skaparate (answered Aug 30 at 18:37) was doing with adding: js:1 In your delete function, you could use this in a simple conditional:
if js = 1
header('HTTP/1.1 200');
else
call view and include/pass-in the 'message'