I'm stuck with the following problem:
I want a function to create DIV tags to hold images and a second function to create the IMG tag and insert them in the DIVs created before. Works well with "alert messages". However without alerts, the second function runs before the first finished loading the DIV tags.
I think I need some kind of callback function worked in, but I have no clue how to do this.
Here is the code:
function imageLoader ()
{
for (i=1;i<=5;i++)
{
checkPath = 'images/pic'i+'.png';
$.ajax({
url: checkPath,
type:'HEAD',
success:
function() {
//create DIVs t hold images
$('.tools').append("<div class='tooling'></div>");
}
});
}
// after creating DIVs, call function to create <img> tags
appendix();
}
Thanks for helping out.
Regards,
frequent
Since, as Kos pointed out, you can't rely on the calls to complete in order, you'll have to get a little tricky to ensure that all your ajax calls have been completed. Try something like this:
function imageLoader() {
var ajaxCounter = 0;
for (i = 1; i <= 5; i++) {
checkPath = 'images/pic'
i + '.png';
$.ajax({
url: checkPath,
type: 'HEAD',
success: function() {
//create DIVs t hold images
$('.tools').append("<div class='tooling'></div>");
ajaxCounter++;
if (ajaxCounter == 5) {
// after creating DIVs, call function to create <img> tags
appendix();
}
}
});
}
}
That should check to make sure you have the required number of ajax successes before executing the appendix() call.
I can't tell exactly what you're asking, but it looks like maybe you need to move your appendix() call into the success callback. Remember that the success callback may not be executed before the appendix call in the code you presented.
function() {
//create DIVs t hold images
$('.tools').append("<div class='tooling'></div>");
appendix();
}
Of course, you might not want this called 5 times inside your for loop. Here's one way to make it call after the last success occurs, perhaps not the cleanest:
for (i=1;i<=5;i++)
{
if(i == 5) {
var succ = function() {
//create DIVs t hold images
$('.tools').append("<div class='tooling'></div>");
appendix();
};
} else {
var succ = function() {
//create DIVs t hold images
$('.tools').append("<div class='tooling'></div>");
};
}
checkPath = 'images/pic'i+'.png';
$.ajax({
url: checkPath,
type:'HEAD',
success: succ
});
}
a) Create a variable to count how many times the success function callback has executed and run appendix only when this count equals the number of your ajax calls,
b) Register the $.ajaxComplete callback.
The elegant way to do this is to use $.when:
function imageLoader(
$.when.apply($, $.map(new Array(5), function (e, i) {
var checkPath = 'images/pic' + (i + 1) +'.png',
d = $.Deferred();
$.ajax({
url: checkPath,
type:'HEAD',
success: function () {
d.resolve($('<div class="tooling">')
.append($('<img>').attr(src, checkPath)));
},
error: function () { d.resolve(null); }
});
return d.promise();
})
.then(function () {
$('.tools').append(Array.prototype.slice(arguments));
});
}
You could set async to false in $.ajax so it blocks execution.
Or, you could check i in the success callback, and if its 5, call appendix().
function imageLoader ()
{
for (i=1;i<=5;i++)
{
checkPath = 'images/pic'i+'.png';
$.ajax({
url: checkPath,
type:'HEAD',
success:
function() {
//create DIVs t hold images
$('.tools').append("<div class='tooling'></div>",
function(){
appendix();
});
}
});
}
}
Related
I have two ajax functions that one is recursively working at loop and other is working when click event invoked. I tested both of the functions that are able to work properly. But when i start recursive function button event is not invoked.
Function that works on click event GET Content from ActionResult (MVC)
function UpdateRequests(url, state, id, cell)
{
$.ajax({
type: "GET",
url: url + id,
success: function (result) {
if (result == "OK")
{
cell.fadeOut("normal", function () {
$(this).html(state);
}).fadeIn();
}
else if(result == "DELETE" || result == "CANCEL")
{
cell.parent().fadeOut("normal", function () {
$(this).remove();
});
}
else
{
$(".modal-body").html(result);
$("#myModal").modal();
}
},
error: function () {
alert("Something went wrong");
}
});
}
Recursive function GET partial view from ActionResult (MVC)
function RefreshRequests()
{
if (isListPage())
{
var id = PageId();
var url = "/Home/List/" + id;
}
else
{
var url = "/Home/Index";
}
$.ajax({
type: "GET",
url: url,
success: function (data) {
$(".ajaxRefresh").html(data);
EditPageHeader();
},
complete: function () {
setTimeout(RefreshRequests, 2000);
}
});
}
Click event
$(".tblRequests").on("click", button, function (e) {
e.preventDefault();
var id = $(this).data("id");
var currentRow = $(this).closest("tr");
var cell = currentRow.children('td.requestState');
UpdateRequests(url, state, id, cell);
});
Main
$(document).ready(function () {
EditPageHeader();
RefreshRequests();
ButtonEvent(".btnPrepare", "/Home/Prepare/", "PREPARING");
ButtonEvent(".btnApprove", "/Home/Approve/", "APPROVED");
ButtonEvent(".btnCancel", "/Home/Cancel/", "CANCELED");
RefreshRequests();
});
Assumptions:
The Ajax Calls bring you data that end up as HTML elements in the modal body.
These new elements added above need to respond to the click event (the one that doesn't work correctly right now)
If the above 2 are true, than what is happening is you are binding events to existing elements (if any) and new elements (coming from API response) are not bound to the click event.
The statement
$(".tblRequests").on("click", button, function (e) {
...
})
needs to be executed every time new elements are added to the body. A better approach for this would be to define the event handler as an individual method and then bind it to each new element.
var clickHandler = function (e) {
e.preventDefault();
var id = $(this).data("id");
var currentRow = $(this).closest("tr");
var cell = currentRow.children('td.requestState');
UpdateRequests(url, state, id, cell);
}
// Then for each new record that you add
$(".tblRequests").on("click", button, clickHandler);
It would be helpful if you can try to explain what exactly you are trying to achieve.
Problem is that the $(this) will hold all elements of the selector. And will also now with one as it will be triggered one time and then never again. Also as can be seen from here, delegate events should be at the closest static element that will contain the dynamic elements.
function ButtonEvent(button, url, state)
{
$("body").on("click", button, function (e) {
e.preventDefault();
var button = e.target;
var id = $(button).data("id");
var currentRow = $(button).closest("tr");
var cell = currentRow.children('td.requestState');
UpdateRequests(url, state, id, cell);
});
}
Attached Event handler to callback like :
$("someSelector").on('click',callBackHandler);
function callBackHandler(){
//Some code
$.ajax({
//Ajax call with success methods
})
}
My success method is manipulating some object properties. Since ajax is involved, it will not wait for the completion and next event handling will start. How can I make sure next click event handling starts only when previous handling is done.
Cannot think of a way of using deferred manually on this because I am triggering event manually on base of some condition in for loop (Not a clean style of coding, but has no other option in particular use case).
$('someSelector').trigger('click');
$("someSelector").on('click', function(event) {
event.preventDefault();
var urAjax = $.ajax({
// Ajax Call here...
});
urAjax.always(function(response) {
$.when( callBackHandler() ).done(function() {
// Handle your ajax response in here!
});
});
});
function callBackHandler() {
// Do Stuff
}
callBackHandler function will fire, and when it's done, your ajax response for .always will fire directly after that. This allows for your ajax to load while the callBackHandler function is running also, but doesn't fire the response until after the function is done! Hopefully I'm understanding what you are asking for here.
You can see an example jsfiddle located here: https://jsfiddle.net/e39oyk8q/11/
Try clicking the submit button multiple times before the AJAX request is finished, you will notice that it will loop over and over again the total amount of clicks you give it on the Submit button. You can see this by the amount of times the Alert box pops up, and also, it adds 100 to the len (that gets outputted on the page) during each call to the callBackHandler function. So, I do believe this is what you asked for.
And, ofcourse, you can still use: $('someSelector').trigger('click');
EDIT
Another approach is to return a json object that can be used within the ajax call or wherever you need it within the click event, like so:
$("someSelector").on('click', function(event) {
event.preventDefault();
var myfunc = callBackHandler();
var urAjax = $.ajax({
type: 'POST',
url: myfunc['url'],
data: myfunc['data']
});
urAjax.always(function(response) {
$.when( myfunc ).done(function() {
console.log(myfunc['time']);
// Handle your ajax response in here!
});
});
});
function callBackHandler() {
var timestamp = new Date().getTime();
return { url: 'my_ajax_post_url', data: {data1: 'testing', data2: 'testing2'}, time: timestamp }
}
fiddle example here: https://jsfiddle.net/e39oyk8q/15/
You can remove the binding on the call of function and bind again when the ajax is done().
function callBackHandler(){
$("someSelector").off('click');
//Some code
$.ajax({
//Ajax call with success methods
}).done(function(){
$("someSelector").on('click',callBackHandler);
})
}
var requestDataButton = document.querySelector('.js-request-data');
var displayDataBox = document.querySelector('.js-display-data');
var displayOperationsBox = document.querySelector('.js-display-operations');
var displayNumberOfRequestsToDo = document.querySelector('.js-display-number-of-requests-to-do');
var isAjaxCallInProgress = false;
var numberOfAjaxRequestsToDo = 0;
var requestUrl = 'http://jsonplaceholder.typicode.com/photos';
requestDataButton.addEventListener('click', handleClick);
function handleClick() {
displayNumberOfRequestsToDo.innerText = numberOfAjaxRequestsToDo;
numberOfAjaxRequestsToDo++;
if(!isAjaxCallInProgress) {
isAjaxCallInProgress = true;
requestData();
}
}
function handleResponse(data) {
displayData(data);
displayOperationsBox.innerHTML = displayOperationsBox.innerHTML + 'request handled <br>';
numberOfAjaxRequestsToDo--;
displayNumberOfRequestsToDo.innerText = numberOfAjaxRequestsToDo;
if(numberOfAjaxRequestsToDo) {
requestData();
} else {
isAjaxCallInProgress = false;
}
}
function displayData(data) {
displayDataBox.textContent = displayDataBox.textContent + JSON.stringify(data[0]);
}
function requestData() {
var request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
var data = JSON.parse(this.response);
handleResponse(data);
} else {
// on error
}
};
request.onerror = function() {
// There was a connection error of some sort
};
request.send();
displayOperationsBox.innerHTML = displayOperationsBox.innerHTML + 'request sent <br>';
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="js-request-data">Request Data</button>
<div>
<h2>Requests wainting in a queue to be sent:
<span class="js-display-number-of-requests-to-do">
0
</span>
</h2>
</div>
<div>
<h2>Operations:</h2>
<div class="js-display-operations"></div>
</div>
<div>
<h2>Response Data:</h2>
<div class="js-display-data"></div>
</div>
edit
here's a utility function that takes similar arguments as $.ajax, and then returns an augmented $.ajax function that keeps an eye on its previous calls and waits for all preceding requests to finish before dispatching another ajax call (are fired sequentially):
function sequentialize(requestData, responseHandler) {
let inProgress = false;
let deferredRequests = 0;
const makeRequest = () => {
$.ajax(requestData).then(processManager);
};
const processManager = (data) => {
responseHandler(data);
if (deferredRequests) {
deferredRequests--;
makeRequest();
} else {
inProgress = false;
}
};
return function () {
if (!inProgress) {
inProgress = true;
makeRequest();
} else {
deferredRequests++;
}
};
}
Is it possible to open the window after the execution of the script expandNextLevel()?
I'm asking this because I don't want to let the client see the expand/collapse animation but just the treeview collapsed.
This is my code.
<script type="text/javascript">
$(function () {
$(".k-gantt").click(function () {
expandNextLevel();
var windowWidget = $("#window");
windowWidget.data("kendoWindow").open().center();
$.ajax({
type: 'GET',
url: '/Act/load',
contentType: 'application/json; charset=utf-8',
success: function (result) {
},
error: function (err, result) {
alert("Error" + err.responseText);
}
});
function expandNextLevel()
{
setTimeout(function () {
var treeview = $("#treeview").data("kendoTreeView");
var b = $('.k-item .k-plus').length;
treeview.expand(".k-item");
treeview.trigger('dataBound');
if (b > 0) {
expandNextLevel();
collapseNextLevel();
}
}
, 200);
};
function collapseNextLevel()
{
setTimeout(function () {
var treeview = $("#treeview").data("kendoTreeView");
var b = $('.k-item .k-minus').length;
treeview.collapse(".k-item");
treeview.trigger('dataBound');
if (b > 0) {
collapseNextLevel();
}
}
, 200);
};
</script>
Regards
try this
$.when(expandNextLevel()).done(function(){
/// show window
});
docs https://api.jquery.com/jquery.when/
I think the fastest way to do something like this is put everything in a hidden div, wich you will then show when you're done with the code execution.
You could also put a visible div with a rotating icon while the code is being executed, and hide it when you show the main content to make the users know something is happening.
EDIT:
I made a slight modification to the expand function, that should let me know when it's done executing the recursion, by adding an index I increment everytime. At the end of the function there is a code that will be executed only when the index is equal to one, wich means the first instance of the function is done executing.
<script type="text/javascript">
$(function () {
$(".k-gantt").click(function () {
expandNextLevel(0);
var windowWidget = $("#window");
windowWidget.data("kendoWindow").open().center();
$.ajax({
type: 'GET',
url: '/Act/load',
contentType: 'application/json; charset=utf-8',
success: function (result) {
},
error: function (err, result) {
alert("Error" + err.responseText);
}
});
function expandNextLevel(var i)
{
i++;
setTimeout(function () {
var treeview = $("#treeview").data("kendoTreeView");
var b = $('.k-item .k-plus').length;
treeview.expand(".k-item");
treeview.trigger('dataBound');
if (b > 0) {
expandNextLevel(i);
collapseNextLevel();
}
if (i == 1)
{
$.("#maincontent").show();
}
}
, 200);
};
function collapseNextLevel()
{
setTimeout(function () {
var treeview = $("#treeview").data("kendoTreeView");
var b = $('.k-item .k-minus').length;
treeview.collapse(".k-item");
treeview.trigger('dataBound');
if (b > 0) {
collapseNextLevel();
}
}
, 200);
};
</script>
You should put you content inside a div
<div id="maincontent" style="display:none;">
/*your content*/
</div>
I didn't test it but it should work :)
There is a better way to do this with jQuery.when, jQuery.done and promises, but I'm not confident I can give you a working sample since I never used those methods
How to can i run the script asd.js on data returned from ajax?
HTML
<script type="text/javascript" src="asd.js"></script>
<div class="one">
<ul id="qwe"></ul>
</div>
JavaScript
$(document).ready(function () {
$('#search').click(function () {
$.ajax({
type: "POST",
url: 'abc.php',
data: {cid: cid},
success: function (data) {
$("#qwe").html(data);
}
});
});
});
The data returned from success ajax is:
<li> <a href="images3.jpg"><img src="images/a4.jpg"/>
<span>
<div class="title"><img src="Images/a5.jpg"/></div>
</span>
</a>
</li>
I assume asd.js adds some event hooks or something that you need to apply again to the HTML elements have come back from your AJAX call. So--wrap the contents of asd.js in a function, call that function once at the end of asd.js, then call that function again after you set $('#qwe').html(data).
If you are adding event hooks or working with the DOM in some other way, the initialization functions in asd.js should be called from the $(document).ready(...) function (otherwise you have potential race conditions going on here.)
If you can't alter asd.js, then things are going to be tougher. Work out which functions are important and call them. In the worst case scenario you could try dynamically removing and adding the script tag to cause it to re-run (but that's horrible).
Edit: ok, I wanted to illustrate this more clearly. I had assumed asd.js was simply a list of operations that executed immediately upon loading the script, e.g.
// asd.js: wiring up a bunch of events
document.getElementById('...').onclick = function() { doSomething(); };
If that was the case, you would wrap it in a function like this:
// asd.js: wiring up a bunch of events
function init()
{
document.getElementById('...').onclick = function() { doSomething(); };
}
init();
Then also call init() from your response handler:
success: function (data) {
$("#qwe").html(data);
init();
}
From your comments it seems that asd.js might look more like this:
// asd.js: wiring up a bunch of events
jQuery(function() {
document.getElementById('...').onclick = function() { doSomething(); };
});
If this is the case, you would do something like this:
// asd.js: wiring up a bunch of events
jQuery(function() { init(); });
function init()
{
document.getElementById('...').onclick = function() { doSomething(); };
}
The jQuery line can be shortened:
jQuery(init);
sorry, but i cant to do edit.
* this is what asd.js cotain (if is help): *
var yoxviewPath = getYoxviewPath();
var cssLink = top.document.createElement("link");
cssLink.setAttribute("rel", "Stylesheet");
cssLink.setAttribute("type", "text/css");
cssLink.setAttribute("href", yoxviewPath + "yoxview.css");
top.document.getElementsByTagName("head")[0].appendChild(cssLink);
function LoadScript(url)
{
document.write( '<scr' + 'ipt type="text/javascript" src="' + url + '"><\/scr' + 'ipt>' ) ;
}
var jQueryIsLoaded = typeof jQuery != "undefined";
if (!jQueryIsLoaded)
LoadScript("http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js");
LoadScript(yoxviewPath + "jquery.yoxview-2.2.min.js");
function getYoxviewPath()
{
var scripts = document.getElementsByTagName("script");
var regex = /(.*\/)yoxview-init/i;
for(var i=0; i<scripts.length; i++)
{
var currentScriptSrc = scripts[i].src;
if (currentScriptSrc.match(regex))
return currentScriptSrc.match(regex)[1];
}
return null;
}
// Remove the next line's comment to apply yoxview without knowing jQuery to all containers with class 'yoxview':
LoadScript(yoxviewPath + "yoxview-nojquery.js");
i get this error, and i don't know how can be solved. I read this link before.
EDIT:1
index.php
<script type="text/javascript">
$(document).ready(function() {
$("#customForm").submit(function() {
var formdata = $("#customForm").serializeArray();
$.ajax({
url: "sent.php",
type: "post",
dataType: "json",
data: formdata,
success: function(data) {
switch (data.livre) {
case 'tags':
$("#msgbox2").fadeTo(200, 0.1, function() {
$(this).html('Empty tags').fadeTo(900, 1);
});
break;
default:
$("#msgbox2").fadeTo(200, 0.1, function() {
$(this).html('Update').fadeTo(900, 1, function() {
$('#conteudo').load('dojo/test_Slider.php');
});
});
break;
}
}
});
return false;
});
});
</script>
test_slider.php
<script type="text/javascript">
var slider = [];
for (i = 0; i < 5; i++) {
slider[i] = (
function(i) {
return function() {
var node = dojo.byId("input"+[i]);
var n = dojo.byId("valores"+[i]);
var rulesNode = document.createElement('div'+[i]);
node.appendChild(rulesNode);
var sliderRules = new dijit.form.HorizontalRule({
count:11,
style:{height:"4px"}
},rulesNode);
var labels = new dijit.form.HorizontalRuleLabels({
style:{height:"1em",fontSize:"75%"},
},n);
var theSlider = new dijit.form.HorizontalSlider({
value:5,
onChange: function(){
console.log(arguments);
},
name:"input"+[i],
onChange:function(val){ dojo.byId('value'+[i]).value = dojo.number.format(1/val,{places:4})},
style:{height:"165px"},
minimum:1,
maximum:9,
}
},node);
theSlider.startup();
sliderRules.startup();
}
})(i);
dojo.addOnLoad(slider[i]);
}
</script>
Problem: First click in submit btn all works well, 5 sliders are imported. Second click, an update is supposed, but i get this message:
Tried to register widget with id==valores0 but that id is already registered
[Demo video]2
Just to add on to #missingo's answer and #Kevin's comment. You could walk through the existing dijits by looking in the registry:
var i = i || 0; // Cache this at the end of your loop
dijit.registry.map(function (widget) {
if (+widget.id.replace(/^[^\d]+/, '') < i) {
widget.destroyRecursive();
}
});
/*
Your loop fixed as described in missingno's answer.
*/
You fell in the age-old trap of making function closures inside a for loop. By the time addOnLoad fires and the sliders are created, i will be equal to 2 and both sliders will try to use the same DOM nodes (something that is not allowed).
You need to make sure that you give a fresh copy of i for everyone. The following is a quick fix:
for(i=0; i<2; i++){
(function(i){
slider[i] = ...
//everything inside here remains the same
//except that they now use their own i from the wrapper function
//instead of sharing the i from outside.
}(i));
}
Dijit stores all active widgets in the dijit.registry, and uses id's as unique qualifiers. You can't create dijits with same id.
Need to clean dojo.registry before create a new slider dijits. Add this code before declare dijit on test_slider.php
dijit.registry["input"+ [i]].destroyRecursive();
can you assign any number ID like ID generated by 10 digit random number or something with datetime combination so id will never be same.