onchange event handler triggering twice - javascript

I have written the below jQuery functions to invoke filters on a datatable. But the "change" jQuery function seems to seems to invoke two times for a single click , hence, if i try to turn off one of the filter, it will immediately turn itself back on.
Function:
$(document).ready(function () {
var table = $('#example').DataTable({
rowReorder: {
selector: 'td:nth-child(2)'
},
responsive: true,
initComplete: function () {
var filters = $('div[id$=referalFilters]').find('input:checkbox');
filters.each(function () {
dict.push({
key: $(this).prop('name'),
value: $(this).prop('checked')
});
$(this).change(function () {
var currentName = $(this).prop('name');
for (var i = 0; i < dict.length; i++) {
if (dict[i].key == currentName) {
if (dict[i].value == true) {
dict[i].value = false;
} else {
dict[i].value = true;
}
}
}
for (var i = 0; i < dict.length; i++) {
if (dict[i].value == true) {
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
var currentStatus = data[3];
var found = false;
for (var i = 0; i < dict.length; i++) {
if (currentStatus == String(dict[i].key)) {
if (dict[i].value == true) {
found = true;
}
}
}
if (found) {
return true;
}
return false;
})
}
}
table.draw();
});
});
//this.api().columns().every( function () {
// var column = this;
//} );
}
});
});
I will appreciate, if someone could let me know what triggers the change function multiple times.
Here is the jsfiddle: https://jsfiddle.net/a81h11tc/ . In Js fiffle, the button seems to toggle but, the filtering does not happen.

As far as I can tell, your problem was an undefined variable, dict inside initComplete. After I added var dict = []; before var filters = ..., it worked like a charm. (My understanding is that you wanted the column Status to hide the rows that contain the value of the clicked filter. If you want the opposite, modify your code accordingly.) Here is a JSFiddle of the altered code, it should work the way you wanted.
TIP: By the way, remember to always check the browser's console as in most cases you can see the proper error messages for stuff like this (i.e. for your code it said RefereceError: dict is not defined, which was basically the only problem your code had).

Related

How to reset recursive function in JS?

I have a function to follow the multistep form:
function multistep() {
function disableBtn() {
continueButton.attr('disabled', true);
}
let select = $('.cf7mls_current_fs select');
let continueButton = $('.cf7mls_current_fs .cf7mls_next');
$('.wpcf7-submit').attr('disabled', false);
$('.preloader-form-wrap').hide();
$('.select-choosen').each(function () {
$(this).removeClass("select-choosen");
});
let selectArr = [];
let currentArr = [];
select.each(function (i, obj) {
currentArr.push($(obj).attr("name"));
})
continueButton.attr('disabled', true);
select.on('change', function () {
if ($(this).prop('selectedIndex') !== 0) {
if ($(this).hasClass("select-choosen") === false) {
$(this).addClass("select-choosen");
selectArr.push($(this).attr("name"));
}
} else {
$(this).removeClass("select-choosen");
selectArr.shift();
disableBtn();
}
console.log(selectArr.length, currentArr.length);
if (selectArr.length === currentArr.length) {
continueButton.attr('disabled', false);
$(continueButton).on("click", function () {
selectArr = [];
$('.preloader-form-wrap').show();
setTimeout(() => {
// if is everything ok, runs function again!
// but array from above will not be reset
multistep();
}, 1000);
});
}
});
}
And the goal of the function is to enable button if every step is filled, and this works, but I have a problem with
let selectArr = [];
When a function calls itself, the inner, variable from (array) above will not be reset? And function will be called multiple times.
First time call function:
$('.btn-open-form').click(function (e) {
....
multistep();
}
And reset again:
function closeForm() {
$(document).click(function (e) {
if ((e.target.className === "overlay-mobile form-active") == true
) {
multistep();
}
}
Function from above will be called on document init.
Well, let selectArr = []; if an array of choosing select items in one tab, and important is to have the length of the array above be equal to a number of displayed selected items.
If the length is the same, the button will be enabled, to jump on another tab.

Why is a function returning undefined and how to debug it?

I'm experimenting with closures and classes in data variables and in the example below I'm getting undefined even though I placed a console.log() right before the function returns the result and it isn't undefined. It seems to work if it isn't attached to an event handler. Can someone tell me why is this happening and if there is a way to spot where exactly does the error happen? When debugging it goes from the console log straight to the error and I don't see how that makes sense.
To trigger the error run the snippet and click on the names.
The same functions in $('#Individuals').data('functions') can be chained and work fine when called in IndividualsList(), but not from the event listener, then the result becomes undefined.
$(document).ready(function() {
var thisWindow = $('#Individuals');
var randomNames = ['Sonia Small', 'Kurt Archer', 'Reese Mullins', 'Vikram Rayner', 'Jethro Kaye', 'Suhail Randolph', 'Kaydon Crouch', 'Jamaal Elliott', 'Herman Atkins', 'Sia Best', 'Kory Gentry', 'Fallon Sawyer', 'Zayyan Hughes', 'Ayomide Byers', 'Emilia Key', 'Jaxson Guerrero', 'Gracey Frazier', 'Millie Mora', 'Akshay Parker', 'Margareta Emiliana'];
var generatedIndividuals = [];
function generateIndividual(name) {
return {
IndividualName: name
};
}
function IndividualsList(element) {
var list = [];
this.add = function(thisIndividual) {
$('#Individuals').data('functions').init(element, list).add(thisIndividual);
}
this.refresh = function() {
$('#Individuals').data('functions').init(element, list).refresh();
}
this.sort = function(order) {
$('#Individuals').data('functions').init(element, list).sort(order);
}
}
thisWindow.data('functions', (function() {
var element = $();
var list = [];
return {
add: function(thisIndividual) {
list.push(thisIndividual);
return thisWindow.data('functions');
},
init: function(thisElement, thisList) {
element = thisElement;
list = thisList;
return thisWindow.data('functions');
},
refresh: function() {
var thisList = element.html('');
for (let i = 0; i < list.length; i++) {
thisList.append(
'<div>' + list[i].IndividualName + '</div>'
);
}
return thisWindow.data('functions');
},
sort: function(order) {
list.sort(function(a, b) {
if (a.IndividualName < b.IndividualName) return -1 * order;
if (a.IndividualName > b.IndividualName) return 1 * order;
return 0;
});
console.log(thisWindow.data('functions'));
return thisWindow.data('functions');
}
}
})());
for (let i = 0; i < 20; i++) {
let nameNum = Math.floor(Math.random() * randomNames.length);
let thisClient = generateIndividual(randomNames[nameNum]);
generatedIndividuals.push(thisClient);
}
(function() {
var targetElement = thisWindow.find('div.individuals-list');
var targetData = {}
targetElement.data('individualsList', new IndividualsList(targetElement));
targetData = targetElement.data('individualsList');
for (let i = 0; i < generatedIndividuals.length; i++) {
targetData.add(generatedIndividuals[i]);
}
targetData.refresh();
})();
thisWindow.on('click', '.individuals-list', function() {
var thisElem = $(this);
var order = parseInt(thisElem.data('order'));
thisWindow.find('div.individuals-list').data('individualsList').sort(order).refresh();
thisElem.data('order', order * (-1));
});
});
.individuals-list {
border: 1px solid;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="Individuals">
<div class="individuals-list" data-order="1"></div>
</div>
https://jsfiddle.net/Kethus/ymgwrLhj/
You are referring to the wrong sort() function, hence call it incorrectly so it returns undefined. Then you call refresh() on undefined that was returned from sort. Here's why:
In your IFFE, you use .data() to set the data = new IndvidualsList on thisWindow.find('div.individuals-list')
This code:
thisWindow.find('div.individuals-list').data('individualsList')
Returns that instantiated IndividualsList Object:
IndividualsList = $1
add: function(thisIndividual)
refresh: function()
sort: function(fieldName, order)
IndividualsList Prototype
Note the sort() function's definition. Sort in this object requires two parameters, fieldName and order; yet you call sort() and only pass order;
This indicates your expectation for the sort() function is incorrect or the wrong sort function is being made available at that line of code (in the click handler).
How to debug
Set a breakpoint at line 132 of the provided JavaScript in the
Fiddle.
Click a name in the list.
While at the breakpoint (execution paused), move to the console and run this in the console:
thisWindow.find('div.individuals-list').data('individualsList')
Note the sort() function definition in the list of functions
Next, in the console run this statement:
thisWindow.find('div.individuals-list').data('individualsList').sort(order)
Note the return is undefined <-- This is the issue
The returned value doesn't transfer from the closure to the instance that called it, the class has to be changed like so:
function IndividualsList(element) {
var list = [];
this.add = function(thisIndividual) {
return $('#Individuals').data('functions').init(element, list).add(thisIndividual);
}
this.refresh = function() {
return $('#Individuals').data('functions').init(element, list).refresh();
}
this.sort = function(order) {
return $('#Individuals').data('functions').init(element, list).sort(order);
}
}
The breakpoint could have been in one of IndividualsList()'s methods so it can be noticed that the closure returns the desired object while the method does not. Different names for either the functions or methods would help to reinforce that they are separate.

Accessing object property on form submit (vanilla javascript)

I've decided to go back and relearn vanilla js since I've always relied on jquery.
I wanted to make some simple food order form.
My issue is that on my submit method, the "this" will refer to the form being submitted, rather than my placeOrder object.
When the order is submitted, I wanted to push it to my orders array which is inside cacheDom. Obviously, it can't use a push method on a form object.
can someone offer advice on how to go about this?
(function () {
var placeOrder = {
init: function() {
this.cacheDom();
this.bindEvents();
},
cacheDom : function() {
this.orderForm = document.getElementById('place-order-form');
this.elements = this.orderForm.elements;
this.orders = [];
},
bindEvents: function() {
if(this.orderForm.addEventListener){
this.orderForm.addEventListener("submit", this.submitOrder, false);
} else if (this.orderForm.attachEvent){
this.orderForm.attachEvent('onsubmit', this.submitOrder);
}
},
submitOrder: function(e) {
// this refers to the form being submitted
e.preventDefault();
var elements = this.elements;
var order = {};
for (var i = 0; i < elements.length; i++) {
if (elements[i].tagName !== 'BUTTON') {
if (elements[i].name === 'name') {
order.name = elements[i].value;
} else if (elements[i].name === 'food') {
order.food = elements[i].value;
}
}
}
//this.orders.push(order);
console.log(this);
}
};
placeOrder.init();
}());

How to use a variable returned by $.getJSON in another function

I have a javascript object with several properties and methods. I want to call the first method within the second, in order to get the default number of ingredients of a pizza and compare it with another value. However, I detect that no-value is present in the comparison of the second method.
Googling about this issue, I saw that I have to make a callback in the first method, but it didn't work for me. So, how can I be sure that the property obj.app.defaultIngredients will have a value returned by the JSON, when a 'click' event in the second method will occur? And, in that moment, I can compare the value as you also can see in the second method?
There is my (not working) code:
obj = {};
obj.app = {
defaultIngredients: '',
getDefaultIngredientsNumber: function() {
$.getJSON('/sites/all/json/pizza.json', function(data) {
var node = $('.node').attr('data-nid'),
node = 'node-' + node; // returns something like 'node-3'
$.each(data, function(key, val) {
// This returns an integer
obj.app.defaultIngredients = parseInt(data[node].general.default_ingredients);
});
}).done(function() {
return obj.app.defaultIngredients;
});
},
customAddToCart: function() {
$('#button').click(function(){
var defaultIngredients = obj.app.getDefaultIngredientsNumber();
var selectedIngredients = 0;
if defaultIngredients >= selectedIngredients) {
alert('Add some ingredients');
}
}
}
};
Some help with this will be very apreciated.
getDefaultIngredientsNumber: function(callback) {
$.getJSON('/sites/all/json/pizza.json', function(data) {
var node = $('.node').attr('data-nid'),
node = 'node-' + node; // returns something like 'node-3'
$.each(data, function(key, val) {
obj.app.defaultIngredients = parseInt(data[node].general.default_ingredients);
});
callback(obj.app.defaultIngredients)
})
},
customAddToCart: function() {
$('#button').click(function(){
obj.app.getDefaultIngredientsNumber(function(defaultIngredients) {
var selectedIngredients = 0;
if (defaultIngredients >= selectedIngredients) {
alert('Add some ingredients');
}
})
})
}
I may be missing something since seems too easy ..
});
}).done(function(data) {
return anotherfunction(data);
});
Or instead of this:
customAddToCart: function() {
$('#button').click(function(){
var defaultIngredients = obj.app.getDefaultIngredientsNumber();
var selectedIngredients = 0;
if defaultIngredients >= selectedIngredients) {
alert('Add some ingredients');
}
}
}
};
I would use this:
customAddToCart: function() {
$('#button').click(function(){
obj.app.getDefaultIngredientsNumber("alert");
}
}
};
And in the getDefaultIngredientsNumber I would check if alert is set, then perform the alert..

Check whether function has run fully before, based on variable

I have a function which "types" out a header title as though it is being typed on the screen.
The typer only starts typing once a particular section of my site is "active" or is seen on the screen.
At present, it takes the outputID aka the area where this text will be typed into. There are two instances of this function being run, each with different outputIDs - I only want the function to run once per outputID.
This is how the function is initially called.
<h2 id="typer-get-in-touch" class="typer" data-text="Get in Toche^^^^^ Touch"></h2>
if(anchorLink == 'contact'){
var outputID = $("#typer-get-in-touch");
textTyping(outputID);
}else if(anchorLink == 'expertise'){
var outputID = $("#typer-expertise");
textTyping(outputID);
}
This is the textTyping function
function textTyping(outputID){
$(outputID).show();
var textString = $(outputID).data("text");
var textArray = textString.split("");
var texttypeing = setInterval(
function() {
typeOutText(outputID,textArray);
}, 170);
function typeOutText(outputID,textArray) {
if (textArray[0] == "^"){
outputID.text(function(index, text){
return text.replace(/(\s+)?.$/, '');
});
textArray.shift();
}else {
if (textArray.length > 0) {
outputID.append(textArray.shift());
} else {
clearTimeout(texttypeing);
}
}
}
}
My issue at present is that the function runs multiple types, and continues to type each time the original anchorLink trigger is achieved. The result is that is writes the title many times e.g:
Get In TouchGet In TouchGet In Touch
Each time the section is navigated to, the typing starts again.
How can I run this function only ONCE per outputID? So once the outputID has been used, the function can no longer run for that data?
JSFiddle of non-working example: https://jsfiddle.net/qLez8zeq/
JSFiddle of mplungjan's solution: https://jsfiddle.net/qLez8zeq/1/
Change
function textTyping(outputID){
$(outputID).show();
var textString = $(outputID).data("text");
to
function textTyping(outputID){
var textString = $(outputID).data("text");
if (textString=="") return;
$(outputID).data("text","");
$(outputID).show();
FIDDLE
What you need to do is to bind the event handler for each ID and then unbind it after it's been triggered the first time. Since you're already using jQuery, you can use the "one" method to do exactly this for each outputID:
$( "#typer-get-in-touch" ).one( "click", function() {
textTyping(outputID);
});
I suppose you could store your processed outputIds into an array and then check if the given outputId is present in the array before starting?
Define your array, check for the existence, if not found, do code example:
var processedIds = [];
function textTyping(outputID) {
var foundItem = false;
for (var i = 0; i < processedIds.length; i++)
{
if (processedIds[i] == outputID) {
foundItem = true;
break;
}
}
if (!foundItem) {
//the rest of your code goes here
}
}
You can add some check at the beginning of your function:
var called = {};
function textTyping(outputID) {
if (called[outputID]) {
return;
}
called[outputID] = true;
// your code
}

Categories

Resources