Synchronize Ajax Calls and executeQueryAsync SharePoint JS CSOM - javascript

I have a problem synchronizing calls using Rest Api and JavaScript Object Model.
I'm currently working with Client Side Rendering to customize a view for a Document Library and add some functionalities in this custom UI.
I have a small collection of id's, and I'm looping through this collection and make some ajax calls with each of this items.
The results of this operation is to perform some tasks and to update my UI when all these operations are completed to refresh my UI and display some icons.
What I expect is to have 3 icons displayed only for my three first items.
The problem is that sometimes it displays all the icons, sometimes the two first... randomly.
I know that there is some problems with the synchronization of my executeQueryAsync calls, I've learned about jQuery Deferred object, I've tried to use them but without results.
Below you'll find screenshots of what I expect.
Expected :
https://onedrive.live.com/redir?resid=E2C3CC814469DA54!3070&authkey=!AEf_C0XGDwfuFRY&v=3&ithint=photo%2cpng
What would be the good way of using deferred ? Could anyone help ?
Thanks a lot
Elhmido
This is my main function for overriding the display :
(function () {
var accordionContext = {};
accordionContext.Templates = {};
// Be careful when add the header for the template, because it's will break the default list view render
accordionContext.Templates.Item = itemTemplate;
// Add OnPostRender event handler to add accordion click events and style
accordionContext.OnPreRender = [];
accordionContext.OnPreRender.push(function () {
$(function () {
IsCurrentUserMemberOfGroup("TEST Owners");
**$.when(IsUserApprover(arrayOfIDS).done(function () {
displayIcons();
}));**
});
});
accordionContext.OnPostRender = [];
accordionContext.OnPostRender.push(function () {
$(function () {
accordionOnPostRender();
fixColumns();
audit.relativeUrl = _spPageContextInfo.webAbsoluteUrl;
});
});
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(accordionContext);
})();
The function where I have the problem,
function IsUserApprover(auditTab) {
var dfd = $.Deferred();
audit.tabIcons = new Array();
for (var i = 0; i < auditTab.length; i++) {
var uri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/Lists/GetByTitle('Audit')/items?$select=UserID&$filter=ID eq " + auditTab[i] + "";
var call = $.ajax({
url: uri,
type: "GET",
dataType: "JSON",
async: false,
headers: {
"Accept": "application/json;odata=verbose"
}
});
call.done(function (data, status, jqxhr) {
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () {
var userId = data.d.results[0].UserID;
var context = SP.ClientContext.get_current();
var auditor = context.get_web().ensureUser(userId);
context.load(auditor);
//I think the problem is here because I don't know how to handle this call
context.executeQueryAsync(userLoaded, userFailed);
function userLoaded() {
var auditorId = auditor.get_id();
checkAuditorValidator(auditorId);
dfd.resolve();
}
function userFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
});
});
call.fail(function (jqxhr, status, error) {
alert(JSON.stringify(error))
dfd.reject();
});
}
return dfd.promise();
}
function checkAuditorValidator(auditorId) {
var uri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/Lists/GetByTitle('SecurityMgmt')/items?" +
"$select=Auditeur/ID,Validateur/ID" +
"&$expand=Auditeur/ID,Validateur/ID" +
"&$filter=(Auditeur/ID eq '" + auditorId + "') and (Validateur/ID eq '" + _spPageContextInfo.userId + "')";
var call = $.ajax({
url: uri,
type: "GET",
dataType: "JSON",
async: false,
headers: {
"Accept": "application/json;odata=verbose"
}
});
call.done(function (data, status, jqxhr) {
if (data.d.results.length > 0) {
if (audit.UserAdmin) {
audit.tabIcons.push(true);
}
}
else {
audit.tabIcons.push(false);
}
});
call.fail(function (jqxhr, status, error) {
alert(JSON.stringify(error))
});
}

Starting with Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), synchronous requests on the main thread have been deprecated due to the negative effects to the user experience.
You should avoid synchronous ajax calls...
I had the same problem and solved by adding an id during the custom rendering of the fields (items), on the postrender call my service asynchronously and according the result edit the OnPreRender page using the previously added ids.
I also did some hacks...e.g overriding the standard function RenderItemTemplate. Yes I know, it's not very clean but it works like a charm.

Related

Reduce number of ajax calls in instant search

I am trying to make an instant search drop down for my site. All work fine, except for this.
var timeOut;
$('#search input[name=\'search\']').on('keyup', function(e) {
// If enter - submit the search field
if (e.keyCode == 13) {
$('header input[name=\'search\']').parent().find('button').trigger('click');
}
// Call only when length is at least 2 and the key pressed is alphanumeric
else if ($('#search input[name=\'search\']').val().length>2 && ((e.keyCode>=65 && e.keyCode<=90) || (e.keyCode>=97 && e.keyCode<=122))) {
timeOut = null;
//alert(timeOut);
if (!timeOut) {
timeOut = setTimeout(function() {
$.ajax({
url: 'ajax.php',
type: 'post',
async: false,
data: 'ACTION=SEARCH&search='+$('#search input[name=\'search\']').val(),
dataType: 'json',
beforeSend: function() {
$('#loader-icon').show();
},
complete: function() {
$('#loader-icon').hide();
},
success: function(json) {
//$('.product-list-row').html(json);
$('#search-listing').html(json['html']);
},
error: function(xhr, ajaxOptions, thrownError) {
alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
}
});
timeOut = null;
}, 500);
}
}
});
Problem 1: My script ends up making too many calls to the server, for some reason setTimeOut isn't working as I expected it to. Ajax call should only be made when the user has done typing or something like that and not at every key press.
Problem 2: For some reason when I type fast the input bar doesn't get edited. The ajax however works, but with the last textual input.
Instead of trying to tackle this with setTimeout, you can abort the previous call using the jqXHR object returned by the AJAX. It is more clean and simple to use this. Remove async: false, too.
var timeOut;
var xhr;
$('#search input[name=\'search\']').on('keyup', function(e) {
// If enter - submit the search field
if (e.keyCode == 13) {
$('header input[name=\'search\']').parent().find('button').trigger('click');
}
// Call only when length is at least 2 and the key pressed is alphanumeric
else if ($('#search input[name=\'search\']').val().length>2 && ((e.keyCode>=65 && e.keyCode<=90) || (e.keyCode>=97 && e.keyCode<=122))) {
if(xhr && xhr.readyState != 4){
xhr.abort();
}
xhr = $.ajax({
url: 'ajax.php',
type: 'post',
data: 'ACTION=SEARCH&search='+$('#search input[name=\'search\']').val(),
dataType: 'json',
beforeSend: function() {
$('#loader-icon').show();
},
complete: function() {
$('#loader-icon').hide();
},
success: function(json) {
//$('.product-list-row').html(json);
$('#search-listing').html(json['html']);
},
error: function(xhr, ajaxOptions, thrownError) {
alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
}
});
}
});
The right solution is combination of both, abort running request, if new one should be made, and also, tackle firing of the new request. You can use underscore library, which has nice function for that, called debounce (see http://underscorejs.org/#debounce) and your code should looks like this:
// This is your xhr, each request save into this
// variable, in order to be able to abort it if needed
var xhr;
// Wrap your event handler using the debounce function
$("#search").on("keyup", _.debounce(function(e) {
// Abort running request
if(xhr) {
xhr.abort();
xhr = null;
}
// Store the new request
xhr = $.ajax({
// Search for the term $(this).val()
});
},500));
There is no need to fire the search for each keyup, but only when user stopped typing - debounce will do it for you. And there is no need to handle previous results if the request should be made.

jQuery order of events - unable to figure out proper solution

I'm trying to create an if/else statement within my jQuery code that changes the ajax URL and success function that gets fired off:
if($($this.context).find('img').hasClass('scheduler-img')) {
url = window.location.origin + "/recipes/" + planned_recipe_id +'/update_recipe'
} else {
url = window.location.origin + "/recipes/" + recipe_id +'/make_recipe'
success = successfulRecipeAdd(closest_date, image_url, recipe_id);
}
$.ajax({
url: url,
method: 'GET',
dataType: 'json',
data: {
planned_for: planned_for,
meal_type: meal_type
},
success: (function() {
success
}),
error: (function(XMLHttpRequest, textStatus, errorThrown) {
alert("Status: " + textStatus); alert("Error: " + errorThrown);
})
});
I'm using this for a jQuery draggable and sortable table, so when I'm dragging and dropping the first item, it doesn't work because it tries creating the 'success' variable with an empty dataset. However, it does work for every subsequent drag/drop after.
function successfulRecipeAdd(closest_date, image_url, recipe_id) {
$.get( window.location.origin + "/recipes/get_recipes", function(data) {
console.log(data);
var planned_recipe_id = $(data).last()[0][0].id;
$(closest_date).append("<img src='"+image_url+"'class='scheduler-img col-md-12' id='"+ recipe_id +"' data-planned-id='"+planned_recipe_id+"'>");
});
}
I'm having a lot of trouble figuring out a way to write this that would allow me to create variables within the if/else statement, then fire off the ajax call using variables, rather than having two ajax calls within the if/else statement.

jQuery how to run ajax with hover option and use .ajaxerror to addClass removeClass individual div

I have a page that displays a calendar into with a attribute "data-date" that as a date like: "11/29/2014" and I need to check server if there's a log file for that date and change css for that div on mouse hover.
So far i get this code:
$(document).ready(function() {
var lab = $( ".label.day" ).hover(
function() {
dd = $(this).attr("data-date").split("/");
ddo = $(this).attr("data-date");
dday = ("0" + (dd[1])).slice(-2);
dmonth = ("0" + (dd[0])).slice(-2);
dyear = dd[2];
url = "logs/log." + dyear + "-" + dmonth + "-" + dday;
$.ajax({
type: 'HEAD',
url: url,
error: function(xhr, status, error) {
console.log(status)
},
success: function(xhr, status, error) {
console.log(status)
}
});
$(document).ajaxError(function(event, jqxhr, settings, thrownError) {
console.log(thrownError)
if ( thrownError == "Not Found" ) {
$(".label.day").filter(ddo).addClass( "error" );
}
});
}, function() {
$(".label.day").filter(ddo).addClass( "noerror" );
}
);
});
<div data-date="1/16/2014" class="label day " original-title="Quinta" style="display: block;">16</div>
I can't change the class for the individual , without the .filter it changes all and .attr("data-date") doesn't work also.
There are several issues with your script:
You are not passing any data to the URL specified, via the data object in the $.ajax() function. Also, you need to specify to expected type of data (dataType) received (is it in JSON, plain text or otherwise?).
Use deferred objects and promises to check the status of the AJAX call
Use context, i.e. $(this), in your hover function so you can dictate which elements to modify without doing any filtering.
HEAD is an invalid value for the type object in the AJAX call. You should use POST or GET instead, depending on how the destination script is written to handle incoming data. Here's a good guide to deciding between the two.
Listen to mouseover instead of hover, as you are adding classes based on the status of the AJAX request, not the toggling between mouseover and mouseout events.
Use var when declaring functions to contain/restrict them within the function's scope, otherwise you risk polluting global variables :)
An improved code is as follow, but might not work unless you furnish more details on how you're checking the server for information.
$(document).ready(function() {
$('.label.day').on('mouseover', function() {
// Cache $(this)
var $t = $(this);
// Reset class
$t.removeClass('error noerror');
// Declare variables within function scope (not global)
var dd = $t.attr("data-date").split("/"),
ddo = $t.attr("data-date"),
dday = ("0" + (dd[1])).slice(-2),
dmonth = ("0" + (dd[0])).slice(-2),
dyear = dd[2],
url = "logs/log." + dyear + "-" + dmonth + "-" + dday;
// Perform AJAX call
var $check = $.ajax({
type: 'POST', //or "GET"
url: url;
});
// jQuery deferred object
$check.fail(function(jqXHR, textStatus) {
// If AJAX request failed and returned an error
console.log(textStatus);
$t.addClass('error');
}).done(function(data) {
// If AJAX request is successful
console.log(data);
$t.addClass('noerror');
});
});
});

Identify 400-request with ajax

I'm having the following problem:
I am grabbing tweets from twitter, using their API. Whenever I've hit the limit of requests, it is returning me a 400 (Bad request) - reply.
Now, how can I find out whether a 400-reply was returned? The callback 'Error' isn't triggered.
$.ajax({
url: 'http://api.twitter.com/1/statuses/user_timeline/' + Followed[Index] + '.json?count=' + Tweetlimit + '&include_rts=true',
dataType: 'jsonp',
success: function (json) {
$.each(json, function (index, tweet) {
var date = Date.parse(tweet.created_at);
Tweets.created_at = date.toString('hh.mm.ss - dd/MM/yy');
Tweets.created_as_date = date;
Tweets.push(tweet);
})
CompletedUsers = CompletedUsers + 1;
},
error: function () {
alert("Error");
},
});
success is called when request succeeds. error is called when request fails. So, in your case, request succeeded and success is called. Now, if you want to respond to specific codes, you should follow this example in addition to your code:
$.ajax({
statusCode: {
404: function() {
alert("page not found");
}
}
});

jQuery - proper way to create plugin

I'm trying to convert some of my code to reusable plugins.
Many times I'm filling selects with dynamic options that comes from Ajax request.
I've managed to create something like this:
$.fn.fillSelect = function fillSelect(options) {
var self = this;
options = $.extend({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "Data.asmx/StatusList",
dataType: "json",
async: true,
success: function(data) {
var list = "";
$.each(data.d, function(i) {
list += '<option value='
+ data.d[i].ID + '>'
+ data.d[i].Nazwa
+ '</option>';
});
self.filter("select").each(function() {
$(this).empty();
$(this).append(list);
//use selectmenu
if ($.ui.selectmenu) $(this).selectmenu();
});
}//,
//error: function(result) {
// alert("Error loading data!");
//}
}, options);
$.ajax(options);
return self;
}
Idea behind this is to be able to fill multiple selects with the same data multiple times with one request.
I have default options for Ajax request, but I would like to add some more options to it.
For example:
clear - fill determinate if I want new options to replace existing ones or append.
Also I would like to add some callbacks to my function that I could pass as parameters.
If for example server request will fail I would like to specify a function that will be called after this error occurs - for example to show alert or disable my selects.
My question is how should I change my plugin or which pattern (boilerplate) I should use?
Every boilerplate I found is for creating plugins that will 'stay' inside selected item, so that it is possible to call method of that plugin later.
I need a simple plugin that will allow user to fill select and then it will end it's life :)
My main idea is to do only one request to server for all elements.
Here is jsfiddle demo: http://jsfiddle.net/JC7vX/2/
A basic plugin can be built as follows
(function ($){
$.fn.yourPlugin = function (options){
// this ensures that function chaining can continue
return this.each(function (){
// merge defaults and user defined options
var params = $.extend({},defaultOptions,options);
// your plugin code
});
}
/* these options will help define the standard functionality of the plugin,
* and also serves as a nice reference
*/
var defaultOptions = {
someProperty : true
}
})(jQuery)
There are other things that you can do to extend the functionality of your plugin and give public methods that retain the context, but that would be overkill for your example.
This is my version of answer http://jsfiddle.net/Misiu/ncWEw/
My plugin looks like this:
(function($) {
$.fn.ajaxSelect = function(options) {
var $this = this;
//options
var settings = $.extend({}, defaults, options);
//disable select
if ($.ui.selectmenu && settings.selectmenu && settings.disableOnLoad) {
$this.selectmenu('disable');
}
//ajax call
$.ajax({
type: settings.type,
contentType: settings.contentType,
url: settings.url,
dataType: settings.dataType,
data: settings.data
}).done(function(data) {
var n = data.d || data;
var list = "";
$.each(n, function(i) {
list += '<option value=' + n[i].Id + '>' + n[i].Nazwa + '</option>';
});
$this.filter("select").each(function() {
$(this).empty();
$(this).append(list);
if ($.ui.selectmenu && settings.selectmenu) {
$this.selectmenu();
}
settings.success.call(this);
});
}).fail(function() {
settings.error.call(this);
});
return this;
};
var defaults = {
type: "POST",
contentType: "application/json; charset=utf-8",
url: '/echo/json/',
dataType: 'json',
data: null,
async: true,
selectmenu: true,
disableOnLoad: true,
success: function() {},
error: function() {}
};
})(jQuery);
I understand that it is very simple, but it has all functionality that I needed:
-You can select multiple elements at one time
-It filters only selects from Your selected items
-It makes only one request to server
-First it builds option string and then append it instead of adding items in loop
-You can specify 2 callbacks: one for error and second for success
And it is my first plugin, so there is much places for improvements.
As always comments and hints are welcome!

Categories

Resources