I develop small backend service/application with Spring Boot and some jQuery/AJAX.
Recently while testing admin functionality I faced strange issue related with the code provided below:
function updateUserRow(id) {
$.get(ajaxUrl + id, function (data) {
$('.userPlaceBtns').empty();
$('.userArticleBtns').empty();
$.each(data, function (key, value) {
form.find("textarea[name='" + key + "']").val(value);
form.find("input[name='" + key + "']").val(value);
form.find('input:checkbox').prop(value ? 'checked' : '');
if(key == 'placeIds' && value.length != 0){
for(i = 0; i < value.length; i++) {
$('<button/>', {
text: value[i],
id: 'btn_place_'+i,
click: function () {
inspectOwnedPlace(value[i]);
}
}).appendTo('.userPlaceBtns');
}
}
if(key == 'articleIds' && value.length != 0){
for(i = 0; i < value.length; i++) {
$('<button/>', {
text: value[i],
id: 'btn_article_'+i,
click: function () {
inspectOwnedArticle(value[i]);
}
}).appendTo('.userArticleBtns');
}
}
});
$('.load-bar').hide();
$('#userEditRow').modal();
});
}
So, besides modal forms data filling, this function helps me to iterate through user-related data and place a button on prepared divs for each of this data.
Strange things happen when I try to inspect user-related places or articles with $.get from server:
function inspectOwnedPlace(id){
console.log(id);
var intId = parseInt(id);
console.log(placesAjaxUrl + intId);
$.get(placesAjaxUrl + intId, function (data) {
var placeForm = $(".ownedPlaceForm");
$.each(data, function (key, value) {
placeForm.find("textarea[name='" + key + "']").val(value);
placeForm.find("input[name='" + key + "']").val(value);
});
$('#inspectOwnedPlaceModal').modal();
});
}
The Issue
For some reason, in 50-70% of cases I get NumberFormatException on server side because of NaN incoming with server request. But in SOME cases it definitely works as expected!
Important thing is: the text on generated buttons ALWAYS appears as expected, so I can make conclusion that server always gives relevant,
non-spoiled data to the front and something in JS code leads to frequent NaN exceptions when I try to request user-relevant data by id as the next step.
Here is a screenshot of the case when I face this issue (browser tab with dev console) for better understanding:
So, here I get server's NumberFormatException because of NaN instead of integer.
What I tried:
treat ids as they are by default not usig parseInt() function;
treat ids as strings and parse them as text.
I just wonder what can lead to non-predictable, non-systemic NaNs, which are more likely "spoiled integers", in the context of my current code. Thanks in advance!
Related
I can't add new name and value ff. this given condition:
$.each(names, function (i, name) {
$.get('https://www.example.com/path/' + name, function (data) {
var arrNow = CSVToArray(data, ',');
allArr.push(arrNow);
counter++;
if (counter === names.length) {
for (var j = 0; j < allArr.length; j++) {
for (var k = 1; k < allArr[j].length; k++) {
//console.log(allArr[j][k][0] + ': ' + allArr[j][k][1]);
//var f = moment(allArr[j][k][0]).format('lll');
var f = allArr[j][k][0];
json.push({
"datetime": f
});
if (j == 0) {
if (json[k].datetime === allArr[0][k][0]) {
var newAtt = "water_actual";
var newValue = allArr[0][k][1];
json[k][newAtt] = newValue;
}
}
if (j == 1) {
if (json[k].datetime === allArr[1][k][0]) {
var newAtt = "rainfall_actual";
var newValue = allArr[1][k][1];
json[k][newAtt] = newValue;
}
}if (j == 2) {
if (json[k].datetime == allArr[2][k][0]) {
var newAtt = "forecast_water";
var newValue = allArr[2][k][1];
json[k][newAtt] = newValue;
}
}
}
}
};
});
});
I was able to add a new namewater_actual and its value using if statement. If the datetime from the json object matches to the array value(date and time), I'd like to add it with its specific name as stated above. But I can't seem to make it work.
Here's the fiddle.
If I may provide some general feedback: it's probably good practice to simplify your code to the minimum example that reproduces your problem. Not only can that drastically increase your chances of fixing it yourself, it also increases the odds that you'll get help here.
With that in mind, consider the basic structure of what you're trying here:
var someNames = ["foo", "bar"];
var allTheData = [{
"aardvark": true
}];
$.each(someNames, function (i, name) {
$.get('http://example.com/api/' + name, function (data) {
data.aNewProperty = 'wombat';
allTheData.push(data);
});
});
console.log(allTheData);
Here, $.each iterates through everything in someNames and then proceeds immediately to the console.log statement. For all we know, each individual API call ($.get) could take seconds, or minutes. By this time we've already tried to use the contents of allTheData, which may or may not have been modified.
To avoid this sort of thing in legacy JavaScript we can make use of the callback already provided by $.get:
$.get('http://example.com/api/' + name, function (data) {
data.aNewProperty = 'wombat';
console.log(data);
});
Inside the callback, we know for sure that the API request has already completed (although the above assumes that it succeeded, which is a whole other kettle of fish). This would output the result of each API request as the responses arrive, though not necessarily in the order you'd expect!
JavaScript's asynchronous nature tended to lead in the past to a whole lot of callbacks. With the advent of ES6 we have some more options available to us, especially promises.
I have searched around and can't seem to find what I'm looking for.
How can I use the results of api.php?action=query&list=allusers&augroup=sysop&aulimit=max&format=json in a javascript?
What I'm trying to do is create a script to simply change the color of usernames on the wiki if they are in certain groups, like sysop, bureaucrat, etc.
Although I'm usually pretty good at figuring these things out, I've been working on this all day and I've gotten nowhere with it. Can anyone help me out with maybe some examples or something? If it can be done with mostly jQuery that would be preferable.
Thanks in advance.
Edit: (in response to comment by ahren):
Well I started out trying to clean up and modify a script written by someone else to add more functionality/make it work as expected, but I had trouble making sense out of it:
/* HighlightUsers by Bobogoobo
* Changes color of links to specified groups and users
* TODO: redo but much better (recursive would be easier - I've learned a lot since I wrote this thing)
*/
function highlightUsers () {
"use strict";
var highlight = window.highlight || {}, selector = '', that, userstr,
indices = [],
i = 0,
user,
ns,
x,
y;
for (ns in mw.config.get('wgNamespaceIds')) {
if (i === 4) {
userstr = ns;
}
i++;
}
userstr = userstr.charAt(0).toUpperCase() + userstr.substring(1);
if (highlight['selectAll']) {
selector = 'a[href$=":';
} else {
selector = 'a[href="/wiki/' + userstr + ':';
}
for (y in highlight) {
indices.push(y);
}
for (x in highlight) {
that = highlight[x];
if (x === 'selectAll') {
continue;
} else if (x === 'users') {
for (user in that) {
$(selector + user.replace(/ /g, '_') + '"]').css({
'color': that[user],
'font-weight': 'bold'
}).attr('data-highlight-index',
$.inArray('users', indices));
}
} else {
(function (userColor, userGroup) { //JavaScript doesn't like to cooperate with me
$.getJSON('/api.php?action=query&list=allusers&augroup=' + userGroup +
'&aulimit=max&format=json', function (data) {
var stuff = data.query.allusers, //, select = '';
user;
for (user in stuff) {
//select += selector + stuff[user].name.replace(/ /g, '_') + '"], ';
$(selector + stuff[user].name.replace(/ /g, '_') + '"]').each(function () {
if (($(this).attr('data-highlight-index') || -1) < $.inArray(userGroup, indices)) {
$(this).attr('data-highlight-index', $.inArray(userGroup, indices));
$(this).css({
'color': userColor,
'font-weight': 'bold'
});
}
});
}
//select = select.substring(0, select.length - 2);
//$(select).css('color', userColor);
});
}(that, x));
}
}
}
That is my latest draft of it, I managed to accomplish a few things, like making the names bold, and correcting syntax mishaps, but I've decided I may be better off starting from scratch than trying to understand someone else's code.
i would prefer using jQuery AJAX functionality.
Usage is simple :
$.ajax({
url : 'api.php',
type : 'post',
datatype : 'json',
data : {
'list' : allusers,
'augroup' : 'sysop'
},
success : function(success_record) {
//here you can do Js dom related modifications like changing color etc.
// after php(server side) completes
}
});
I tried the AJAX solution described by Markrand, but unfortunately I wasn't able to get it to work. First off I was getting "allusers is not defined", so I wrapped it in quotes so that it wasn't treated as a var, then I had to add change 'api.php' to '/api.php' because it was becoming '/wiki/api.php' which doesn't exist, and adding the slash got it to use the base URL. It would then execute and return an object, however there was nothing useful in that object that I could use (such as an array of usernames), all it gave me was the API documentation... So I ended up doing this instead:
function highlightAdmins() {
$.getJSON('/api.php?action=query&list=allusers&augroup=sysop&aulimit=max&format=json',
function(data) {
for (var i = 0; i < data.query.allusers.length; i++) {
$('a[href$="User:' + data.query.allusers[i].name + '"]').css('color', '#FF6347');
}
});
}
This gave me an object containing the results of the query, in this case an array of sysop usernames (data.query.allusers[i].name) which I could iterate though and perform actions with.
I have an application that uses the DataTables jQuery library to render content in my target browser IE8. The problem is when I push a big array to be rendered, IE8 sometimes throws up the infamous long running script error.
After profiling the app it seems that the call to __fnAddData in the following code is causing the problem:
if (bUsePassedData) {
for (var i = 0, len = oInit.aaData.length; i < len; i++) {
_fnAddData(oSettings, oInit.aaData[i]);
}
} else if (oSettings.bDeferLoading ||
(oSettings.sAjaxSource === null && oSettings.ajax === null)) {
_fnAddTr(oSettings, $(oSettings.nTBody).children('tr'));
}
I was looking around for solutions and saw Nicholas Zakas' write up here and tons of other solutions that would work if the for loop wasn't inside of an if else if "block". When I tried, on my 1st attempt of many, to wrap it in a setTimeout function it of course didn't work because the 2nd part of the if else if resolves to true.
(oSettings.sAjaxSource === null && oSettings.ajax === null) // true
What is a good solution for this? Thanks in advance.
I think you might split up your function in 3 functions:
Before the if statement.
Processing the oInit.aaData
After the if statement
Here is the code split up in 3 functions:
function beforeIf(){
if (bUsePassedData) {
procesData(oSettings,oInit.aaData.concat());
} else if (oSettings.bDeferLoading ||
(oSettings.sAjaxSource === null && oSettings.ajax === null)) {
_fnAddTr(oSettings, $(oSettings.nTBody).children('tr'));
}
afterIF();
}
function processData(oSettings,arr){
//process in chuncks of 50;
// setTimeout takes a long time in IE
// it'll noticibly slow donw your script when
// only processing one item at the time
var tmp=arr.splice(0,50);
for (var i = 0, len = tmp.length; i < len; i++) {
_fnAddData(oSettings, tmp[i]);
}
if(arr.length!==0){
setTimeout(function(){
processData(oSettings,arr);
},0);
return;
}
afterIf();
}
function afterIf(){
//continue processing
}
Thanks #HMR. You helped to bring me closer to my goal. To solve the problem I worked my code down to this IIFE:
(function processData(oSettings, arr) {
var tmp = arr.splice(0, 50);
tickApp.$orders.dataTable().fnAddData(tmp);
if (arr.length !== 0) {
setTimeout(function () {
processData(oSettings, arr);
}, 0);
}
}(oSettings, oInit.aaData.concat()));
Instead of using the private _fnAddData function I opted for the DataTables public fnAddData (http://datatables.net/ref#fnAddData) function. This way I am able to push 50 rows at a time into the table which is stored in the tickApp.$orders object which I just a reference to my jQuery object that stores the table in memory:
tickApp.$orders = $('#orders');
In another part of my code. They way you had it it was still pushing 1 row at a time instead of the whole 50.
Thanks again.
If you are using ajax to fetch your data, you can override "fnServerData" in your datatables config object. This will allow you to fetch the data to be loaded and then process it however you want.
In my case, I have a generic datatables config object that I use for all my datatables. I override the default fnServerData function with one that passes rows to the datatable in sets of 200 using fnAddData and setTimeout to call the function again until all the data has been processed, finally I call fnDraw to draw the table.
var DEFAULT_CHUNK_SIZE = 200;
function feedDataToDataTableInChunks(startIndex, data, oSettings) {
var chunk = data.slice(startIndex, DEFAULT_CHUNK_SIZE);
oSettings.oInstance.fnAddData(chunk, false);
if((startIndex += DEFAULT_CHUNK_SIZE) < data.length) {
setTimeout(function () {
feedDataToDataTableInChunks(startIndex, data, oSettings);
});
} else {
oSettings.oApi._fnInitComplete(oSettings, data);
oSettings.oInstance.fnDraw();
}
}
var config = {fnServerData: function(){
oSettings.jqXHR = $.getJSON(sSource, aoData)
.done(function (result) {
feedDataToDataTableInChunks(0, result || [], oSettings);
});
}}
I am using datatables version 1.9.4
I'm going to try to explain this as best I can, please feel free to ask for clarifications as required.
Using IE10, CRM Online with RU12.
I am playing about with subgrids and getting them to refresh. Consider the following script, which I have nicked wholesale from MSDN (and wrapped in a try/catch block)
function start() {
try {
var controls = Xrm.Page.ui.controls.get(isSubGrid);
if (controls.length > 0) {
var subGridNames = "";
for (var i in controls) {
controls[i].refresh();
subGridNames += (" - " + controls[i].getName() + "\n");
}
alert("The following subgrids were refreshed: \n" + subGridNames);
}
else {
alert("There are no subgrid controls on the current form.");
}
}
catch (ex) {
alert(ex);
}
}
function isSubGrid (control)
{
return control.getControlType() == "subgrid";
}
Nothing special going on there - get all controls of type subgrid (this returns 10 elements as expected) and call refresh() on them.
However this is consistently failing on the first call to refresh().
The exception details is fairly straightforward
TypeError: Unable to get property 'Refresh' of undefined or null reference
Which suggests that the control[i] is null when called in the loop at this point here
for (var i in controls) {
controls[i].refresh();//error thrown here - suggests controls[i] is null
subGridNames += (" - " + controls[i].getName() + "\n");
}
However I can see that it isn't null (and has the method refresh as expected).
I can make it work by using setInterval
function waitAndThenRefresh(gridname) {
var grid = Xrm.Page.ui.controls.get(gridname);
var intervalId = setInterval(function () {
if (grid === null || grid._control === null || grid._control._element === null) {
return;
}
if (grid._control._element.readyState === 'complete') {
window.clearInterval(intervalId);
if (grid != null) {
grid.refresh();
}
}
}, 1000);
}
But that is pretty hideous, not to mention does not explain with the SDK call doesn't work as expected.
So I guess the question is: has anyone else seen this issue? Or can you replicate it on another instance? Am I missing something? There is nothing in the SDK that suggests you need to defer calling refresh until the inner control's readyState is complete?
The code block you are using,
for (var i in controls) {
controls[i].refresh();
subGridNames += (" - " + controls[i].getName() + "\n");
}
should be replaced with the following:
for (var i in controls) {
i.refresh();
subGridNames += (" - " + i.getName() + "\n");
}
or:
for (var i = 0; i < controls.length; i++) {
controls[i].refresh();
subGridNames += (" - " + controls[i].getName() + "\n");
}
You are getting the exception because controls[i] is undefined in your case, i being the control (the element of the array controls).
I asked a CRM-buddy of mine. He said that the issue depends on the new refreshment Engine. According to him, it's sort of a bug but not really. If I got it right, the refresh has been reengineered to accommodate the new perpetual saving functionality.
Hey I'm trying to return a message when there are no results for the users current query! i know i need to tap into the keyup event, but it looks like the plugin is using it
This question is really out of date, anyways I'm working with the new jQuery UI 1.8.16, autocomplete is now pretty different:http://jqueryui.com/demos/autocomplete/#default
Anyways if you're trying to the do the same thing as the question asks, there is no more parse function, as far as I know there is no function that is called with the search results.
The way I managed to pull this off is by overriding the autocomplete's filter function - Note: this will affect all your autocompletes
$.ui.autocomplete.filter = function(array, term) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
var aryMatches = $.grep( array, function(value) {
return matcher.test(value.label || value.value || value);
});
if (aryMatches.length == 0){
aryMatches.push({
label: '<span class="info" style="font-style: italic;">no match found</span>',
value: null
});
}
return aryMatches;
};
The function is slightly modified from the source, the grep call is the same, but if there are no results I add an object with a value of null, then I override the select calls to check for a null value.
This gives you an effect where if you keep typing and no matches are found you get the 'no matches found' item in the dropdown, which is pretty cool.
To override the select calls see jQuery UI Autocomplete disable Select & Close events
$(this).data('autocomplete').menu.options.selected = function(oEvent, ui){
if ($(ui.item).data('item.autocomplete').value != null){
//your code here - remember to call close on your autocomplete after
}
};
Since I use this on all my autocompletes on a page, make sure you check if value is null first! Before you try to reference keys that aren't there.
You could try supplying a parse option (function to handle data parsing) and do what you need when no results are returned to parse.
This example assumes you're getting back an array of JSON objects that contain FullName and Address attributes.
$('#search').autocomplete( {
dataType: "json",
parse: function(data) {
var array = new Array();
if (!data || data.length == 0) {
// handle no data case specially
}
else {
for (var i = 0; i < data.length; ++i) {
var datum = data[i];
array[array.length] = {
data: datum,
value: data.FullName + ' ' + data.Address,
result: data.DisplayName
};
}
}
return array;
}
});
I'm using the following code for the same purpose (the message is shown in the autocomplete list):
success: function(data, status, xhr){
if(!data.length){
var result = [
{
label: 'There are no matches for your query: ' + response.term,
value: response.term
}
];
response(result);
}
else{
// normal response
}
}
You can also utilize the "response" event to examine this. Simple but powerful. http://api.jqueryui.com/autocomplete/#event-response
response: function (event, ui) {
if (ui.content.length == 0) {
//Display an alert or something similar since there are no results
}
},