I'm developing an application where I load content using ajax calls. But in a certain situation, I get an ajax call on a loop but it ends in random iterations. Below is the sequential code from the a double click event to the final ajax call.
//I use the double click event to fire the function
$('.item-container').on('click', '.item', function(e) {
var posX = e.pageX;
var posY = e.pageY;
var element = $(this);
var that = this;
setTimeout(function() {
var dblclick = parseInt($(that).data('double'), 10);
if (dblclick > 0) {
$(that).data('double', dblclick-1);
} else {
showToolTip(posX,posY,element);
}
}, 300);
}).on('dblclick', '.item', function(e) {
$(this).data('double', 2);
addItemsToBill(this);
});
function addItemsToBill(selectedItem) {
var element = null;
$(".bill-item-list").each(function() {
if ($(this).css("display") == "block") {
element = $(this);
return;
}
});
if (element == null) {
getSystemMessage(BILL_SELECT_VALIDATION_MSG);
return;
}
if ($('#open-tab .bill-list').find(".bill-item-list").length == 0
&& $(element).find('.bill-item').length == 0) {
getSystemMessage(BILL_SELECT_VALIDATION_MSG);
return;
}
var bill = $('.sample-elements').find('.bill-item');
var clone = bill.clone();
var itemId = $(selectedItem).attr('id');
var billId = $(element).parent().attr('id');
if ($(selectedItem).attr('isopen') == '1') {
$('.open-item-popup-overlay').lightbox_me( {
centered : true,
destroyOnClose : false,
});
$('.itmRate').val('');
$('#itmAmt').html('0.00');
$('.itemQty').text('1');
$('.itm-cancel').click(function() {
$('.open-item-popup-overlay').trigger('close');
});
//when the first run goes it's fine, bt after that, the looping starts from here. I double checked whether there is another code segment which fires this but there are none.
$('.itm-ok').click(function(){
if ($('.itmRate').val() == "") {
getSystemMessage(AMOUNT_INPUT);
return;
}
//This function gets iterated multiple times if I run the same event for multiple times.
//The function is only used in this two locations and no any other places.
billItemDrop(itemId, billId, clone, selectedItem, null,element, 1, true);
return false;
})
} else {
billItemDrop(itemId, billId, clone, selectedItem, null, element, 0,true);
}
}
function billItemDrop(itemId, billId, billItemClone, doubleClickedItem,draggableHelper, catchElement, isOpen, isDoubleClick) {
var openItemQuantity = stringToDouble($('.itemQty').val());
var openItemRate = stringToDouble($('.itmRate').val());
var openItemAmount = openItemQuantity * openItemRate;
var ajaxObject = 'itemId=' + itemId + '&billId=' + billId + '&qty='
+ openItemQuantity + '&rate=' + openItemRate + '&openItem='
+ isOpen;
ajaxCall(ADD_ITEM_TO_BILL,AJAX_POST,ajaxObject,function(response) {
var responseObject = response.billDetail;
var billTotal = responseObject.billTotal;
var total = response.billDetail.rate * response.billDetail.qty;
// Add Items to the bill failed
if (response.status != "success") {
getSystemMessage(ADD_ITEM_TO_PRINTED_BILL);
return;
}
billItemClone.attr('itemId', responseObject.itemId);
billItemClone.attr('billDetailId', responseObject.billDetailId);
billItemClone.find('.bill-item-name').text(
responseObject.itemName);
billItemClone.find('.bill-item-rate span').text(
responseObject.rate.toFixed(2));
billItemClone.find('.bill-item-amount').text(
total.toFixed(2));
billItemClone.find('.bill-item-qty span').text(responseObject.qty);
if (responseObject.kotBotNo != null) {
billItemClone.find('.kot-bot-num').text(
responseObject.kotBotNo);
}
billItemClone.draggable( {
revert : false,
addClasses : false,
zIndex : 1,
containment : "window",
opacity : 0.5,
cursor : "move",
scroll : false,
helper : function() {
return $(this).clone().appendTo('body').show();
}
});
if (isDoubleClick == true
&& (doubleClickedItem != undefined
|| doubleClickedItem != null || doubelClickedItem != "")) {
billItemClone.find('.bill-item-img img').attr('src',
$(doubleClickedItem).css('background-image'));
} else if (isDoubleClick == false
&& (draggableHelper != undefined
|| drdraggableHelper != null || draggableHelper != "")) {
billItemClone.find('.bill-item-img img').attr('src',
draggableHelper.css('background-image'));
}
if (catchElement.height() > 300) {
catchElement.css('overflow-y', 'scroll');
catchElement.css('height', '320px');
}
if (catchElement.find('.bill-item').length == 0) {
billItemClone.insertBefore(
catchElement.find('.item-drop-point')).hide()
.fadeIn("slow");
} else {
billItemClone.insertBefore(
catchElement.find('.bill-item').first()).hide()
.fadeIn("slow");
}
catchElement.parent().find('.amount')
.html(billTotal.toFixed(2));
if (draggableHelper != undefined || draggableHelper != null) {
draggableHelper.hide('fade', function() {
draggableHelper.remove()
});
}
$('.item-drop-point').removeClass('item-drop-hover');
},false);
}
/**
*
* Function for ajax calls - url : webservice url - type : ajax call type - data :
* data that needs to be passed - callback : function that needs to be executed
*
* when ajax call succeed
*
* #param url
* #param type
* #param data
* #param callback
*/
function ajaxCall(url, type, data, callback,async){
// Assign default value to async if its not specified
if (async===undefined) async = true;
$.ajax({
url: url,
type: type,
data: data,
dataType : "json",
cache: false,
beforeSend: function(x){
if(x && x.overrideMimeType){
x.overrideMimeType("application/json;charset=UTF-8");
}
},
async : async,
success:callback
});
}
The function gets iterated so the ajax call gets iterated too. I used break points to track the iteration but from the point of the click event I couldn't locate where it get's triggered after the first click event.
Is there anything wrong in the code which makes the function iterate. Please help.
Every time you call addItemsToBill(this); you are adding new event handlers to $('.itm-ok')
Try removing the call to billItemDrop(itemId, billId, clone, selectedItem, null,element, 1, true); and replacing it with a console.log('Test' If you find that multiple console logs are being made every time you click the issue is that every click is being handled by multiple handlers. If that is the case and you really want to use this structure you will have to unbind you click handler before rebinding it.
$('.itm-ok').off('click')
$('.itm-ok').on('click',function(){
if ($('.itmRate').val() == "") {
getSystemMessage(AMOUNT_INPUT);
return;
}
//This function gets iterated multiple times if I run the same event for multiple times.
//The function is only used in this two locations and no any other places.
billItemDrop(itemId, billId, clone, selectedItem, null,element, 1, true);
return false;
})
Related
on page load I run this function to hide long quoted posts into a clickable link to prevent huge comments on my site:
function hide_long_quotes()
{
$('.comments .comment_quote').each(function(i)
{
var actual_text = $(this).text();
var content = $(this).outerHTML();
if(actual_text.length > showChar)
{
var cite = $(this).find('cite span.username').first().text();
var cite_link = '';
if (cite.length > 0)
{
cite_link = 'from ' + cite;
}
var html = '<span class="morecontent">' + content + '</span>' + moretext + cite_link + '<br />';
$(this).replaceWith(html);
}
if (i+1 === quote_count) // this will be executed at the end of the loop
{
// deal with being linked to a comment, so we can put the window to the correct scroll position, since it will be different due to hidden quotes making the page smaller
if(window.location.hash)
{
var hash = window.location.hash.substring(1); //Puts hash in variable, and removes the # character
if (hash.indexOf("r") >= 0)
{
$('#'+hash)[0].scrollIntoView();
}
}
}
});
}
The problem is that when I reload ".comments" through ajax/load the above function no longer works:
function paginate_comments(page, article_id)
{
var url = "/includes/ajax/post_comment.php";
var current_url = window.location.href;
var host = window.location.host;
if(current_url.indexOf(host + '/admin.php?module=reviewqueue') != -1 || current_url.indexOf(host + '/admin.php?module=articles&view=Submitted') != -1 || current_url.indexOf(host + '/admin.php?module=articles&view=Submitted') != -1)
{
var area = 'admin';
}
else
{
var area = 'normal';
}
$('.comments').load(url, {'type':'reload', 'article_id': article_id, 'page': page, 'area': area}, function()
{
$(".lb-container").show();
$('.box.comments').get(0).scrollIntoView();
hide_long_quotes();
});
}
Not sure why it doesn't work, as the function is being called in the completed callback part of the load function?
What about giving the element collection to that function:
function hide_long_quotes(var comments = false)
{ /* for the case that you don't pass a collection to the function, maybe in the first call when the page is loaded */
comments = comments ? comments : $('.comments .comment_quote');
comments.each(function(i)
{
...
}
function paginate_comments(page, article_id)
{
...
$('.comments').load(
url,
{
'type':'reload',
'article_id': article_id,
'page': page,
'area': area
},
function()
{
$(".lb-container").show();
$('.box.comments').get(0).scrollIntoView();
hide_long_quotes($('.comments .comment_quote'));
}
);
}
I found this code that's setting a KeyUp event on certain input objects. I can't modify this code, it's part of Drupal's core. You can see there is a function call in there called populatePopup() (line 103) and I want to be able call it but I don't do much with JS script so I'm hoping someone can clue me in on how to call it.
Here's the code that sets up the events:
(function ($) {
/**
* Attaches the autocomplete behavior to all required fields.
*/
Drupal.behaviors.autocomplete = {
attach: function (context, settings) {
var acdb = [];
$('input.autocomplete', context).once('autocomplete', function () {
var uri = this.value;
if (!acdb[uri]) {
acdb[uri] = new Drupal.ACDB(uri);
}
var $input = $('#' + this.id.substr(0, this.id.length - 13))
.attr('autocomplete', 'OFF')
.attr('aria-autocomplete', 'list');
$($input[0].form).submit(Drupal.autocompleteSubmit);
$input.parent()
.attr('role', 'application')
.append($('<span class="element-invisible" aria-live="assertive"></span>')
.attr('id', $input.attr('id') + '-autocomplete-aria-live')
);
new Drupal.jsAC($input, acdb[uri]);
});
}
};
/**
* Prevents the form from submitting if the suggestions popup is open
* and closes the suggestions popup when doing so.
*/
Drupal.autocompleteSubmit = function () {
return $('#autocomplete').each(function () {
this.owner.hidePopup();
}).length == 0;
};
/**
* An AutoComplete object.
*/
Drupal.jsAC = function ($input, db) {
var ac = this;
this.input = $input[0];
this.ariaLive = $('#' + this.input.id + '-autocomplete-aria-live');
this.db = db;
$input
.keydown(function (event) { return ac.onkeydown(this, event); })
.keyup(function (event) { ac.onkeyup(this, event); })
.blur(function () { ac.hidePopup(); ac.db.cancel(); });
};
/**
* Handler for the "keydown" event.
*/
Drupal.jsAC.prototype.onkeydown = function (input, e) {
if (!e) {
e = window.event;
}
switch (e.keyCode) {
case 40: // down arrow.
this.selectDown();
return false;
case 38: // up arrow.
this.selectUp();
return false;
default: // All other keys.
return true;
}
};
/**
* Handler for the "keyup" event.
*/
Drupal.jsAC.prototype.onkeyup = function (input, e) {
if (!e) {
e = window.event;
}
switch (e.keyCode) {
case 16: // Shift.
case 17: // Ctrl.
case 18: // Alt.
case 20: // Caps lock.
case 33: // Page up.
case 34: // Page down.
case 35: // End.
case 36: // Home.
case 37: // Left arrow.
case 38: // Up arrow.
case 39: // Right arrow.
case 40: // Down arrow.
return true;
case 9: // Tab.
case 13: // Enter.
case 27: // Esc.
this.hidePopup(e.keyCode);
return true;
default: // All other keys.
if (input.value.length > 0 && !input.readOnly) {
this.populatePopup();
}
else {
this.hidePopup(e.keyCode);
}
return true;
}
};
/**
* Puts the currently highlighted suggestion into the autocomplete field.
*/
Drupal.jsAC.prototype.select = function (node) {
this.input.value = $(node).data('autocompleteValue');
$(this.input).trigger('autocompleteSelect', [node]);
};
/**
* Highlights the next suggestion.
*/
Drupal.jsAC.prototype.selectDown = function () {
if (this.selected && this.selected.nextSibling) {
this.highlight(this.selected.nextSibling);
}
else if (this.popup) {
var lis = $('li', this.popup);
if (lis.length > 0) {
this.highlight(lis.get(0));
}
}
};
/**
* Highlights the previous suggestion.
*/
Drupal.jsAC.prototype.selectUp = function () {
if (this.selected && this.selected.previousSibling) {
this.highlight(this.selected.previousSibling);
}
};
/**
* Highlights a suggestion.
*/
Drupal.jsAC.prototype.highlight = function (node) {
if (this.selected) {
$(this.selected).removeClass('selected');
}
$(node).addClass('selected');
this.selected = node;
$(this.ariaLive).html($(this.selected).html());
};
/**
* Unhighlights a suggestion.
*/
Drupal.jsAC.prototype.unhighlight = function (node) {
$(node).removeClass('selected');
this.selected = false;
$(this.ariaLive).empty();
};
/**
* Hides the autocomplete suggestions.
*/
Drupal.jsAC.prototype.hidePopup = function (keycode) {
// Select item if the right key or mousebutton was pressed.
if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
this.select(this.selected);
}
// Hide popup.
var popup = this.popup;
if (popup) {
this.popup = null;
$(popup).fadeOut('fast', function () { $(popup).remove(); });
}
this.selected = false;
$(this.ariaLive).empty();
};
/**
* Positions the suggestions popup and starts a search.
*/
Drupal.jsAC.prototype.populatePopup = function () {
var $input = $(this.input);
var position = $input.position();
// Show popup.
if (this.popup) {
$(this.popup).remove();
}
this.selected = false;
this.popup = $('<div id="autocomplete"></div>')[0];
this.popup.owner = this;
$(this.popup).css({
top: parseInt(position.top + this.input.offsetHeight, 10) + 'px',
left: parseInt(position.left, 10) + 'px',
width: $input.innerWidth() + 'px',
display: 'none'
});
$input.before(this.popup);
// Do search.
this.db.owner = this;
this.db.search(this.input.value);
};
/**
* Fills the suggestion popup with any matches received.
*/
Drupal.jsAC.prototype.found = function (matches) {
// If no value in the textfield, do not show the popup.
if (!this.input.value.length) {
return false;
}
// Prepare matches.
var ul = $('<ul></ul>');
var ac = this;
for (key in matches) {
$('<li></li>')
.html($('<div></div>').html(matches[key]))
.mousedown(function () { ac.hidePopup(this); })
.mouseover(function () { ac.highlight(this); })
.mouseout(function () { ac.unhighlight(this); })
.data('autocompleteValue', key)
.appendTo(ul);
}
// Show popup with matches, if any.
if (this.popup) {
if (ul.children().length) {
$(this.popup).empty().append(ul).show();
$(this.ariaLive).html(Drupal.t('Autocomplete popup'));
}
else {
$(this.popup).css({ visibility: 'hidden' });
this.hidePopup();
}
}
};
Drupal.jsAC.prototype.setStatus = function (status) {
switch (status) {
case 'begin':
$(this.input).addClass('throbbing');
$(this.ariaLive).html(Drupal.t('Searching for matches...'));
break;
case 'cancel':
case 'error':
case 'found':
$(this.input).removeClass('throbbing');
break;
}
};
/**
* An AutoComplete DataBase object.
*/
Drupal.ACDB = function (uri) {
this.uri = uri;
this.delay = 300;
this.cache = {};
};
/**
* Performs a cached and delayed search.
*/
Drupal.ACDB.prototype.search = function (searchString) {
var db = this;
this.searchString = searchString;
// See if this string needs to be searched for anyway. The pattern ../ is
// stripped since it may be misinterpreted by the browser.
searchString = searchString.replace(/^\s+|\.{2,}\/|\s+$/g, '');
// Skip empty search strings, or search strings ending with a comma, since
// that is the separator between search terms.
if (searchString.length <= 0 ||
searchString.charAt(searchString.length - 1) == ',') {
return;
}
// See if this key has been searched for before.
if (this.cache[searchString]) {
return this.owner.found(this.cache[searchString]);
}
// Initiate delayed search.
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(function () {
db.owner.setStatus('begin');
// Ajax GET request for autocompletion. We use Drupal.encodePath instead of
// encodeURIComponent to allow autocomplete search terms to contain slashes.
$.ajax({
type: 'GET',
url: db.uri + '/' + Drupal.encodePath(searchString),
dataType: 'json',
success: function (matches) {
if (typeof matches.status == 'undefined' || matches.status != 0) {
db.cache[searchString] = matches;
// Verify if these are still the matches the user wants to see.
if (db.searchString == searchString) {
db.owner.found(matches);
}
db.owner.setStatus('found');
}
},
error: function (xmlhttp) {
Drupal.displayAjaxError(Drupal.ajaxError(xmlhttp, db.uri));
}
});
}, this.delay);
};
/**
* Cancels the current autocomplete request.
*/
Drupal.ACDB.prototype.cancel = function () {
if (this.owner) this.owner.setStatus('cancel');
if (this.timer) clearTimeout(this.timer);
this.searchString = '';
};
})(jQuery);
You can see that on line 103 there's this line this.populatePopup();. I would like to call this function directly from my code elsewhere.
So far I have got:
(function ($) {
Drupal.behaviors.tweaks = {
attach: function (context, settings) {
$('#object-1', context).keyup(function () {
setTimeout(function() {
var $input = $('#object-2');
$input.populatePopup();
}, 250);
});
}
};
})(jQuery);
Which all seem to work except for the final two lines with the variable $input. Which are producing the error:
Uncaught TypeError: $input.populatePopup is not a function
attach http://<DOMAIN>/sites/all/modules/custom/ats/js/dynamic-views-filters.js
So clearly I am not getting to the function but I'm not sure how to do so. Any help much appreciated.
Ultimately what it came down to is I was trying to do things in a harder way than needed and #2pha helped with me finding a better direction with his note about event.target.
Rather than try to get the functions attached the object and its events I just triggered the keyup and keydown events on the object and the functions got triggered without me having to directly call them.
Im trying to get an animation on mouseenter and stop animation on mouseleave, but I'm onlny getting one cycle instead of a full cycle on mouseenter. Must be doing something wrong.
https://jsfiddle.net/b6kezaku/7/ here is a fragment.
var arrowUpFirst = document.getElementsByClassName('turn_up_svg')[0]
var arrowUpTransform = window.getComputedStyle(arrowUpFirst, null).getPropertyValue('transform')
var arrowUpTransformScale = arrowUpTransform.split(',')
var arrowUpValue = typeof arrowUpTransformScale[2] === 'string'
console.log(arrowUpValue)
var arrowNumberUpValue = Number(arrowUpValue) * 100
console.log(typeof arrowNumberUpValue === 'number')
var a = 'start'
var c = 'stop'
var fullCycle = 100
function up_svg(b) {
if (a == b) {
requestAnimationFrame(up_svg)
}
if (c == b) {
cancelAnimationFrame(up_svg)
}
if (arrowNumberUpValue <= 100 && arrowNumberUpValue >= 50) {
fullCycle--
fullCycle <= 75 ? arrowNumberUpValue++ : arrowNumberUpValue--
//document.querySelectorAll('p')[1].textContent = arrowNumberUpValue
var trans = "scale(" + (arrowNumberUpValue / 100).toPrecision(2) + ") rotate(-90deg)"
//document.querySelectorAll('p')[3].textContent = (arrowNumberUpValue/100).toPrecision(2)
arrowUpFirst.style.transform = trans
if (fullCycle < 52) {
fullCycle = 100
arrowNumberUpValue = 100
//document.querySelectorAll('p')[2].textContent = arrowNumberUpValue
}
}
}
//FirstFace.onmouseenter = up_svg
FirstFace.addEventListener('mouseenter', function() {
up_svg('start')
}, false)
//FirstFace.addEventListener('mouseenter', up_svg,false)
//FirstFace.addEventListener('mouseleave', up_svg.bind(null,'stop'),false)
FirstFace.addEventListener('mouseleave', function() {
up_svg('stop')
}, false)
The problem I see is that when requestAnimationFrame invokes up_svg, it doesn't pass a value for b, so you don't trigger a second requestAnimationFrame.
Consider doing the following:
function loopAnimation() {
requestAnimationFame(loop)
// run animation
}
function startAnimation() {
// cancel to prevent duplication
cancelAnimationFrame(loopAnimation)
requestAnimationFame(loopAnimation)
}
function stopAnimation() {
cancelAnimationFrame(loopAnimation)
}
FirstFace.addEventListener('mouseenter', startAnimation,false)
FirstFace.addEventListener('mouseexit', stopAnimation,false)
In this case, up_svg gets split into three functions. One is dedicated to carrying out the animation and preserving it, while the other two can start or stop the loop.
It seems that when requestAnimationFrame(up_svg); runs the callback, it is passed a numerical value, so because b is neither 'start' or 'stop', it doesn't refresh again or ask again to requestAnimationFrame(up_svg);. Here is a little fix so that the function launches itself again:
in declarations:
var running = false;
in the function:
if(a == b){
running = true;
cancelAnimationFrame(up_svg); //just a precaution
}
if(c == b){
running = false;
cancelAnimationFrame(up_svg)
}else{
if(running){
requestAnimationFrame(up_svg);
}
}
fiddle: https://jsfiddle.net/b6kezaku/10/
I recently learned a few ajax methods in jQuery and I stumbled upon this code and I cannot figure out what it's doing.
ajax: {
init: function() {
app.ajax._ctr = 1;
app.ajax._preloaded = {};
app.ajax.inProgress = false;
app.dom.$body.on('click', 'a:not(.no-ajax):not([hreflang])', function(e) {
var $a = $(e.currentTarget);
var href = $a.attr('href');
// what is indexOf
if (href.indexOf('mailto:') < 0
&& href != '#'
&& ((href.indexOf(app.baseUrl) >= 0 && href.indexOf(app.baseUrl) <= 7) || href.indexOf('://') < 0)
&& !$a.parents('.mejs-container').length) {
e.preventDefault();
app.ajax.loadPage(href, true, $a.attr('data-ajax-resolver'));
}
});
// below is load event
app.dom.$window.on('load', function() {
app.ajax.currentStateCtr = app.ajax._ctr;
history.replaceState({ internal: true, ctr: app.ajax._ctr++ }, null, document.URL);
});
// below is popstate event
app.dom.$window.on('popstate', function(e) {
if (e.originalEvent.state && e.originalEvent.state.internal) {
app.ajax.loadPage(location.href, false, null, e.originalEvent.state.ctr < app.ajax.currentStateCtr);
app.ajax.currentStateCtr = e.originalEvent.state.ctr;
}
});
}, // end of init
getResolver: function(resolverName) {
if (resolverName !== undefined && app.ajax._resolvers[resolverName] !== undefined) {
return app.ajax._resolvers[resolverName];
}
return app.ajax._defaultResolver;
},
I notice that it declares it within an App object with an empty DOM object. I also understand there are a few jQuery events being used here, but I don't understand exactly how the 'popstate' event and the getResolver function is being used with data.
Here in the below code iam calling ajax on scroll.
But this is calling ajax multiple times. To restrict this i added setTimeout function and flag (i.e. isActive) still it is calling two times.
Please help me where iam going wrong.
Thanks in advance
var isActive = false;
var sIndex =12;
var myflag = '1';
var offSet = 12;
var timeout;
jQuery(window).scroll(function () {
if(typeof timeout == "number") {
window.clearTimeout(timeout);
delete timeout;
}
timeout = window.setTimeout( check, 500);
});
function check(){
var cat = $(".mi-selected").attr('id');
var tecID = $("#technologyID").val();
var notSameInd = $("#notSameInd").val();
var sIndex =$("#startInd").val();
if (!isActive && ($(window).scrollTop() + $(window).height() == $(document).height()) && (sIndex !== notSameInd) ) {
var isActive = true;
jQuery.ajax({
type: "POST",
url: 'http://some.com/responcePortfolio.php',
data: {
tecID:tecID,
cat:cat,
startIndex:sIndex,
offset:offSet,
count_now:count_now
},
success: function (result) {
if(result !== ''){
jQuery("#LoaderImage").hide();
jQuery("#portfolioList").append(result);
$("#notSameInd").val(sIndex);
sIndex = parseInt(sIndex) + parseInt(offSet);
$("#startInd").val(sIndex);
}
else{
jQuery("#LoaderImage").hide();
}
isActive = false;
},
error: function (error) {
//alert(error);
}
});
}
}