Why is one button overriding my other button even with unique classes? - javascript

I am new to javascript/jquery and have been stuck on this problem for a while. So I have two buttons, a clear button that will clear all forms in a row of a table and a reset button that holds all initial values for each row of the table.
The issue: So currently when I run the script the reset button will keep overriding the clear button. Meaning when I click on clear it will also act as a reset instead of clearing the row. I tried creating unique classes (.clear_button, .reset_button) to be called as you see here. I find it hard to troubleshoot javascript especially being new to it so why is this happening?
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
$(".clear_button").click(function(){
function my_clearFunction(i) {
document.getElementById("id_form-" + (i - 1) + "-Name").value = " ";
document.getElementById("id_form-" + (i - 1) + "-Start").value = "";
document.getElementById("id_form-" + (i - 1) + "-End").value = "";
document.getElementById("id_form-" + (i - 1) + "-Advanced").value = " ";
}
});
$(".reset_button").ready(function(){
$('#reset :input:not([type="button"])').each(function(idx, ele) {
ele.dataset.initvalue = ele.value;
});
$('#reset [type="button"]').on('click', function(e) {
// reset current row............
$(this).closest('tr').find(':input:not([type="button"])').each(function(idx, ele) {
// restore the initial value
ele.value = ele.dataset.initvalue;
})
});
});
});
</script>
Note: I understand the code is not uniform, for example, my clear button logic was not written in jquery. Sorry I couldn't attach a jsfiddle, this project is relatively pretty big and I use django to import my forms so it was difficult to set up. So any input would be greatly appreciated since I have been stuck on this for quite some time and can't seem to get it. It's also worth mentioning my input tags for the buttons so here they are.
<input type="button" class="clear_button" onclick="my_clearFunction({{ forloop.counter }})" value=" x ">
<input type="button" class="reset_button" value=" x ">

when I click on clear it will also act as a reset instead of clearing the row.
Your reset listener is declared as
$('#reset [type="button"]').on('click', function(e) {
...
})
It seems the #reset element contains both clear and reset buttons, so clicking in either will restore the initial values.
The clear button, also, has two handlers of its own. There is one declared in code which in turn declares a function (that isn't called in the handler itself) and an inline handler that tries to invoke said function. That shouldn't work, for it isn't visible from the global scope.
Instead of
$(button).on('click',(e)=>{
function doSomethingWith(i) {
...
}
doSomethingWith(e.target.id);
})
If should be
function doSomethingWith(i) {
...
}
$(document).ready(function(){
$(button).on('click',(e)=>{
doSomethingWith(e.target.id);
});
});
then it would be visible for the handler but also on the global scope, so you could call it using the inline "onclick"
<button onclick="doSomethingWith({{ forloop.counter }})" >
However, you shouldn't have an inline handler if you're also declaring one in JS. Since you're dealing with the reset button in code, stick to that approach for the clear button too.
Now, the approach you follow to clear a row needs for you to know the row relative index, and the inputs on each row, for which you compute their respective ids. Whereas, when it comes to reset the original values, you don't need to know anything:
$('.reset_button').on('click', function(e) {
// reset current row............
$(this).closest('tr').find(':input:not([type="button"])').each(function(idx, ele) {
// restore the initial value
ele.value = ele.dataset.initvalue;
})
});
The button needs only know it's inside the same <tr> element as other inputs whose value needs to be restored. It doesn't care about the index, the IDs, not even what inputs are in place, as long as they aren't buttons.
You should do the same to clear the values:
$('.clear_button').on('click', function(e) {
// reset current row............
$(this).closest('tr').find(':input:not([type="button"])').each(function(idx, ele) {
ele.value = "";
});
});
When it comes to storing the original value I'm also used to resort to jQuery.data . Anyway, for this use case you can perfectly stick to
input.dataset.initialValue = input.value
Instead of
$(input).data('initialValue',input.value)
As long as you keep in mind these approaches are not interchangeable. You can't set the initialValue with dataset then get it with jQuery.data or the other way around.
function randomTime() {
return [
Number(100 * Math.random() % 12).toFixed(0).padStart(2, '0'),
Number(100 * Math.random() % 60).toFixed(0).padStart(2, '0')
].join(':');
}
function addFormRow(player_name = 'N/A') {
let tr = $('<tr class="form_row">'),
name = $('<input type="text" name="name" class="name">'),
start = $('<input type="time" name="start" class="start">'),
end = $('<input type="time" name="end" class="end">'),
advanced = $('<input type="number" name="advanced" class="advanced">'),
clear = $('<button class="clear_button">Clear</button>'),
reset = $('<button class="reset_button">Reset</button>');
name.val(player_name);
start.val(randomTime());
advanced.val(parseInt(Math.random() * 100, 10))
end.val(randomTime());
for (let input of [name, start, end, advanced, clear, reset]) {
$('<td>').append(input).appendTo(tr);
}
tr.appendTo('#forms tbody');
}
addFormRow('player one');
addFormRow('player two');
addFormRow('player three');
$(document).ready(function() {
$('#forms tbody tr').each((index,tr)=>{
$(tr).find('input').each((idx,input)=>{
$(input).data('initialValue',$(input).val());
});
})
$(".clear_button").on('click', (e) => {
let $this = $(e.target),
tr = $this.closest('tr');
tr.find('input').each((index, input) => {
input.value = '';
});
});
$(".reset_button").on('click', (e) => {
let $this = $(e.target),
tr = $this.closest('tr');
tr.find('input').each((index, input) => {
$(input).val($(input).data('initialValue'));
});
});
});
.advanced {
width: 4em;
}
.name {
width: 9em;
}
.start,
.end {
width: 5.5em;
}
.form_row input {
height: 1.1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="forms">
<thead>
<tr>
<th>name</th>
<th>start</th>
<th>end</th>
<th>advance</th>
<th colspan="2">actions</th>
</tr>
</thead>
<tbody>
</tbody>
</table>

Your $(".clear_button").click() handler isn't doing anything. It defines a local function, but never calls it.
Instead of trying to call the function from onclick(), add a data attribute to buttons holding the index. Then the click handler can get this attribute and use it to find all the related elements that it needs to clear.
$(".clear_button").click(function() {
var i = $(this).data("rel-id");
document.getElementById("id_form-" + (i - 1) + "-Name").value = " ";
document.getElementById("id_form-" + (i - 1) + "-Start").value = "";
document.getElementById("id_form-" + (i - 1) + "-End").value = "";
document.getElementById("id_form-" + (i - 1) + "-Advanced").value = " ";
});
<input type="button" class="clear_button" data-rel-id="{{ forloop.counter }}" value=" x ">

Related

text search using jquery filter delay during backspace

I am using jquery filter to search for names in a big list of names. It works just fine with good speed when I type in the input field. But when I press backspace button to clear the search text there is a delay of more then 4s when there are about 2 or 3 characters left.
I have made a demo to explain my problem
<input type="text" class="search">
<div class="list"></div>
<script>
// Get the list div
let $list = $('.list');
// Form a list of numbers from 1 to 8000
let listHtml = ''
for (let i = 0; i < 8000; i++) {
listHtml += `<div class="list_item"><div class="list_item_value c${i}">${i}</div></div>`;
}
$list.html(listHtml);
// Get all the list items
$listItem = $list.find('.list_item');
$('.search').on('keyup', function(e) {
// Get the search text
let text = $(this).val();
$listItem.filter(function() {
$(this).toggle($(this).find(`.list_item_value`).text().includes(text));
});
});
</script>
I have simplified my problem with this demo by replacing the text search by number search.
One way to fix this problem would be by canceling the ongoing jquery filter process when a backspace is pressed. But I don't know how to do that. Please someone help me fix it.
Consider the following example.
$(function() {
function makeItems(n, t) {
for (var i = 1; i < n; i++) {
$(t).append("<div class='list_item'><div class='list_item_value " + i + "'>" + i + "</div>");
}
}
var $list = $('.list');
makeItems(8000, $list);
$('.search').keyup(function(e) {
var text = $(this).val().toLowerCase();
$(".filtered").removeClass("filtered");
var search = (text.length > 0);
if (search) {
$(".list_item", $list).filter(function(index) {
return $(this).text().indexOf(text) === -1;
}).addClass("filtered");
}
});
});
.filtered {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" class="search">
<div class="list"></div>
Not too much different from your code. Some reduction of code. When the Search is done, there is a condition to ensure it's not done on an empty value. Also we reset the view each time. Show, Hide, and Toggle have easing and callbacks, so they can be slower at times. Add/Remove class is very quick.
https://ryanpeden.com/javascript-indexof-vs-includes-performance/
So while includes will be a tiny, tiny amount slower because it has to check if you passed it a regex, in reality this will make no difference to how fast your code runs. You should use indexOf if you care about where the substring is in the original string. If you don’t care, just call includes because it makes the intent of your code more clear.
In the end, it's your call if you want to use includes or indexOf for your comparison.

Hide/show function jquery or javascript calling more than one action, but not at the same time

So i want to make a remove player button, so that everytime i click it, it hides a images/button, the same for a add player function.
function addplayer(){
for (var i = 51; i <= 94; i++) {
if (i == 51 || i == 61 ||){
$("#" + "addplayer" + i).show();
}
}
}
This is my html caller
<button onclick="addplayer();"style="bottom:55;position:fixed;right:10;width:100;height:40px;background-color:#FFFFFF;">addplayer</button>
document.getElementById("addplayer2").onclick=function(){
document.getElementById("51Container").style.display="inline-block";
document.getElementById("52Container").style.display="inline-block";
document.getElementById("53Container").style.display="inline-block";
document.getElementById("54Container").style.display="inline-block";
}
document.getElementById("addplayer3").onclick=function(){
document.getElementById("61Container").style.display="inline-block";
document.getElementById("62Container").style.display="inline-block";
document.getElementById("63Container").style.display="inline-block";
document.getElementById("64Container").style.display="inline-block";
}
(i got 6 in total completly looking the same), just to illustrate, how it would work
Theese are my add player function, just on 5 different buttons, just to showcase that it is doing something, it doest seem to work for me, how do i do this, so that the same button will add (show), different object instead of the solution i got atm.
Hope somebody will help me.
If you want to invoke the function which is assigned to the addplayer# control, instead of calling $("#addplayer" + i).show() try calling $("#addplayer" + i).click() .. however, based on our back-and-forth, it seems that your i needs some attention.
As you said, your addplayer# controls are buttons, therefore, I suggest the following:
function addplayer(){
$("button[id^='addplayer']").each(function(i,e) { $(e).click(); });
}
This will invoke any click event function defined for any buttons whose id starts with addplayer.
See this fiddle for an example of how it works: https://jsfiddle.net/qhevn1x3/2/
Although I do not know your page's exact makeup, I would suggest something along these lines (if possible):
<div id='51Container'>some player</div>
<button class="addPlayer" data-id="51">Add Player</button>
<button class="removePlayer" data-id="51">Remove Player</button>
Then my JS would be something like:
// Page-level execution to assign my click events
$(function() {
$("button.addPlayer").on("click", function() {
var id = $(this).attr("data-id");
$("#" + id + "Container").css({'display':'inline-block'});
$("button.addPlayer[data-id='" + id + "']").toggle();
$("button.removePlayer[data-id='" + id + "']").toggle();
});
$("button.removePlayer").on("click", function() {
var id = $(this).attr("data-id");
$("#" + id + "Container").css({'display':'none'});
$("button.addPlayer[data-id='" + id + "']").toggle();
$("button.removePlayer[data-id='" + id + "']").toggle();
});
})();
This will wire up the add/remove buttons with the corresponding player. Then, if you wish to have a button to hide/show multiple, you need only select the appropriate add/remove buttons (by filtering by data-id) and invoke their click events.

Unable to select the newly add button

I have a table with a default of 4 rows for user to input. please see the fiddle here http://jsfiddle.net/xaKXM/4/
When the user click on "Add More", the table will add new row to the "labelTable" with unique ID, as well as "configtableTable".
var displaymore = '<tr id=row'+currentIndex+'><td style="text-align: center">'+currentIndex+'</td>'+
'<td width="60%"><p id="label_row_'+currentIndex+'"></p></td>'+
'<td><button type="button" class="switch" id="switch_'+currentIndex+'"data-role="button" data-transition="fade">Activate</button></td></tr>';
When button "Submit" is pressed, user can see the description and the "Activate" button in the configtableTable. In order to make sure the Activate button is useful, i append thisIndex to a paragraph #ptest. It works for the first 4 default rows but does not work for the newly added rows (5 onwards).
What's wrong with my logic and code?
SOLVED: by creating a class "switch" and use .on()
$("#configtableTable").on('click', ".switch", function () {
thisIndex= $('td:nth(0)',$(this).closest('tr')).text();
if(thisIndex == ""){thisIndex = 0;}
$('#ptest').append(thisIndex);
$.post('/request', {responseNumber:$('#number_'+thisIndex).val(), key_pressed:"activate"});
});
there are two errors
1.In the generated code for "addmore", it should be following code for button
id="switch_' + currentIndex + '"
2.After creating new buttons, you have to add the click event for them.
I suggest the following code
$('#configsubmit').click(function () {
for (var x = 1; x <= currentIndex; x++) {
$('#label_row_' + x).html($('#label_' + x).val());
}
$('#configtable').show();
$("#configeditdiv").show();
$('#labels').hide();
$("#configtableTable [id^='switch_']:button").unbind('click');
$("#configtableTable [id^='switch_']:button").click(function () {
thisIndex = $('td:nth(0)', $(this).closest('tr')).text();
if (thisIndex === "") {
thisIndex = 0;
}
$('#ptest').append(thisIndex);
$.post('/request', {
responseNumber: $('#number_' + thisIndex).val(),
key_pressed: "activate"
});
});

jquery toggle depending on value of textbox

This must be very simple, but I just got stuck with this... I have a list of products with an input field for the quantity and next to it a column with the prices. The prices are displaying two values. One of them is hidden. If the value of the input field goes over a certain value, it should hide the other price.
Example:
(input: [], show price1price2 )
input: [2], show <span class=one>price1</span>
input: [5], show <span class=one>price1</span>
input: [8], show <span class=two>price2</span>
input: [9], show <span class=two>price2</span>
My code so far (example, since I show just 2 products):
<form name="formname" action="formaction" method="post">
prod 1<input type="text" value="" class="class1" size="3"><span class="one">$1.00</span><span class="two">$2.00</span>
prod 2<input type="text" value="" class="class1" size="3"><span class="one">$4.00</span><span class="two">$6.00</span>
</form>
And at the bottom in script tags:
$(document).ready(function() {
if($('input.class1').val() > 5) {
$('.one').show();
$('.two').hide();
}
});
What am I missing? The form name perhaps?
This is just the first part...
My other question would be.. how can I make it so that if the sum of all the input fields (with class1 as class) is more than 5, do the same. (So now depending on the sum of the input fields, rather than each individual one)
var inputs = $('input[class^=class]'); // get inputs which class name
// start with "class" eg: class1, class2..
// fire blur event
// you may use keyup or something else
inputs.on('blur', function(e) {
var val = 0;
// adding all inputs value
// of all inputs with class = blured input class
$('input.' + this.className).each(function() {
val += parseInt( this.value, 10);
});
// checking for condition
if (val > 5) {
$(this).next('.one').show().next('.two').hide();
} else {
$(this).next('.one').hide().next('.two').show();
}
});
Demo with blur
Demo with keyup
Note
Place you all jQuery codes within $(document).ready().
According to comment
See this update
Code
if (val > 5) {
$('.one').show();
$('.two').hide();
} else {
$('.one').hide();
$('.two').show();
}
Update after last comment
Just change
val += parseInt( this.value, 10);
to
val += parseInt( this.value || 0, 10); // default 0, if no value given
According to comment
How to implement above code for select box?
var selects = $('select[class^=class]'); // get selects which class name
// start with "class" eg: class1, class2..
// fire change event
selects.on('change', function(e) {
var val = 0;
// adding all selects value
$('select.' + this.className).each(function() {
val += parseInt( this.value, 10);
});
// checking for condition
if (val > 5) {
$(this).next('.one').show().next('.two').hide();
} else {
$(this).next('.one').hide().next('.two').show();
}
});
You are calling this on page load, you need to run it each time the value of the input fields is changed.
Consider wrapping your if block in this:
$('input.class1').blur(function() {
if($('input.class1').val() > 5) {
$('.one').show();
$('.two').hide();
}
});
Or something to that effect, consider using $.on().
If you want to sum them, assign each input a common class, and use $.each():
$('.common-class').each(function() {
sum = sum + $(this).val()
});
Hope this helps.

Creating <td> elements with customer behavior on the fly in jQuery

Just getting started with jQuery, and I've had some success so far. I've created a handler that updates a <table> every time a user makes a selection from a dropdown. It looks mostly like this:
function loadAttributes() {
$.ajax({
type: "POST",
url: "../ws/itemSearch/getAttributesForItemType",
contentType: 'application/xml',
data: '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.itemsearch.foo.com/">'
+ '<soapenv:Header/><soapenv:Body><ser:getAttributesForItemType>'
+ '<arg0>' + this.value + '</arg0>'
+ '</ser:getAttributesForItemType></soapenv:Body></soapenv:Envelope>',
processData: false,
dataType: "xml",
success: function(data) {
var attributes = '<table summary="Attribute Section"><tr class="underline">';
var attCount = 0;
$(data).find("return").each(
function() {
if (++attCount % 4 == 0) {
attributes += '</tr><tr class="underline">';
}
// TODO PMA click handler to the new <td> element
attributes += '<td>' + this.textContent + '</td>';
}
);
attributes += '</tr></table>';
$("div[id=attributes]").html(attributes);
}
});
}
As you can see my next step is to not just add literal <td> elements to the row containing the attributes, but to add a click handler to them. This click handler would append the contents of the <td> to a text box; something like:
tdItem.click(function() {
$("input[name=search]").append(tdItem.textContent);
}
To do that, I'd prefer to create each td item as a separate Element and build up the table in an object-oriented manner using <tr> elements rather than pasting together a literal string which is what I'm doing now. Most of the examples on the jQuery site have to do with adding listeners to existing elements and not to building up a new section of the document like this on the fly every time. At the very least, can someone point me to a good tutorial on how to accomplish what I'm trying to do here?
This is actually fairly simple to achieve and I would recommend taking advantage of some jQuery functions when writing your html building.
First $("<tr></tr") will create a tr element stored in a jQuery object as if you had just selected it. If you are building html as above I would recommend changing to something like this in your success function.
var attributes = $('<table summary="Attribute Section"></table>');
var attCount = 0;
var attributeRow = $('<tr class="underline"></tr>');
$(data).find("return").each(
function() {
if (++attCount % 4 == 0 && attCount != 0) {
attributes.append(attributeRow.clone());
attributeRow = $('<tr class="underline"></tr>');
}
// TODO PMA click handler to the new <td> element
var attribute = $('<td>' + this.textContent + '</td>');
attribute.click(function(){//click function});
attributeRow.append(attribute );
}
);
//add the last attribute row
attributes.append(attributeRow);
$("div[id=attributes]").append(attributes);
Above you will notice that You now have the attribute td generated as a jQuery object that you can apply click functions to.
The only way to do that with string literals is to add an onclick attribute to your td elements like
attributes += '<td onclick="somefunction();">' + this.textContent + '</td>';
that would work, but I would not recommend that. To stay unobtrusive, use jQuery objects for table creation. For instance
success: function(data) {
var $table = $('<table summary="Attribute Section"></table>');
$table.append($('<tr class="underline"></tr>'));
var attCount = 0;
$(data).find("return").each(
function() {
if (++attCount % 4 == 0) {
$table.append($('<tr class="underline"></tr>').append($('<td/>', {
click: function(e){
alert($(this).text());
}
})));
}
}
);
$("div[id=attributes]").empty().append($table);
}
To me, a more elegant approach to your click handling for the td's is to put one click handler on the table itself, and let it catch the click events that bubble up from the td's. That way you don't need to worry about adding handlers to each td in the table. It's also better for performance, though in this situation, I doubt it matters much in that regard.
Here's what you would do (you could add this right after you declare the $table variable):
$table.click(function(e) {
var target = $(e.target);
if (target.is('td')) {
var input = $('input[name=search]');
input.val( input.val() + target.text() );
e.stopPropagation();
}
});

Categories

Resources