I'm trying to complete a DataTable with information gathered from an API.
I made a "fiddle" here to make it easier to help and understand what I mean:
http://live.datatables.net/jujofoye/3/edit
I'm starting from an HTML table containing only ID's. Then I use that ID in a rowCallback to call an API and write the fetched value in the table with jQuery $('td:eq(1)', nRow).html( json.Title );
function customFnRowCallback( nRow, aData, iDisplayIndex ) {
var imdbID = aData[0];
fetch("http://www.omdbapi.com/?i="+imdbID+"&plot=short&r=json&tomatoes=true")
.then(function(response) {
return response.json();
})
.then(function(json) {
$('td:eq(1)', nRow).html( json.Title );
})
.catch(function(error) {
console.log('There has been a problem with your fetch operation: ' + error.message);
});
}
However the problem here is that you cannot sort on the second column. Even though you can see the data perfectly fine. I bet the DataTable doesn't really know there is new data and you end up sorting empty values instead of the values you added.
A second effect of this same issue (which does not show in my fiddle) is that collapsed rows (rows should collapse when not enough width) also show up empty when the row is expanded.
A third effect is that the search doesn't work on fetched data.
Is there a way to really add the fetched data to the DataTable? Not cosmetically that is.
(Note: All the responses I can find are about populating a full DataTable with an AJAX request. I'm only adding data to an already populated DataTable)
I took a different approach. I used the DataTable ajax with when/done so it will not refresh the table until all the gets are processed.
I set sorting so the list will come out alphabetically even those that is not the order of the list.
I also got unique set of values from imdb.
http://jsbin.com/yojoka/edit?html,js,output
<script type="text/javascript">
// Sample return set from
var sampleReturn = { "Title": "Seven Samurai", "Year": "1954", "Rated": "UNRATED", "Released": "19 Nov 1956", "Runtime": "207 min", "Genre": "Adventure, Drama", "Director": "Akira Kurosawa", "Writer": "Akira Kurosawa (screenplay), Shinobu Hashimoto (screenplay), Hideo Oguni (screenplay)", "Actors": "ToshirĂ´ Mifune, Takashi Shimura, Keiko Tsushima, Yukiko Shimazaki", "Plot": "A poor village under attack by bandits recruits seven unemployed samurai to help them defend themselves.", "Language": "Japanese", "Country": "Japan", "Awards": "Nominated for 2 Oscars. Another 5 wins & 6 nominations.", "Poster": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTc5MDY1MjU5MF5BMl5BanBnXkFtZTgwNDM2OTE4MzE#._V1_SX300.jpg", "Metascore": "98", "imdbRating": "8.7", "imdbVotes": "238,165", "imdbID": "tt0047478", "Type": "movie", "Response": "True" };
var deferreds = [];
var newData = [];
$(function ($) {
var dt = $("#example").DataTable({
columnDefs:[{targets:[0, -1], width:"150px"}],
columns: [
{ data: "imdbID" },
{ data: "Title" },
{ "data": "Year" }
],
deferLoading: 0,
deferRendering: true,
"order": [[ 1, "asc" ]],
ajax: function (data, cb, setting) {
// get the list of ids created by DataTables from the embedded html
var curData = $("#example").DataTable().rows().data();
// if you don't clear, you will end up with double entries
$("#example").DataTable().clear();
$.each(curData, function (i, item) {
var sr = { i: item.imdbID, plot:"short", r:"json", "tomatoes":true};
deferreds.push(
$.get("http://www.omdbapi.com/", sr)
.then(function (response) {
// push the response into the global array
newData[newData.length] = response;
})
);
});
// now make all of the calls. When done, use the callback to return the data and populate the table
$.when.apply(null, deferreds)
.done(function () {
cb({ data: newData })
});
}
});
});
</script>
You're correct in that DataTable doesn't know what's in the table to sort.
I would say to run the function once the table is completely made.
$('#example').find('tbody tr').each(customFnRowCallback);
function customFnRowCallback()
{
var $this = $(this);
var imdbID = $this.find('td:eq(0)').text();
fetch("http://www.omdbapi.com/?i="+imdbID+"&plot=short&r=json&tomatoes=true")
.then(function(response) {
return response.json();
})
.then(function(json) {
$this.find('td:eq(1)').text( json.Title );
})
.then(function() {
$('#example').DataTable();
})
.catch(function(error) {
console.log('There has been a problem with your fetch operation: ' + error.message);
});
}
Related
I have a strange issue on a system that creates a youtube playlist, and then fills the playlist with videos from a database table.
The code is Version Controlled by GIT, and have no commits in the code for 3-4 months, and it has worked up to a couple of days back.
Suddenly it can't find the items, and therefore doesn't find the id, of the item that it should find. The system creates a list, then find the latest created playlist (which is the one you just created), and then fills the playlist up normally.
I'm not very good as javascript to be honest, are there any good javascript developers out there that can solve this? The error seems pretty common when googling, but In addition to the youtube api use, I find it hard to figure out the issue. Why it suddenly doesn't result items. (If I run a GET in Postman, ill get the correct playlist, so it should be something in the code that isn't working 100%)
function createPlaylist() {
var client = document.getElementsByName("client")[0].value;
var description = document.getElementsByName("information")[0].value;
return gapi.client.youtube.playlists.insert({
"part": [
"snippet,status"
],
"resource": {
"snippet": {
"title": client,
"description": description,
"position": 1,
"resourceId": {
"kind": "youtube#video",
"videoId": "mhmGwTDpPf0"
},
"tags": [
"postural workout"
],
"defaultLanguage": "en"
},
"status": {
"privacyStatus": "public"
}
}
})
.then(function(response) {
return gapi.client.youtube.playlists.list({
"part": [
"id,snippet,contentDetails"
],
"maxResults": 1,
"mine": true
})
.then(function(response) {
console.log("Response", response);
MyVars.latestPlaylistID = response.result.items[0].id;
pID = MyVars.latestPlaylistID
console.log(pID + " is the latest playlist");
var doms = document.getElementsByTagName("tr");
// Get every TR into an array
var array = $('tbody > tr').map(function() {
return $.map($(this).data(), function(v) {
return v;
});
}).get();
//array.reverse();
var array = array.filter(function(element){ return element.length>=11});
videosIdArray = array.reverse();
console.log(pID, videosIdArray, 0);
addVideoToPlayList(pID, videosIdArray, 0);
// setTimeout(addVideoToPlayList(pID, videosIdArray, 0), 5000);
document.getElementById("playlistID").innerHTML = 'https://www.youtube.com/playlist?list=' + pID;
document.getElementById("playlistID").href = 'https://www.youtube.com/playlist?list=' + pID;
},
function(err) { console.error("ListPlaylist error", err); });
},
function(err) { console.error("InsertPlaylist error", err); });
}
This is what happens now:
Error
And as you can see the items array is empty.
I have now solved it!
By breaking down the function into smaller functions it seems to give me the correct result. Why the problem suddenly occured is still uknown, but I'm glad it now works.
This is the final solution if others out there are trying to solve similiar issue.
function createPlaylist() {
var client = document.getElementsByName("client")[0].value;
var description = document.getElementsByName("information")[0].value;
return gapi.client.youtube.playlists.insert({
"part": [
"snippet,status"
],
"resource": {
"snippet": {
"title": client,
"description": description,
"position": 1
},
"status": {
"privacyStatus": "public"
}
}
}).then(function(response) {
console.log("Response", response);
});
}
function addToPlaylist() {
return gapi.client.youtube.playlists.list({
"part": [
"id,snippet,contentDetails"
],
"maxResults": 1,
"mine": true
})
.then(function(response) {
console.log("Response", response);
MyVars.latestPlaylistID = response.result.items[0].id;
pID = MyVars.latestPlaylistID
console.log(pID + " is the latest playlist");
var doms = document.getElementsByTagName("tr");
var array = $('tbody > tr').map(function() {
return $.map($(this).data(), function(v) {
return v;
});
}).get();
var array = array.filter(function(element){ return element.length>=11});
videosIdArray = array.reverse();
console.log(pID, videosIdArray, 0);
addVideoToPlayList(pID, videosIdArray, 0);
},
function(err) { console.error("ListPlaylist error", err); });
}
I have a search input where my autocomplete shows but for some reason it the results are not being filtered - can anyone tell or show me a way to filter results to show the correct autocomplete prior in my code below.. Below is the json format and the html code updated. Thanks for the help.
Here is my code
$( function() {
var cache = {};
$( "#searchTextField" ).autocomplete({
minLength: 2,
source: function( request, response ) {
var term = request.term;
if ( term in cache ) {
response( cache[ term ] );
return;
}
$.post( "http://localhost:8080/myApp/JobSearchItem.xhtml", request,
function( data, status, xhr ) {
cache[ term ] = data;
response( data );
});
}
});
} );
JobSearchItem Return JSON
[
{
"id": "9000",
"label": "PROGRAMMER TEST 1 (9000) ",
"value": "90000"
},
]
html
<h:body>
<f:view transient="true">
<tp:header/>
<tp:searchForm/>
<div id="results">
</div>
<h:panelGroup id="dTable" class="container">
</h:panelGroup>
</f:view>
<f:view transient="true">
<div class="jobEntity">
<div class="job-container-header">
<h4>#{testBean.jobEntity.toString()}</h4>
<c:if test="#{testBean.jobEntity.validURLConnection}">
<a href="#{testBean.jobEntity.pGradeDescriptionLink}"
class="btn btn-info-One"
target="_blank">[ Test ]</a>
</c:if>
<h4>#{testBean.jobEntity.mu} - #{testBean.jobEntity.muDescription}</h4>
<h4>#{testBean.jobEntity.specialNotes}</h4>
<h4>#{testBean.jobEntity.syRgeMnSepMsg}</h4>
</div>
<c:if test="${testBean.jobEntity.sectionToDisplay eq 'Range'}">
<table class="table">
<tbody>
<tr>
<th></th>
<c:forEach var="stepNumber" begin="1" end="#{testBean.jobEntity.stepSize}">
<th>Step #{stepNumber}</th>
</c:forEach>
</tr>
<c:forEach items="#{testBean.jobEntity.jobRows}" var="jobRow">
<tr>
<th>#{jobRow.rateType}</th>
<c:forEach items="#{jobRow.steps}" var="step">
<td>#{step.amount}</td>
</c:forEach>
</tr>
</c:forEach>
</tbody>
</table>
</c:if>
</div>
When you specify a remote URL as the datasource like this, the remote server is expected to do the filtering based on the search term given to it by the autocomplete, and return the results already filtered.
Autocomplete only carries out the filtering if you provide it with static data. See http://api.jqueryui.com/autocomplete/#option-source for more details.
N.B. If your remote server is unable to do any filtering (e.g. because it just returns a static file) then you'd have to filter the data client-side in your callback before you return it to the autocomplete. But of course this is not very efficient because you keep downloading all the data and then discarding most of it (unless the browser helpfully caches it).
Since you are calling data from a .xhtml file, it is not going to be able to filter the results, unless you can update the server side script to accept and perform activities based on data posted to it.
I would suggest you gather the static data upfront and then filter based on that. This might look something like:
$( function() {
var myData;
$.get( "http://localhost:8080/myApp/JobSearchItem.xhtml", function( data ){
myData = data;
} );
$( "#searchTextField" ).autocomplete( {
minLength: 2,
source: myData
} );
} );
This assumes that your xhtml is providing a Array of data (usually in JSON format). This can be simple:
[
"Item 1",
"Item 2",
"Item 3"
];
Or something more advanced:
[{
"label": "Item 1",
"value": 1
},{
"label": "Item 2",
"value": 2
},{
"label": "Item 3",
"value": 3
}];
If the data you get back is something else: HTML Table, XML, or text, then using a function with Source will help you. If you update your question and provide an example of the data, we could provide a more complete example or guidance.
Update 1
Given the following JSON Data:
[{
"id": "9000",
"pGrade": "0",
"label": "PROGRAMMER TEST 1"
},{
"id": "6000",
"pGrade": "0",
"label": "WEB PROGRAMMER TEST 1"
}];
This does not comply with the standard Autocomplete expected data. If you are able to POST data to JobSearchItem.xhtml, then you can have it filter first and return data. If JobSearchItem.xhtml does not accept POST, then I would perform a GET of all the data up front and then filter it later. I will include an example of both.
POST
If you are posting the data, the server-side script needs to know what data you are sending it in the form of a variable name and value. You did not supply a variable name in your example and you have not supplied the JobSearchItem.xhtml content, so it's really hard to identify how this script works.
For this example, we will use term and our example data will be we. If this was a GET command, it would look like:
JobSearchItem.xhtml?term=we
For Post we will use an Object that is submitted:
{ "term": "we" };
Here are the basics:
$(function(){
var cache = {};
$("#searchTextField").autocomplete( {
minLength: 2,
source: function(request, response){
var t = request.term;
if (t in cache){
response(cache[t]);
return;
}
var results = [];
$.ajax({
url: "http://localhost:8080/myApp/JobSearchItem.xhtml",
data: {
term: t
},
dataType: "json",
method: "POST",
success: function( data, status, xhr ) {
$.each(data, function(k, v){
results.push({
label: v.label,
value: v.label,
id: v.id,
grade: v.pGrade
});
});
cache[t] = results;
});
response(results);
});
}
});
});
So, in this case, if the user enters we, this is sent to the script, which will filter the results and will send back JSON that should look like:
[{
"id": "6000",
"pGrade": "0",
"label": "WEB PROGRAMMER TEST 1"
}];
Since Autocomplete is expecting an object containing label and value can't just be sent direct to response(). Using $.each() we can iterate the results and adjust so that it's formatted for Autocomplete.
GET
If your obSearchItem.xhtml is static and just provides a list of JSON data, using GET might be a good way to collect this data. Consider that you can get all this data up front, and then use it later. This is the most common way to use Autocomplete, but the data still has to be in the right format.
$( function() {
var myData = [];
$.get("http://localhost:8080/myApp/JobSearchItem.xhtml", function(data){
$.each(data, function(k, v){
myData.push({
label: v.label,
value: v.label,
id: v.id,
grade: v.pGrade
});
});
});
$("#searchTextField").autocomplete({
minLength: 2,
source: myData
});
});
One of these should work.
I have a rails application where I have JSON data as shown below:
{"makes":[{"id":200347864,"name":"AM General","niceName":"am-general","models":[{"id":"AM_General_Hummer","name":"Hummer","niceName":"hummer","years":[{"id":3407,"year":1998},{"id":1140,"year":1999},{"id":305,"year":2000}]}]}]}
This is a very long list of car objects with multi-levels of nesting. Make, model, year, trim etc.
I want to send this JSON to javascript and populate my autofill drop down menu.
Previously, I was using a third-party API and the code looked like:
$( document ).ready(function() {
var prev ="https://xyz/makes";
var rest ="?fmt=json&api_key=";
var key = "<%= ENV['XYZ_API_KEY'] %>";
var net = prev+rest+key;
var options = [];
options.push("All");
var dictionary = {};
$.ajax({
url: net,
dataType: "json",
type: "get",
data: $(this).serialize()
}).done(function(data){
change_make(data);
change_model();
});
function change_make(data) {
for (var key in data["makes"]){
if (data["makes"].hasOwnProperty(key)){
var make = data["makes"][key];
options.push(make.name);
buffer = [];
// console.log(make.models);
for (var key2 in make.models){
// console.log(make.models[key2].name);
if (make.models.hasOwnProperty(key2)){
// console.log(make.models[key2].name);
buffer.push(make.models[key2].name);
}
}
dictionary[make.name] = buffer;
}
}
dictionary["All"] = ["All"]
// console.log(options);
$.each(options, function(key, value) {
$('#category')
.append($("<option></option>")
.attr("value", value.html_safe)
.text(value));
});
};
$('#category').on('change', function(){
console.log("change success");
change_model();
});
function change_model(){
var make = $('#category').find(":selected").text();
var models = dictionary[make];
// models.unshift("All");
$('#subcategory').empty();
$.each(models, function(key, value) {
$('#subcategory')
.append($("<option></option>")
.attr("value",value.html_safe)
.text(value));
});
}
$("#searchboxcontainer").delay(100).fadeIn(200);
});
Instead of using the ajax request, I want to use json string directly.
I wrote a helper method in application helper module as shown:
def edmunds_json
the_json_object
end
But when I am using it in javascript, its adding &gm characters and theobject comes out as:
{:makes=>[{:id=>200347864, :name=>"AM General",...
and the code is giving errors
Uncaught SyntaxError: Unexpected token :
When I am using escape in JSON, it's giving me the error on rails unexpected $undefined. expecting ').
The JSON object is using double quotes and I want to use it in my code. How should I proceed so > etc won't be added.
I think what you need to do is to first convert your Ruby Hash into json in your helper
def edmunds_json
the_json_object.to_json
end code here
Then in your view in a script tag do something like this
<script>
const object = <%= raw edmunds_json %>
console.log(typeof(object)) // => Object
</script>
Solved it by using:
<%= get_make_model_json.to_s.gsub("=>",":").html_safe %>; in javascript
where get_make_model_json is:
def get_make_model_json
JSON.parse(
'{
"makes": [{
"id": 200347864,
"name": "AM General",
"niceName": "am-general",
"models": [{
"id": "AM_General_Hummer",
"name": "Hummer",
"niceName": "hummer",
"years": [{
"id": 3407,
"year": 1998
}, {
"id": 1140,
"year": 1999
}, {
"id": 305,
"year": 2000
}]
}]
},...
end
I've got a query tool I've been working on, which has an angular form that is filled out, and then when it's submitted it uses AJAX which returns JSON, which is then rendered into ui-grid, that JSON response looks like
{
"success": true,
"message": "",
"columns": ["first_name", "last_name", "company", "employed"]
"results": [
{first_name: "John", last_name: "Smith", company: "Abc Inc", employed: true},
{first_name: "Johnny", last_name: "Rocket", company: "Abc Inc", employed: true}]
}
I'm working on both the PHP and angular so I have full control over this JSON response if need be. I'm running into an issue when my JSON response from a first AJAX call is rendered, and then I run another, seperate AJAX call on the same page and get a new data set: this new data set does not render any of the columns that were not in the original data set. This is hugely problematic as the table is essentially cleared when none of the columns are the same, and I often need to load completely different data into ui-grid in this single page app.
When the JSON is recieved I simply bind the jsonResult.results to the old $scope.myData variable that ui-grid is bound to.
I've made a plunker isolating this issue. A dataset with a "punk" column is loaded, and then clicking "swap data" will try to load a dataset with "employee" column instead of "punk". I've so far looked into directives that will refresh or reload when the $scope.myData variable changes using $watch, and looked at finding something like $scope.columnDefs to let ui-grid know. Relatively new to angular and javascript so directives are still a bit over my head.
I have updated your plunker slightly:
$scope.swapData = function() {
if ($scope.gridOpts.data === data1) {
$scope.gridOpts.columnDefs = [
{ name:'firstName' },
{ name:'lastName' },
{ name:'company' },
{ name:'employee' }
];
$scope.gridOpts.data = data2;
//punk column changes to employee
}
else {
$scope.gridOpts.columnDefs = [
{ name:'firstName' },
{ name:'lastName' },
{ name:'company' },
{ name:'punk' }
];
$scope.gridOpts.data = data1;
//employee column changes to punk
}
};
http://plnkr.co/edit/OFt86knctJxcbtf2MwYI?p=preview
Since you have the columns in your json, it should be fairly easy to do.
One additional piece that I figured out with the help of Kevin Sage's answer and the plunker example... If you are using the backward-compatible "field" attribute the swapping does not work properly when there are field name overlaps between the two sets of column definitions. The column headers and the column widths are not rendered properly in this case. Using the "name" attribute of the column definition corrects this.
$scope.swapData = function() {
if ($scope.gridOpts.data === data1) {
$scope.gridOpts.columnDefs = [
{ field:'firstName' },
{ field:'lastName' },
{ field:'company' },
{ field:'employee' }
];
$scope.gridOpts.data = data2;
//punk column changes to employee
}
else {
$scope.gridOpts.columnDefs = [
{ field:'firstName' },
{ field:'lastName' },
{ field:'company' },
{ field:'punk' }
];
$scope.gridOpts.data = data1;
//employee column changes to punk
}
};
Example here: Plunker
My solution:
$http.get('url').success(function(res) {
// clear data
gridOptions.data.length = 0;
// update data in next digest
$timeout(function() {
gridOptions.data = res;
});
});
Below is the structure of JSON which I use to query an API
"order_items": [
{
"menu_item_id": "VD1PIEBIIG",
"menu_item_name": "Create Your Own",
"modifiers": [
{
"modifier_id": "6HEK9TXSBQ",
"modifier_name": "Shrimp"
}
],
"quantity": "1",
"total": 15.99,
"variant_id": "TXDOR7S83E",
"variant_name": "X-Lg 18\""
}
]
Now I want to call this API from an HTML page using Javascript(Using HTML elements like forms and drop down menus etc). I want to create a Javascript object with proper structure and then convert it to JSON using "stringify" function. But I am not able to create the Javascript object. Can anyone help with this?
Like i want to have the following structure
obj.order_items[0].menu_item_id="VD1PIEBIIG";
obj.order_items[0].menu_item_name="Create Your Own";
obj.order_items[0].modifiers[0].modifier_id="6HEK9TXSBQ";
and so on.
var jsonToSend = { "order_items": [ ] };
// then for each order item
var orderItem = { "menu_item_id": <whatever>,
"menu_item_name": <whatever>,
"quantity": <whatever>,
"total": <whatever>,
"variant_id": <whatever>,
"variant_name": <whatever>,
"modifiers": []
};
// then for each modifier
var modifier = { "modifier_id": <whatever>, "modifier_name": <whatever> };
orderItem.modifiers.push(modifier);
jsonToSend.order_items.push(orderItem);
JSON.stringify(jsonToSend);
Well there are a couple of ways to do this.
Manually create the Json object to send from the HTML elements:
$.ajax({
type: "POST",
url: "some.php",
data: new {"order_items": [
{
"total": $('total').Val(),
"variant_id": $('variant_id').Val(),
"variant_name": $('variant_name').Val()
}
]})
.done(function( msg ) {
alert( "Data Saved: " + msg );
});
You could use a great framework like KnockoutJs, this will keep your JSON object up to date with your form, so that you don't have to do it manually. When you are ready you just submit your original json back to the server.
See this basic example on JsFiddle
var ClickCounterViewModel = function() {
this.numberOfClicks = ko.observable(0);
this.registerClick = function() {
this.numberOfClicks(this.numberOfClicks() + 1);
};
this.resetClicks = function() {
this.numberOfClicks(0);
};
this.hasClickedTooManyTimes = ko.computed(function() {
return this.numberOfClicks() >= 3;
}, this);
};
ko.applyBindings(new ClickCounterViewModel());
You can use any number of plugins to Serialize the form, but the problem is getting the JSON structure just right.
See SerializeArray
$( "form" ).submit(function( event ) {
console.log( $( this ).serializeArray() );
event.preventDefault();
});