I have a form a where fields are named like data[Field][0][value], data[Field][1][value], et cetera, where the integer increments but the rest stays the same. Is there a way of finding out the lowest available integer value with jQuery?
My form uses JS to dynamically add and remove fields (some of which are created by the PHP backend on page load), so I need the lowest integer so as to create fields without conflicts.
Right now on page load, I set field_count to $( "input[name$='[value]'" ).length; and then increment it as I create my inputs. This prevents any namespace collisions, however it doesn't take removed inputs into consideration: I'd like to be able to re-use the names of removed fields.
If you can address data[Field][0] ... data[Field][n], then I would say data[Field] is an Array and data[Field].length returns the current length of that Array. In that case the last value would then be in data[Field][data[Field].length-1] and the next available integer (index) would be data[Field].length.
Just for fun and to demonstrate an alternative: if you ditch the 'naming scheme' and use a data-attribute to keep track of the input fields, something like <input type="text" data-idx="1"> you could use a method to dynamically determine the lowest slot available:
function getFreeSlot(inputFields) {
var freeslots = []
,idxs = inputFields.map(
function (i, el) { return +$(el).attr('data-idx');}
).toArray()
idxs.every( function (v,i) {
void( v-(i ? 1 : 0) !== this[(i>0 ? i-1 : 0)]
&& freeslots.push(this[(i>0 ? i-1 : 0)]+1) );
return v; }, idxs );
return freeslots.length ? freeslots[0] : idxs.length+1;
}
Working example
Assuming your description is of the name attribute of the dynamically created/destroyed fields on the page (in other words, <input name="Data[Field][0][value]">):
var $lastField = $('[name^=Data\\[Field\\]\\[][name$=\\]\\[value\\]]').last();
var lastField = $lastField[0];
This should get the last element in the DOM with a name matching the regex Data[Field][.*][value]. If you want to ensure that the middle bit is a number, you need to get a little more involved:
var $lastField = $('[name^=Data\\[Field\\]\\[').filter(function(idx, element) {
return /Data\[Field\]\[[0-9]+?\]\[value\]/.test($(this).attr('name'));
}).last();
var lastField = $lastField[0];
Can you keep a list of the removed elements and then just pop one of those of the list to use when you need to create a new one (and if the list is empty, just increment your field_count as you are doing now).
Related
I am trying to build a logic where we click a button and save an ID in localStorage. We could have 2 ids max. However, we can also remove an ID from it and add a new one. Finally, we cannot add more than 2 and these ids must be unique. These IDs will be then set as an input value in a form.
So I have four steps page:
Homepage page
Search result page
Single article page
Form result page
Each page has:
<form id="post_form_id" method="post" action="/test/compare/">
<input id="input_post_id" type="hidden" name="varPostId" value="">
<button type="submit" class="btn btn-link nav-link" id="jikubox"></i>
<span class="badge badge-secondary">0</span></button>
</form>
Saved IDs will be set in name="varPostId" value=""
Now the tricky part which is confusing me, so localStorage can only have strings so first of all on each page load I do:
var lines = localStorage.getItem("lines") ?
JSON.parse(localStorage.getItem("lines")) : [];
Then since in the article page I have a save button to add the current article id in the array, I do:
<button type="button" class="save_post">SAVE</button>
JS
$(".save_post").on("click", function(e) {
e.stopImmediatePropagation();
if (localStorage.getItem("attempts") >= 2) {
alert('nope');
return;
} else {
// Here I set the id for each article which I saved before via php in a span
var thisId = $(".my_post_id").attr("data-id");
var lines = localStorage.getItem("lines") ? JSON.parse(localStorage.getItem("lines")) : [];
lines.push(thisId);
localStorage.setItem("lines", JSON.stringify(lines));
console.log(lines);
$("#input_post_id").val(JSON.parse(localStorage.getItem("lines")));
var attempts = Number(localStorage.getItem("attempts"));
localStorage.setItem("attempts", ++attempts);
console.log(localStorage.getItem("attempts"));
$("#jikubox span").text(localStorage.getItem("attempts"));
}
});
Notice I have another localStorage: attempts, that is used to add or remove a number I set in a counter badge, basically an indicator which tells how many items I have in my "bag". Remember: max 2 and unique.
So finally on the form result page I have a button to remove added items and the following js is used:
$(".removeJiku").on("click", function(){
// Here I set the id for each article, I do that with php
// <span class="removeId" data-thisPostId="<?php echo $idThisPost; ?>"></span>
var removeThisId = $(".removeId").attr("data-thisPostId");
$(this).parent().fadeOut();
var attempts = Number(localStorage.getItem("attempts"));
localStorage.setItem("attempts", --attempts);
$("#jikubox span").text(localStorage.getItem("attempts"));
lines.splice($.inArray(removeThisId, lines), 1);
localStorage.getItem("lines", lines);
localStorage.setItem("lines", lines);
console.log(localStorage.getItem("lines"));
});
The logic is kinda ok but after I try few times, I eventually get empty lines array and the ids are not set to the input value. I think I over complicated the logic and I was wondering where and how I could simplify and correct it. I am looking into a single general bit of code which handles all of this without complicating the logic.
UPDATE
Thanks to an answer, one bit of code has been simplified and the counter will be set by checking the length of the ids in lines array so We can remove the whole code for the attempts local storage logic
$("#jikubox span").text(lines.length);
Assume you store your IDs in an array-
var idList=["123","456"];
You can store IDs like this -
localStorage.setItem("idList",JSON.stringify(idList));
and fetch idList like this-
JSON.parse(localStorage.getItem("idList")).length
Just make sure to put validations all around.
P.S. No need to count the "attempts" as you can anytime use the below code to find the length of the idList, which can be 2 or any number you want
JSON.parse(localStorage.getItem("idList")).length
UPDATE:
To remove IDs-
function removeId(array, element) {
const index = array.indexOf(element);
array.splice(index, 1);
}
Fetch array from localStorage and pass it into this function -
idList = JSON.parse(localStorage.getItem("idList"))
and call the function like this -
removeId(idList,idToBeDeleted)
This block isn't doing what you want:
lines.splice($.inArray(removeThisId, lines), 1);
localStorage.getItem("lines", lines);
localStorage.setItem("lines", lines);
The second line is getting something from localStorage but isn't doing anything with it. It could be the equivalent of
lines.splice($.inArray(removeThisId, lines), 1);
['one line', 'another line'];
localStorage.setItem("lines", lines);
Just some random value that proceeds to get ignored by the interpreter.
The other problem is that you're setting localStorage.lines to the plain lines variable, not to JSON.stringify(lines).
Note that you can simplify your syntax noise just by doing
localStorage.lines = JSON.stringify(lines);
and
const lines = JSON.parse(localStorage.lines || '[]');
localStorage.getItem('item') only accepts one parameter, which will return the value if it exists in the local storage and will return null item doesn't exist.
localStorage.setItem(item, value) accepts two parameters. item which is a key and the value which is to be saved for the key. If item is already set it will overwrite the value.
I have a problem with selecting dynamically-added elements, each with dynamically-defined ID; script always returns null when using getElemendById.
I want the script to first check, if element with certain ID already exists within the DOM, and if not - create one, if it does - do something else.
My code looks like this:
document.addEventListener('DOMContentLoaded', function () {
//define variables
var playersOnField = document.querySelector('.players');
//using timeBar as indicator of current time frame
var timeBar = document.querySelector('#time');
//its maximum value needs to be adjusted to the number of the frames we have
timeBar.setAttribute("max", data.player_positions.length);
//display the players position for current frame
positionPlayers = function() {
time = timeBar.value;
currentFrame = data.player_positions[time];
//check if DOM element representing each player of current frame already exists, if not - create it
for (var i = 0; i < currentFrame.length; i++) {
var playerId = currentFrame[i][0];
//ID's start with number, so they require special unicode notation
if (!!document.getElementById(`#\\3${playerId} `) == false) {
console.log('no element, let\'s create one');
var newPlayer = document.createElement('div');
newPlayer.id = `${playerId}`;
playersOnField.appendChild(newPlayer);
} else {
console.log('element already exists!');
}
}
}
//every time the bar changes its postion (and therefore its value) it should trigger the action to set up the players in the current position on the field
timeBar.addEventListener('change', positionPlayers);
timeBar.addEventListener('input', positionPlayers);
})
But the function is always returning false, and creating dozens od div's of the same ID, as getElementById never finds any of those newly-appended elements. How can I avoid this happening, preferably using vanilla JS?
You seem to have an extra space in the id string you are testing for. Since #{...} is an evaluated value, it shouldn't be in quotes in the first place.
Also, why use the back-tick string syntax here?
if (!!document.getElementById(`#\\3${playerId} `) == false) {
And, getElementById() already knows to look for id's so adding the # is going to search for elements that actually start with #.
Finally, if the element with that id does exist, it will return a "truthy" value that when converted to a Boolean, will convert to true, so there is no need to force a Boolean conversion (with !!) and then check to see if that is false.
That line can be rewritten to:
if (!document.getElementById(${playerId})) {
An interface has a number of hidden inputs called "kilos[]" or "precio[]". These are then passed on to a PHP function that handles them like an array. All that is fine. However, if I require to delete a row (tr) from the table (where the inputs will be deleted, too) then I do the following:
var e=t.parentNode.parentNode;
var ix=e.sectionRowIndex;
var p=e.parentNode;
var f2=t.form;
var kl= p.rows.length > 2 ? f2.elements["kilos[]"][ix].value : f2.elements["kilos[]"].value;
var pc= p.rows.length > 2 ? f2.elements["precio[]"][ix].value:f2.elements["precio[]"].value;
f2.tokilos.value-=parseFloat(kl).toFixed(2);
f2.tomonet.value-=parseFloat(pc).toFixed(2);
f2.totamb.value-=parseFloat(kl).toFixed(2);
p.removeChild(e);
Notice that this code only works in Chrome, nowhere else. Can you see what needs to be done in order to get the correct values of "kilos[]" and "precio[]"?
If the total number of rows left in the table is greater than 2, then I can use:
f2.elements["kilos[]"][ix].value
However, if the number of rows is not greater than 2, I need to do this, instead:
f2.elements["kilos[]"].value
That is the only way for it work and only in Chrome. sectionRowIndex returns the correct values all the time; it is the form.elements["name[]"][ix].value that by itself does not behave as expected when the number of rows in the tbody is only 1 (the last one). The code works and does what I need it to be done; however, it is odd that such a workaround is needed.
Is there a way to make this work in all browsers, using pure javascript?
You could use document.getElementsByName('kilos[]') which will always return an array-like object.
If you want to restrict the search to a specific element, you can use querySelectorAll:
var inputs = f2.querySelectorAll('[name="kilos[]"]');
// or
var inputs = document.querySelectorAll('#formID [name="kilos[]"]');
Sorry if this is a stupid question. I am a newbie to programming...
I have 3 values from a text input. I want 2 of these values to be stored into separate arrays for later access. finally I want to display a sum of these 3 values in my document.
What am I doing wrong?
Here is my code:
<script>
function displayCurrentBalance() {
var initialBalance = parseFloat(document.getElementById("initialBalance").value);
var inAmounts=[0];
var outAmounts = [0];
inAmounts.push(document.getElementById("amountIn").value);
outAmounts.push(document.getElementById("amountOut").value);
var sumIn = (inAmounts.reduce(function(a, b) {
return a + b[1];
}, 0));
var sumOut = (outAmounts.reduce(function(c, d) {
return c + d[1];
}, 0));
var result = initialBalance + sumIn - sumOut;
document.write(result);
};
displayCurrentBalance();
</script>
document.write(result); will overwrite your document before you even have a chance to enter the values. Make it display a value in another input box, or alert(result).
You should probably attach displayCurrentBalance to an onclick handler of some button, like <button onclick="displayCurrentBalance()">Calculate</button>.
document.getElementById("amountIn").value and similar calls will give you a string. You should use parseFloat or parseInt to convert it to an integer or a float.
You are likely calling the function before the elements are available in the page. Otherwise the script should be below the elements in the page.
You main problem is how you are using reduce. The values returned from the inputs are strings, so convert them to numbers before trying to add them (otherwise you will be concatenating strings, not adding numbers). You should probably validate the values too.
Also, reduce is called with the values of the members of the array, not the array itself (which is the 4th parameter provided to the callback, not the second), so:
var sumIn = (inAmounts.reduce(function(a, b) {
return +a + +b;
}, 0));
Modify the other call to reduce similarly. I don't think you need to provide an initial value since you initialise the array as [0]. Alternatively, keep the initial value in the call and initialise the array as [] (i.e. an empty array).
Rather than using document.write to display the result, better to write it as the value of a read–only input element so you can update it with subsequent calls as the user modifies the values in the other inputs.
I have a form field (a series of checkboxes) that's being created dynamically from a database, so it's possible that the field will not exist on the form (if there are no matching values in the database). I have some code that needs to execute based on whether the field exists, and pull in the values that are selected if it does exist. I can't seem to get javascript to acknowledge that this field exists, though. Here's what I've tried:
function displayAction(){
var f = document.adminForm;
var a = f.action;
if(f.prefix.value!="-") {
a = a + '&task=callExclusionDisplay&prefix=' + f.prefix.value;
}
else {
var exclusions = document.getElementById("exclusions");
if (exclusions != null){
alert("exclusions set");
a = a + '&task=callExclusionCreate&prefix=' + f.prefix.value + '&exclusions=' + exclusions.join();
}
}
alert('after if, action is ' + a);
}
The code never passes the if statement checking to see if exclusions is not null, even though when I look at the page there are a number of checkboxes named exclusions (with the id also set to exclusions). Is the issue with !=null because it's a group of checkboxes, rather than a single form element? How can I get this to work? If I skip the test for null, the code throws errors about exclusions not being defined if the database doesn't return any matching values.
You're using document.getElementById, but form elements have a name.
Try f.elements.namedItem("exclusions") instead of exclusions != null
Multiple elements in the same page cannot share an id attribute (ie. id must be unique or unset). As well, though some (older) browsers erroneously collect elements whose name matches the ID being looked for with getElementById, this is invalid and will not work cross-browser.
If you want to get a group of elements, you can give them all the same name attribute, and use document.getElementsByName to get the group. Note that the result of that will be a NodeList which is kind of like an array in that it can be iterated over.
Do all the checkboxes have the same id == exclusions?
If yes, then you must first correct that.
Before you do so, did you try checking the first checkbox and see if the if condition goes through?
if you have more than one element with the id "exclusions" it will screw up the functionality of getElementById. I would remove the duplicate "exclusions" ids from all of your elements and use getElementByName() instead, and give your group of checkboxes the name="exclusions" instead.
Edit:
But there is a much simpler way using jQuery, and it gives you some cross browser compability guarrantee. To do the same thing with jQuery do this:
var checkBoxesExist = $('[name=exclusions]').count() > 0;
Or if you have given your elements unique ID's then you can do this:
var checkbox1exists = $('#checkBox1').count() > 0;
Each element must have a unique ID.
Then, you can check just like this:
if (document.getElementById('exclusions1')) {
//field exists
}
Or if you need to loop through a bunch of them:
for (x=0; x<10; x++) {
if (document.getElementById('exclusions' + x)) {
//field X exists
}
}