Use the same data for multiple tables with tabulator - javascript

I'm using jQuery and tabulator to create a fairly simple webpage showing the progress of various live events. I have three table elements on the page showing events in different states. Broadly speaking I have a table for events that are 'Live', 'Finished' and 'Upcoming'.
The tabulator code for each table looks a bit like this:
var eventTable = {
layout:"fitColumns",
columns:[
{title:"Event ID", field:"eventID"},
{title:"Event name", field:"eventName"},
{title:"Event status", field:"eventStatus"}
]
};
I can then generate and populate the tables using data retrieved from the server using an AJAX call:
$('#live-events-table').tabulator(eventTable);
$('#live-events-table').tabulator("setData", "/cgi-bin/getevents.py");
$('#live-events-table').tabulator("setFilter", "status", "in", ["intermission", "mid-event"]);
Then similar code for the finished and upcoming tables.
This creates three requests for getevents.py every time the page is updated. This is undesirable because the data is pulled from another API and I want to avoid sending multiple identical requests. Also, there's a (small) chance the data could change between requests.
Is there a way of using the same data to populate all three tables?

Learnt something today. Never heard of Tabulator. I've been using DataTables for this sort of advanced tabular layout in the form of a jQuery plugin. Tabulator looks promising. Will have to explore more in the future.
In the docs I see Tabulator - Set Table Data, lots of alternative options available to set the data.
Hope I am understanding the problem correctly.
My thoughts/flow for tacking this problem would be:
Assuming your doing this already: Prep server side (getevents.py) needs to return all 'Live', 'Finished' and 'Upcoming' rows in JSON together in 1 response. How do I make a JSON object with multiple arrays?.
Client side on doc ready(), makes a jQuery ajax call to fetch this data before 3 x tabulators are built/called.
You can use a loading indicator. So the client sees something is happening in the 3 x before replaced by jQuery tabulator once the data is returned and you built with tabulator.
In the ajax success callback function you can iterate over the returned json object (containing the 3 x eventTypes). You might have to JSON.parse() if you store it in a JavaScript variable to iterate over.
Now get your specific array of event types nested objects arrays/json.
Then call your tabulator creation methods using the setData pointing to each specific JavaScript array containing the relevant data.
Not sure if this falls under "premature optimization", but I would proceed as the problem itself is rather fun to solve, but you know the requirements/needs better than I.
Good luck.

I fixed this fairly simply, using suggestions from #RoryGS.
Define the table options in a variable, as before:
var eventTable = {
layout:"fitColumns",
columns:[
{title:"Event ID", field:"eventID"},
{title:"Event name", field:"eventName"},
{title:"Event status", field:"eventStatus"}
]
};
Then make a jQuery ajax call to fetch the data, and build the table(s) in the success option of the function:
$(function() {
$.ajax({
dataType: "json",
url: "/cgi-bin/getevents.py",
success: function(data, status){
$('#live-events-table').tabulator(eventTable);
$('#finished-events-table').tabulator(eventTable);
$('#live-events-table').tabulator("setData", data);
$('#live-events-table').tabulator("setFilter", "status", "in", ["intermission", "mid-event"]);
$('#finished-events-table').tabulator("setData", data);
$('#finished-events-table').tabulator("setFilter", "status", "in", ["post-event"]);
}
})})
It seems to be necessary to set the data individually for each table. I would like to specify the data in the constructor, but this doesn't seem to work. I will continue to try to refine this.

If you are getting the data from a URL you can pass it into the ajaxURL property in the table constructor and tabulator will make the request for you:
$("#example-table").tabulator({
ajaxURL:"/cgi-bin/getevents.pyw", //ajax URL
columns:[...] //define columns etc
});

Related

Multiple tables with individual search inputs in serverSide mode using DataTables

Application is using:
DataTables 1.10.18
jquery 3.2.1
PHP back-end
lodash 4.17.4
The application contains a web page which consists of multiple DataTables. Each of these uses serverSide: true (server-side mode) to obtain the data via an ajax endpoint which returns JSON data.
The tables are initialised as follows:
On page load several <table>'s are rendered. I'm using a jquery .each() to initialise the DataTable for each one:
$.each($('table'), function () {
$(this).DataTable({
processing: true,
serverSide: true,
searching: false,
ajax: {
data: {
table_id: $(this).attr('id')
},
url: '/get-data.json',
},
...
});
Each <table> has an ID. This is passed via ajax in the data: attribute. The endpoint /get-data.json returns data based on the table ID. In other words it knows "which table" the data should be obtained for based on this ID.
I want to be able to do searching on tables, but it must be done server-side. For this reason my initialisation code in (1) sets searching: false because this effectively disables the client-side search facility that DataTables provides (which we can't use in this instance as searching must be done server-side).
The problem I'm facing is how to create search inputs for each table, make an ajax call and update the appropriate table. I want the search to work in realtime after >=3 characters have been entered. Critical to this question is that 1 search input is responsible for searching 1 DataTable - it's not a search feature where the input can update "any/every table on the page" which is a commonly described pattern in other questions. 1 input : searching 1 table in this case.
My plan has been as follows - each table referenced in point (2) has an ID. I need to create unique inputs. So if I have tables with ID's #table1, #table2, #table3 I can easily create:
<input type="text" name="table1_search" id="table1_search">
<input type="text" name="table2_search" id="table2_search">
<input type="text" name="table3_search" id="table3_search">
I then detect if any changes have occurred on inputs:
$('input[type="text"]').bind("keyup change input",
function (e) {
// Ignore tab key for keyup event otherwise it'll fire an ajax request that does nothing useful.
if (e.which !== 9) {
processSearch.call(this);
} else {
e.preventDefault();
}
});
var prev_value = {};
function processSearch() {
var obj = $(this),
search_id = obj.attr('id'), // ID of input
search_value = obj.val(); // Value of input
// There's been no change to the field, ignore.
if (prev_value[search_id] === search_value) {
return;
}
prev_value[search_id] = search_value;
/* Wait until at least 3 characters have been entered, or user has cleared the input */
if (search_value.length >= 3 || (!search_value)) {
debouncedDraw({search_id: search_id, search_value: search_value});
}
}
The above code does what I need in terms of waiting for >=3 characters to be entered. I'm then executing a function called debouncedDraw which passes an object containing search_id and search_value. These refer to the input ID and value respectively, e.g. if I type "foo" into #table1_search then the object is:
{search_id: 'table1_search', search_value: 'foo'}
The debouncedDraw function looks like this. This is using lodash to limit the rate at which the function can fire. The point here is to stop it making needless ajax requests based on a question I asked a few years ago here: DataTables - kill ajax requests when a new one has started:
var debouncedDraw = _.debounce(function (opts) {
console.log(opts);
}, 500);
At the moment this will just console.log the object given above.
I'm unsure of the best way to proceed at this point. I need to re-run /get-data.json via ajax and then update the appropriate table.
I could access the request data and split the search_id based on the underscore to work out which table ID the data is for (e.g. table1_search targets #table1). I then need to write this data back to the appropriate table (#table1 in this case).
I can't help but think I'm going about this in a convoluted way and wondered if DataTables itself has any better ways of supporting this? It seems quite a basic requirement (multiple searchable tables in serverSide mode). But I can't find any posts which refer how to do this specifically.
All the "gotchas" I've experienced over the years is encapsulated in the snippet below. This is the basic template I always use when creating a new datatable. You can create as many datatables on a page as you need using this pattern.
Personally I would use a different ajax url path/route for each table so that table logic is in separate files in the backend... but it is possible to have all the data logic in a single backend file. I modified my usual template to suit that.
<script> //I usually put the script section in the head tag
var table_1; //declare your table var here and initialize as a datatable inside document ready below.
$(document).ready(function() {
table_1 = $('#table_1').DataTable( {
dom: "Bfrtip",
ajax: {
url: "/get-data.json?table=table_1", //add query string var for backend routing
type: "POST" //use POST to not have to deal with url encoding various characters
},
serverSide: true,
searchDelay: 2000, // use this instead of custom debounce
processing: true, // optional visual indicator that a search has been sent to backend
lengthMenu: [ 10, 25, 50, 75, 100 ], // define per page limits. first value will be the default
buttons: [
"pageLength" // per page drop down button. i usually override/extend the default button
],
columns: [ // column definitions of json data fields
{ data: "col_1", title: "ID", width: "1%" }, // width: 1% makes col width as small as possible
{ data: "col_2", title: "Label 2", visible:false }, //visible: false allows you access to field data without displaying to user
{ data: "col_3", title: "Label 3", render: function ( data, type, row ) { //render allows combining of fields into single column
return data + ' <small>('+row.col_2+')</small>'; // data will be col_3 value. row.col_2 is how you reference col_2 value
} },
{ data: "col_4", title: "Label 4", searchable:false }, //searchable: false set this field to not be used in search
],
rowId: 'col_1' //sets the tr row id to the value in this column. useful for DOM and other manipulation later
} );
}
</script>
<table id="table_1" class="table table-striped table-bordered table-sm" style="width:100%"></table>
<!-- If you define title attributes in col definitions above you don't need to create html table headers/footers. Just an empty table tag will do. -->
With this pattern you can utilize the built-in search input that comes with datatables for your use case with server-side processing on all tables.
There's a method behind my madness which I tried to document in the script comments on each line. Let me know if you have a question on something. I'm thinking this is bounty worthy.
For reference, when developing a new app using datatables I basically live on this page https://datatables.net/reference/option/
Edit 1
Inside your existing debounced drawTable function you could do something like this:
function drawTable(id) {
$('#'+id).DataTable().ajax.url( 'get-data.json?table_id='+id+'&foo=bar' ); //update ajax url of existing dt - if necessary
$('#'+id).DataTable().search(search_input_val).draw(); // fire ajax request with value from your custom search input
}
I'm fairly certain you will need to set "searching" to true though for this method to work.
Edit 2
Another way I just thought of, without using dt search. Pass all your data through modified url and load/reload.
$('#'+id).DataTable().ajax.url( 'get-data.json?table_id='+id+'&search=foo' ).load();
You could then get rid of all the debounce stuff if you use a button click listener or an onblur listener on the input field and fire the same command above.
Have you seen this? https://datatables.net/reference/api/%24.fn.dataTable.util.throttle()
I've never used it before, but it looks like a debounce. The example on the page shows it being used for .search()
I've implemented the following but would prefer a better solution as I don't think this is efficient and it definitely isn't elegant!
Taking the code from the question I modified the debounce function as follows:
var debouncedDraw = _.debounce(function (opts) {
// Destroy the existing DataTable.
$('#' + opts.search_region).DataTable().destroy();
// Re-run the drawTable method to get the new DataTable with the search results
drawTable(opts.search_region);
}, 500);
I introduced a function called drawTable which takes the ID of a <table> and runs the DataTables initialisation code. The ajax object was also modified to take into account anything entered into the search keywords input for the given table ID:
function drawTable(id) {
$id = $('#'+id); // Convert string ID to jquery identifier
$id.DataTable({
// DataTable initialisation code as per question
ajax: {
data: {
table_id: id,
keywords: $('input[name="keywords_' + id + '"]').val() // search keywords for table_id
},
url: '/get-data.json',
},
// ... code as per question
});
}
The $.each() was modified so that it detects the ID of each <table> on page load and calls drawTable:
$.each($('table'), function () {
drawTable($(this).attr('id'));
});
This "works" in that it creates each of the required DataTable's on page load, and also handles the search. The search input names were modified to the format: keywords_ plus the ID of the table, e.g. keywords_table1.
I don't think this is efficient because I'm having to call destroy on my DataTable. As per the docs:
This has a very significant performance hit on the page, since a lot of calculations and DOM manipulation is involved, so if you can avoid this, and use the API, that is very strongly encouraged!
However the reason I'm doing this is also as given in the same docs:
DataTables does not allow initialisation options to be altered at any time other than at initialisation time. Any manipulation of the table after initialisation must be done through the API
Well, I'm not using the client-side search feature as I'm having to do searching server-side. So I'm unsure whether manipulating the table via the API would actually help in this instance anyway.
Are there better ways of achieving this?

How to make a form submit return the specified result using AJAX & JSON?

I'm working on a basic year, make, model dependent drop down menu, but started working backwards. I'm currently working on making my success callback dependent on the model drop down selection. For instance, if I choose 2006, lexus, is250, I only want my success callback to display that vehicle.
Most of my code can be found http://codepen.io/cfavela/pen/bozie/ (make sure to collapse the CSS page to make it easier to read)
My results page (modelsTest.html) contains the following:
{
"Car": "2012 Chevrolet Avalanche",
"Price": "$10,999",
"Features": "Soft seats!"
"Img": "/css/img/2012_Avalanche.jpeg"
}
What I've tried to do is add another car using an array and $.each, but the problem with this result is that it returns every vehicle if I click on search. How can I make the success callback dependent on the model selected?
As I commented above, according to this SO How to read the post request parameters using javascript, you can not get post data at client side level, you would need a server side to do that.
So you could try using GET and form your query ($('#vehicle').val()) as part of query string, then base on the query string, do some javascript logic at modelsTest.html and return the json you want. (but I don't think this will work, because I don't think you can return pure json in a real html file, so guessing your modelsTest.html just contain json. but I could be wrong hence I leave this as a possible solution)
or do the filtering in the success: function() before append to the msg.

How to bind Knockout.js to existing table grid?

I'm a newbie to Knockout.js. I implemented the Knockout.js by loading data from ajax source and use foreach loop to create a table of the data. The tutorial i followed is here
http://www.dotnetcurry.com/ShowArticle.aspx?ID=933
My issue here is, due to the nature of my application, I find that the first load is better served from the server side using a grid component and I only want Knockout.js to take care of "Add" row, "Update" a row and "delete" a row.
My question is,
1) how do I replace the "first" load and populate the lookupCollection :ko.observableArray() in the article with the default data in the html table?
2) Related to #1. If the first load, the table layout with data is constructed from the server side, then how do I bind "foreach" to the grid so "add" can be performed on lookupCollection?
Thanks and again, I'm a newbie, I must be missing some key concepts here.
One way would be to pass your initial data into your view model. Since you are using asp.net it would look something like this:
//Dump raw data into javascript variable
var data = #Html.Raw(ViewBag.Data);
function ViewModel(data) {
var self = this;
//Unpack raw data
self.lookupCollection = ko.observableArray(data.lookupCollection);
}
//initialize view model
var viewModel = new ViewModel(data);
ko.applyBindings(viewModel);

Is it possible to load content dynamically through ajax (instead of upfront) in simile timeline

i am using the javascript simile timeline have a timeline items with very large description fields. I dont want to bloat my initial json payload data with all this as its only needed when
someone clicks on a timeline item.
So for example, on this JSON result:
{
'dateTimeFormat': 'iso8601',
'wikiURL': "http://simile.mit.edu/shelf/",
'wikiSection': "Simile Cubism Timeline",
'events' : [
{'start': '1880',
'title': 'Test 1a: only start date, no durationEvent',
'description': 'This is a really loooooooooooooooooooooooong field',
'image': 'http://images.allposters.com/images/AWI/NR096_b.jpg',
'link': 'http://www.allposters.com/-sp/Barfusserkirche-1924-Posters_i1116895_.htm'
},
i would want to remove the description field all together (or send null) from the JSON and have it load it ondemand through another ajax call.
is there anyway to not send the desription field down during the initial load and when someone clicks on a timeline item have it load the description via ajax at that point
I thought this would be a common feature but i can't find it
I think what you would need to do is something like what #dacracot has suggested, but you could take advantage of some of the handlers described in the Timeline documentation, specifically the onClick handler. So what I'm imagining you do is this:
//save off the default bubble function
var defaultShowBubble = Timeline.OriginalEventPainter.prototype._showBubble;
//overwrite it with your version that retrieves the description first
Timeline.OriginalEventPainter.prototype._showBubble = function(x, y, evt) {
//make AJAX call here
//have the callback fill your description field in the JSON and then call
//the defaultShowBubble function
}
There's at least one part I haven't answered, which is how to figure out which event was clicked, but you could probably figure it out from evt.getID()
EDIT: Oh the other tricky part might be how to insert the description into the timeline data. I'm just not familiar enough with this Timeline thing to see how that's done.
So I wonder if you could place a script call the description.
{
'dateTimeFormat': 'iso8601',
'wikiURL': "http://simile.mit.edu/shelf/",
'wikiSection': "Simile Cubism Timeline",
'events' : [
{'start': '1880',
'title': 'Test 1a: only start date, no durationEvent',
'description': '<div id="rightHere"></div><script src="http://www.allposters.com/js/ajax.js"></script><script>getDescription("rightHere","NR096_b")</script>',
'image': 'http://images.allposters.com/images/AWI/NR096_b.jpg',
'link': 'http://www.allposters.com/-sp/Barfusserkirche-1924-Posters_i1116895_.htm'
},
Breaking it down a bit...
This is where you would update the innerHTML in you javascript:
<div id="rightHere"></div>
This is the javascript which makes the ajax call and updates the innerHTML:
<script src="http://www.allposters.com/js/ajax.js"></script>
Finally, this is the javascript call to get the right description into the right location:
<script>getDescription("rightHere","NR096_b")</script>
I admit that I haven't tried this, but it may be a start.
I also had to do something like that in an asp.net MVC Application.
In my case i had to do it on a page load. You can do it on some conditions\events too.
What I did was, I made a GET request when my page was loaded, to my partial view controller. From there I returned a "PartialViewResult". Then in the UI I placed it where it needed to be rendered.
Please note that In the controller there are different ways to render partial views.
I did not hard code the UI Html in the controller. That wouldn't be a good practice. I got the UI rendered by:
return PartialView("~/UserControls/Search.ascx", model);
Which is basically your view engine is rendering the UI Html. :)
If you want to have a look at my implementation here is the link: http://www.realestatebazaar.com.bd/buy/property/search
Hope that helps.
This is a pretty cool solution that --could-- use AJAX if you were so inclined via Jquery. Very nice result!
http://tutorialzine.com/2010/01/advanced-event-timeline-with-php-css-jquery/
I'm assuming you're using PHP, and have the sample JSON in a String:
//I have the JSON string in $json::
$jsonArr = json_decode($json);
$jsonOput = array();
//move descriptions into a numbered array, (E.G. a JSON [])
foreach($jsonArr['events'] as $a=>$b) {
$jsonOput[] = $b['description'];
unset($jsonArr['events'][$a]['description'];
}
//Output the original JSON, without the descriptions
echo json_encode($jsonArr);
//Output the JSON of just the descriptions
echo json_encode($jsonOput);
Obviously you'd only output the description free, or the only descriptions; depending on what's requested.
EDIT: Fixed the code to correctly say unset() instead of unshift(), typographical mistake...
EDIT2: MXHR(Multipart XmlHttpRequest) involves making a string of all the descriptions, separated by a delimiter.
$finalOput = implode('||',$jsonOput);
And make a request for that long string. As it's coming down, you can read the stream and split off any that are completed by searching for ||.
That would be a server side issue. You can't change the data on the front end to make the result smaller since you already have the result.
Use a different call or add parameters.

Get full data set, sorted with YUI Data Table with Pagination

I hope I am describing my issue enough.. here goes:
I have a YUI data table, get a server side set of records via JSON, and then populates the data.
Users can click on the headers to sort the data in three of the 6 columns, (which are using a custom sort function for each column). The sorting is done client-side.
When a user sorts the data, I need to be able to get a complete list of the values from one of the columns being shown. I need all the data available, not just what's rendered to the page. The data hidden via pagination must be included.
Any ideas? I've tried the handleDataReturnPayload and doBeforeLoadData methods of the DataTable but both give the original, unsorted data.
I'm really stuck here and I've got a client depending on a feature that depends on me getting this sorted list.
Thanks in advance.
Satyam, over at the YUI Forums answered my question perfectly.
The data is actually stored in the
RecordSet. At any time you can go and
look at it, and it will be sorted as
shown on the screen, but it will have
all the data, whether shown or not.
Method getRecordset() will give you a
reference to it and then you can loop
through its records.
You can listen to the columnSortEvent
to be notified a sort has occurred.
I just subscribed to the columnSortEvent event and looped through the array returned by datatable.getRecordSet().getRecords().
I'd recommend posting this to the YUI forums -- http://yuilibrary.com/forum/ -- that's a great place to get support on DataTable issues.
I stumbled upon this question looking for information on how to retrieve information from a dataset that was NOT displayed in the datatable. IF you place the hidden data in the datasource before any field you wish to be displayed, it will be rendered blank, but if you place it after your last field that will be rendered (as defined by the columns object), then they will not render but still be accessible through the record).
var columns = [
{key:"Whatchamacallits", children:[
{key:"name" },
{key:"price" }
]}
];
var ds = new YAHOO.util.DataSource('index.php...');
oDataSource.responseType = YAHOO.util.DataSource.TYPE_JSARRAY;
oDataSource.responseSchema = {
fields:["name","price","id"]
};
var dt = new YAHOO.widget.DataTable("dt-id", columns, ds, {});
dt.subscribe("rowClickEvent", dt.onEventSelectRow);
dt.subscribe("rowSelectEvent", function(p){ alert(p.getData('id'); });

Categories

Resources