I have some tables that start empty and DataTables requests WebServer for the data.
It's ok to take some minutes to load it. DataTables shows default Loading message. But I'd like to add a counter informing how long the loading is running, instead of a simple Loading text or some wacky animation.
I can't find a way to do it on its documentation. Is it possible?
Update: MonkeyZeus's answer worked perfectly. Here's my final code:
// ...
,dataTablesLoading: function(e, settings, processing ){
setTimeout(function(){
var targetJs = e.target;
var target = $(targetJs);
var timerContainer = target.find('.dataTables_empty');
//tlog(targetJs,'targetJs');
//tlog(target,'target');
//tlog(timerContainer,'timerContainer');
if(processing){
var timer = 0;
var timerHandler = setInterval(function(){
timer++;
var hours = Math.floor(timer/3600);
var minutes = Math.floor((timer-(hours*60))/60);
var secs = timer-(hours*3600)-(minutes*60);
var timerText = hours+':'+minutes.lpad("0",2)+':'+secs.lpad("0",2);
tlog(timerText,'timerText');
//tlog(timerContainer,'timerContainer');
timerContainer.text("Loading... "+timerText);
},1000);
targetJs.setAttribute("data-loading-timer",timerHandler);
tlog(timerHandler,'timerHandler processing');
}else{
var timerHandler = parseInt(targetJs.getAttribute("data-loading-timer"));
tlog(timerHandler,'timerHandler not processing');
if(timerHandler>0)
clearInterval(timerHandler);
}
},1000);
}
// ...
$('#...')
.on( 'processing.dt', Framework.utils.dataTablesLoading )
.DataTable({...})
First, you will need to enable processing when invoking the datatable:
$('#example').dataTable( {
"processing": true
} );
Next, you will need to declare what happens instead of the default Loading message using the dt namespace's processing event listener:
// This event will fire twice so pay attention to the processing parameter
$('#example').on( 'processing.dt', function ( e, settings, processing ) {
if( processing === true ) {
alert('Hey, we are processing!');
// some custom code which targets #processingIndicator and applies some timer plug-in or whatever; you figure it out.
}
else {
alert('Hey, we are done processing!');
// some custom code which targets #processingIndicator and hides it; you figure it out.
}
} )
.dataTable();
Additionally, long load times have a UX aspect to consider as well so definitely check out https://ux.stackexchange.com/a/80858/45170 if you want to make a nicer experience.
Related
i have table with search fields seprated to
1- table header contain "search fields - table headers"
2- table body => come from ajax post method
3- pagination => come from ajax post method after data excuted "i used setTimeout to delay this function"
the data display automaticly in the table body by ajax post & if the user use search fields also the data filterd by search words and pagingation also display basd on data
i have 2 cases
1- in the main page the table loaded with the page and evry thing working fine i used the follwing function:
follwing function responsible for data
jsQueryDataLive(defaultPageNo);
function jsQueryDataLive(defaultPageNo){
var objData = {
action : dataTableName +'/QueryData',
ajaxClass : actionSplit0,
ajaxPage : defaultPageNo,
};
$('#form-query-main').find(":input").each(function(){
objData[this.name] = $(this).val();
});
// console.log(objData);
// NOTE: the below line is Responsible for determining the location we need to view data on
var locationID = '#main-table-tbody';
ajaxPost(objData, locationID);
}
the following function for pagination
jsPaginationLive(defaultPageNo);
function jsPaginationLive(defaultPageNo){
setTimeout(function () {
var objData = {
action : dataTableName +'/Pagination',
ajaxClass : actionSplit0,
ajaxPage : defaultPageNo,
};
// document.getElementById("jsData").innerHTML =JSON.stringify(objData);
// NOTE: the below line is Responsible for determining the location we need to view data on
var locationID = '#pagination';
ajaxPost(objData, locationID);
}, 100);
}
i used the following function for filter
$('.searchField').keyup(function() {
jsPageNo(defaultPageNo);
} );
function jsPageNo(defaultPageNo) {
jsQueryDataLive(defaultPageNo);
jsPaginationLive(defaultPageNo);
}
2- the second case is when i want to display the obove bage in the modal
as you see in the page the table and data display correctly
i used this function to define table and load the main bag :
var dataTableName;
jsDataTableName();
function jsDataTableName(tableName) {
if (tableName === undefined) {
dataTableName = actionSplit0;
// console.log(dataTableName);
}
else {
dataTableName = tableName;
console.log(dataTableName);
// NOTE: load External Class Query page
jsLoadExternalClass();
}
}
function jsLoadExternalClass(){
var objData = {
action : dataTableName +'/Query',
ajaxClass : actionSplit0,
ajaxPage : defaultPageNo,
};
// document.getElementById("jsData").innerHTML =JSON.stringify(objData);
// jsPaginationLive(defaultPageNo);
// NOTE: the below line is Responsible for determining the location we need to view data on
var locationID = '#externalClass1';
ajaxPost(objData, locationID);
jsPageNo(defaultPageNo);
}
the problem with me in filtered not working when i type imediatly but it work after i close the modal and re open it again
enter image description here
also if there is a better solution to start function jsPaginationLive after jsQueryDataLive finished instead of setTimeout please let me know
If you set the timer on setTimeout from 100 ms to 2,000, does it still have the issue? One of the problems I've run into when using timeouts to handle situations like these is that any variance in the loading times of the site (which can always vary for any number of reasons) can cause your timeout to fail to achieve your goal.
So instead I'd suggest this: either add a variable which is set by the data function upon completion or you could use a value in the data function which must not be undefined before proceeding, then have the pagination function in a setInterval, like this:
var interval;
interval = setInterval(function(){
if (dataHasLoaded){ // This will be whatever the you decide to name the variable
jsPaginationLive(defaultPageNo);
clearInterval(interval);
}
},500);
This will ensure that the pagination function will never attempt to load before the data has been fully loaded.
EDIT: Alternatively, you can place "jsPaginationLive(defaultPageNo);" somewhere at the end of "jsQueryDataLive(defaultPageNo);" so it won't attempt pagination until the data function has completed.
Answer to 2nd question:
If you want to only run an interval while a user is typing, you can do something like this:
var interval;
window.onkeydown = function(){
clearInterval(interval);
interval = setInterval(function(){
if (dataHasLoaded){
jsPaginationLive(defaultPageNo);
clearInterval(interval);
}
},500);
}
var keyTimeout;
window.onkeyup = function(){
clearTimeout(keyTimeout);
keyTimeout = setTimeout(function(){
clearInterval(interval);
}, 5000); // The interval will be cleared 5 seconds after the last keypress. Change as needed.
}
I feel like there's a cleaner way to do this, though. Why do you want the interval to only be active during typing?
I'm trying to solve a quite simple task but stuck with JQuery behavior.
I have a HTML button which I disable (add disabled attribute) right after it get clicked to prevent multiple clicks, do something long running (i.e. update DOM with a lot of elements) and enable the button back.
Problem is that even the button is disabled jquery queues all clicks on it and raise my click handler right after it became enabled.
According to JQuery docs it should not raise events for a disabled element.
Bellow is my code. Open JS console, click two times on the button, notice couple 'START --' messages in the console.
<div>
<button id="mybtn" type="button">Find</button>
</div>
var btnElement = $('#mybtn');
btnElement.click(doClick);
function doClick() {
var btnElement = $('#mybtn');
btnElement.attr('disabled', true);
console.log('START --' + Date());
for (var i = 0; i < 70000; i++) {
var el = $('#mybtn');
var w = el.width();
w += 10;
}
console.log('STOP --' + Date());
el.attr('disabled', false);
}
Here is my solution http://jsfiddle.net/DRyxd/8/
var btnElement = $('#mybtn');
var buttonIsBusy = false;
function doHeavyJob () {
console.log('START --' + Date());
for (var i = 0; i < 70000; i++) {
var el = $('#mybtn');
var w = el.width();
w += 10;
}
var timeoutId = setTimeout (unblockTheButton, 0);
console.log('STOP --' + Date());
}
function unblockTheButton () {
console.log('unblockTheButton');
btnElement.attr('disabled', false);
buttonIsBusy = false;
}
function doClick() {
console.log('click', buttonIsBusy);
if (buttonIsBusy) {
return;
}
btnElement.attr('disabled', true);
buttonIsBusy = true;
var timeoutId = setTimeout (doHeavyJob, 0);
}
btnElement.click(doClick);
The issue here is that click-handler function has not finished and browser has not refreshed the DOM. That means that block was not yet applied to the button. You can try pushing your heavy code out of the current context like this:
function someHeavyCode () {
/* do some magic */
}
var timeoutId = setTimeout(someHeavyCode, 0);
This will push your heavy code out of the current context.Letting browser to update the DOM first and only after execute the heavy code.
While the heavy code is executed, browser (at least Chrome) kept the user input queue somewhere in other place (or most-likely other thread). And as soon as heavy code completes - it feeds the DOM with all that queued events. We need to ignore that events somehow. And I use the setTimeout with 0-time again. Letting the browser do what was queued before unblocking the button.
WARNING But be extremely careful with this technique. Browser will still be blocked and if you spawn a lot of such blocks it may hang.
See also this Why is setTimeout(fn, 0) sometimes useful? and consider using webworkers.
P.S. Blocking a user input in such a way is not a good approach, try to rethink what you are going to do, probably there is a better solution for that.
I am building a django site and have implemented the redips.drag library in one of my pages to allow dragging of table rows. I want a very simple functionality in my code- add a listener, so when the row is dropped, it send the row data to the server. jQuery-speaking, something like this:
$(function() {
$(someDomElement).on('DropEvent', function() {
// send data to server
};
});
The problem though, is that redips.drag is not a jQuery plugin but a javascript one, so my knowledge is a little (more than a little) lacking. I can probably find some other library, but it's performing really well and I prefer understanding how to work with it than look for a different one.
I can probably handle the "sending the data to the server" part by myself, what I can't understand at all is how to "catch" the drop event, what part of the dom do I listen to? I tried adding monitorEvents to different selectors but failed completely.
I also tried to manipulate the script.js file (the one that initializes the row handling), but also failed. here's the one I'm using (example 20 in the redips package):
"use strict";
// define redips object container
var redips = {};
redips.init = function () {
// reference to the REDIPS.drag library and message line
var rd = REDIPS.drag,
msg = document.getElementById('msg');
// initialization
rd.init();
//
// ... more irrelevent code ...
//
// row event handlers
//
// row clicked (display message and set hover color for "row" mode)
rd.event.rowClicked = function () {
msg.innerHTML = 'Clicked';
};
// row row_dropped
rd.event.rowDropped = function () {
msg.innerHTML = 'Dropped';
};
// and so on...
};
// function sets drop_option parameter defined at the top
redips.setRowMode = function (radioButton) {
REDIPS.drag.rowDropMode = radioButton.value;
};
// add onload event listener
if (window.addEventListener) {
window.addEventListener('load', redips.init, false);
}
else if (window.attachEvent) {
window.attachEvent('onload', redips.init);
}
Now I tried adding a console.log('hello') to the rd.event.rowDropped function (right above the msg.innerHTML line), but that doesn't work, I drop the row and nothing shows in the log. Doing a console.log outside the init function works so I know the script can pass stuff to the console.
Please, can anyone help me? I'm at a complete loss...
I know this may be a little lateto answer your question but I found the answer. You need to use the event dropped and the attribute rd.obj (REDIPS.drag.obj) to get the id use it with simple javascript like getAttribute('id')
redips.init = function () {
// reference to the REDIPS.drag library and message line
var rd = REDIPS.drag,
msg = document.getElementById('msg');
// initialization
rd.init();
// row clicked (display message and set hover color for "row" mode)
rd.event.clicked = function () {
msg.innerHTML = 'Clicked' + rd.obj.getAttribute('id');
};
// row row_dropped
rd.event.dropped = function () {
msg.innerHTML = 'Dropped' + rd.obj.getAttribute('id');
};
};
I have a jQuery script that searches in the DOM and shows the results in a list.
There is a simplified version of the script here: http://jsfiddle.net/FuJta/1/
There is usually a large number of results, so the script can take a while to execute. (In the example above, this is simulated with a function that delays the script). So if you type too fast in the searchbox, the script prevents you from typing, and it feels bad.
How could I change my script so that you can type freely, and the results show up when they are ready. I want something like the facebook search : if you type too fast, the results are just delayed, but you can still type.
Html
<p>Type in foo, bar or baz for searching. It works, but it is quite slow.</p><br/>
<input type="text" id="search"/>
<div id="container" style="display:none">
<div class="element">foo</div>
<div class="element">bar</div>
<div class="element">baz</div>
</div>
<div id="results">
</div>
Javascript
$(function() {
function refreshResults() {
var search = $('#search').val();
var $filtered = $('#container .element').clone().filter(function() {
var info = $(this).text();
return info.toLowerCase().indexOf(search) >= 0;
});
$('#results').empty();
$filtered.each(function() {
$('#results').append($(this));
});
}
// simulating script delay
function pausecomp(millis) {
var date = new Date();
var curDate = null;
do {
curDate = new Date();
}
while (curDate - date < millis);
}
$('#search').keyup(function() {
pausecomp(700);
refreshResults();
});
});
One solution could to refresh the results only when pressing enter. This way, the delay for searching the results feels ok. But I would prefer if I just delay the results and let the user freely type.
You should perform a search like this using asynchronous techniques. No doubt Facebook uses some sort of AJAX to request search results - which means getting the results from the server. This will help prevent the UI 'freeze' that you are currently experiencing.
Here is a very simple example of what you can try (it uses JQuery for the AJAX requests):
var searchInProgress = false;//used to work out if a search is in progress
var searchInQueue = false;//used to flag if the input data has changed
function getSearchResults(searchText){
if (searchInProgress ) {
searchInQueue = true;
return;
}
searchInProgress = true;
searchInQueue = false;
$.getJSON("URL",//URL to handle AJAX query
{ searchText: searchText},//URL parameters can go here
function (data) {
//handle your returned data here
searchInProgress = false;
if (searchInQueue){//text has changed, so search again
getSearchResults();
}
});
}
$('#search').keyup(function() {
getSearchResults($(this).val());
});
A few things to note: It is probably a good idea to handle failed AJAX requests to ensure you can reset the searchInProgress flag as needed. Also, you can add delays after the keyup as desired, but this all depends on how you want it too work.
From How to delay KeyPress function when user is typing, so it doesn't fire a request for each keystroke? :
var timeoutId = 0;
$('#search').keyup(function () {
clearTimeout(timeoutId); // doesn't matter if it's 0
timeoutId = setTimeout(refreshResults, 100);
});
It does what I want indeed.
Here's a solution that divides the search process into steps, returning flow to the browser during the process to allow the UI to respond.
$(function() {
function searchFunc($element,search) {
var info = $element.text();
return info.toLowerCase().indexOf(search) >= 0;
}
var searchProcessor = null;
function restartSearch() {
console.log('Restarting...');
// Clear previous
if (searchProcessor != null) {
clearInterval(searchProcessor);
}
$('#results').empty();
// Values for the processor
var search = $('#search').val();
var elements = $('#container .element').get();
console.log('l:',elements,elements.length);
// Start processing
searchProcessor = setInterval(function() {
if (elements.length == 0) {
// Finished searching all elements
clearInterval(searchProcessor);
searchProcessor = null;
console.log('Finished.');
} else {
console.log('Checking element...');
var $checkElement = $(elements.shift());
if (searchFunc($checkElement, search)) {
$('#results').append($checkElement.clone());
}
}
}, 10);
}
$('#search').keyup(function() {
restartSearch()
});
});
It only processes one element each time. That should probably be increased so it handles perhaps 10 or 100 each time around, but the important point is that the work is divided into chunks.
This solution should also be faster than the original because it doesn't clone() everything, only the elements that were matched.
I've got a search input which sends data from an input to a php file as I type. The php file does a search on my database and shows up a list of search options. You know, the ajax style live searching.
My problem is, if you type something really fast, it might just conduct a search off of the first 1 or 2 letters even though another 10 have been typed. This causes a few problems.
My jQuery looks a bit like this:
$(document).ready(function(){
$('#searchMe').keyup(function(){
lookup(this.value);
});
});
and
function lookup(searchinput) {
if(searchinput.length == 0) {
// Hide the suggestion box.
$("#suggestions").hide();
} else {
$('#loading').fadeIn();
$.post("/RPCsearch.php", {queryString: ""+searchinput+""}, function(data){
if(data.length > 0) {
$("#suggestions").html(data).show();
$('#loading').fadeOut();
}
});
}
} // lookup
So I'm just curious, how can I make it so that my script waits until I've finished typing before running the function? My logic says something like if a key hasn't been pressed for 200 micro seconds, run the function, otherwise hold up a bit.
How is this done?
Easy, using setTimeout. Of course you only want one timer going at once, so it's important to use clearTimeout at the beginning of the function...
$(function() {
var timer;
$("#searchMe").keyup(function() {
clearTimeout(timer);
var ms = 200; // milliseconds
var val = this.value;
timer = setTimeout(function() {
lookup(val);
}, ms);
});
});
You may be interested in my bindDelayed jQuery mini-plugin. It:
Allows you to specify a delay before kicking off the request
Automatically cancels any previous requests that were scheduled to go off
Automatically cancels any in-air XHR requests that were in progress when you make your request
Only invokes your callback for the latest request
If the user types "s", waits long enough for the request to go out, and then types "a", and the response for "s" comes back before the response for "sa" you won't have to deal with it.
The answer to the original question using bindDelayed would look like so:
// Wait 200ms before sending a request,
// avoiding, cancelling, or ignoring previous requests
$('#searchMe').bindDelayed('keyup',200,'/RPCsearch.php',function(){
// Construct the data to send with the search each time
return {queryString:this.value};
},function(html){
// Use the response, secure in the knowledge that this is the right response
$("#suggestions").html(html).show();
},'html','post');
In case my site is down, here's the plugin code for Stack Overflow posterity:
(function($){
// Instructions: http://phrogz.net/jquery-bind-delayed-get
// Copyright: Gavin Kistner, !#phrogz.net
// License: http://phrogz.net/js/_ReuseLicense.txt
$.fn.bindDelayed = function(event,delay,url,dataCallback,callback,dataType,action){
var xhr, timer, ct=0;
return this.on(event,function(){
clearTimeout(timer);
if (xhr) xhr.abort();
timer = setTimeout(function(){
var id = ++ct;
xhr = $.ajax({
type:action||'get',
url:url,
data:dataCallback && dataCallback(),
dataType:dataType||'json',
success:function(data){
xhr = null;
if (id==ct) callback.call(this,data);
}
});
},delay);
});
};
})(jQuery);
You really ought to look at using the jQuery autocomplete plugin. I find this plugin to be very useful and it already does what you need. Look particularly at the delay option, which you can customize to change how long the plugin waits after a keystroke to run.
1 solution in psuedocode:
OnKeyPress()
txt = getTxt
sleep(200)
newTxt = getTxt
if (txt == newTxt) // nothing has been typed so do something
run my thing
this one is happy
$(document).ready(function(){
$("#searchMe").keyup(function () {
try{window.clearTimeout(timeoutID);}catch(e){}
timeoutID = window.setTimeout(run, 2000); //delay
function run()
{ //dowhatev
var text = $("#searchMe").val();
//$("#showit").html(text);
}
});
});
I have found the best success when attaching the event to keypress, keydown, and keyup inputs. Safari/FireFox/IE all seem to handle special keypresses (delete, backspace, etc.) a bit differently but using all events together seems to cover it. The only way that running all events works though is to use setTimeout so that when they all fire it just resets the timer and ultimately the callback only gets executed once.
var delay = 200;
var search_timer = null;
$("#searchMe").keydown(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});
$("#searchMe").keypress(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});
$("#searchMe").keyup(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});