I have from where you can add dynamic unlimited inputs in most pages i have over 500 inputs the bigest one have 1450 inputs.
When you type in any input i like to check for duplicate values in other inputs.
Atm i think to build my code like this
$('.request').keyup(function(){
const current_element = $(this)
$('.request').not(this).each(function(key, element){
if (current_element.val() == element.val()) {
console.log('error')
}
})
})
But this do not look very good to scan each time all elements. Is there any better way i can do this?
Use input values as keys to let JS do the iterating for you. Construct a map object with your initial values (if any), then when an input changes you can check if the key already exists in your object. The values of the object could be arrays of elements with the same input value, to highlight each one as being in an error state until there is only one element in the array. Store the previous value on each input so that you know where to "move" it from on your map object.
I recommend you debounce the keyup event, and also listen on change for pastes, autofills, etc. Otherwise you're slowing down your code trying to make irrelevant matches before the user has finished typing.
This is an untested example but it should get you most of the way there:
(function() {
const inputs = new Map(),
previousValues = new Map();
$('.request').each(function() {
// populate maps
if (this.value) {
previousValues.set(this, this.value);
const same = inputs.get(this.value);
if (same) {
same.push(this);
}
else {
inputs.set(this.value, [this]);
}
}
}).on('keyup change', debounce(function() {
const previousValue = previousValues.get(this);
if (this.value) {
if (previousValue == this.value) {
return; // unchanged
}
previousValues.set(this, this.value);
}
else if (previousValue) {
previousValues.delete(this);
}
else {
return; // unchanged
}
const samePrevious = previousValue && inputs.get(previousValue),
sameCurrent = this.value && inputs.get(this.value);
if (samePrevious) {
if (samePrevious.includes(this)) { // should always exist
samePrevious.splice(samePrevious.indexOf(this), 1); // remove without creating new array
}
switch (samePrevious.length) {
case 0:
inputs.delete(previousValue);
break;
case 1:
// remove error condition from single remaining element in samePrevious
break;
}
}
let clearError = true;
if (sameCurrent) {
if (!sameCurrent.includes(this)) { // should never exist
sameCurrent.push(this);
}
if (sameCurrent.length > 1) {
clearError = false;
// set error condition on all elements in sameCurrent
}
}
if (clearError && currentHasError()) { // currentHasError() is any expression to get current element's status
// remove error condition from current element
}
}));
inputs.forEach(function(same, text) {
if (same.length > 1) {
// set error condition on the elements in same
}
});
})();
Related
I created two functions and a global object data. I am trying to split what a person inputs inside a text area into individual strings and push them in the global object. The Error I have says userInputParam.split is not a function.For the sake of brevity I have excluded the updateUserInput function which is attached to a click event.
let data = {
userInput: [],
splittedInput: [],
slicedInput: [],
};
function updateUserInput(data) {
if (data.userInput.length == 0) {
// console.log("You can do this Panda!")
data.userInput.push(input.value);
//input.value is what a user inputs in a textarea.
splitUserInput(data.userInput)
}
}
function splitUserInput (userInputParam){
let splittedInput = userInputParam.split(/([*/+-])/)
//console.log(splittedInput)
}
The argument to splitUserInput should be the input value, not the array that you pushed it onto.
splitUserInput should return the result of splitting, and then you can push that onto data.sp;littedInput.
function updateUserInput(data) {
if (data.userInput.length == 0) {
// console.log("You can do this Panda!")
data.userInput.push(input.value);
//input.value is what a user inputs in a textarea.
data.splittedInput.push(splitUserInput(data.userInput));
}
}
function splitUserInput (userInputParam){
let splittedInput = userInputParam.split(/([*/+-])/)
return splittedInput;
}
I am writing some JavaScript to manipulate a form. The form originates from Gravity Forms on WordPress so I don't have direct manipulation of the form. I am using Custom HTML to load a JS file. It's working fine, but my question is specific to JavaScript.
I have 3x date fields for MM, DD, and YYYY. I would like to execute a function when all of these fields are completely entered.
Currently, I have this, which I found on another thread and it works for one field's test. It's listening for keyup event and then testing length, and then calling doMyFunction(). The input_9_60_1 is the working ID of the field.
document.querySelector('input[id="input_9_60_1"]').onkeyup = function() {
if (this.value.length === 2) doMyFunction();
}
What I need to do is test input_9_60_1, input_9_60_2, and input_9_60_3 for lengths of 2, 2, and 4 respectively, and if all three are true, then doMyFunction(). Is there a simple way of doing this?
I am thinking I need to set some variables for the 3x length values, and then evaluate against all of them on each input's onkeyup event.
You could make an array of selectors and lengths to check against:
const inputs = ['#input_9_60_1', '#input_9_60_2', '#input_9_60_3']
.map(([sel]) => document.querySelector(sel));
const validLengths = [2, 2, 4];
for (const input of inputs) {
input.addEventListener('keyup', () => {
if (inputs.every((input, i) => input.value.length === validLengths[i])) {
doMyFunction();
}
});
}
The easiest way: use a global variable: the state of your page. it will keep data on the inputs. e.g.:
let state = {
input_9_60_1: false,
input_9_60_2: false,
input_9_60_3: false
}
// Same event for input_9_60_2 and input_9_60_3
document.querySelector('input[id="input_9_60_1"]').onkeyup = function() {
if (this.value.length === 2) {
state.input_9_60_1 = true;
doMyFunction();
}
}
function doMyFunction(){
if(state.input_9_60_1 && state.input_9_60_2 && state.input_9_60_3){
// run your code
}
}
simple and effective
I think I got it myself but please let me know if there's a better way. I'm sure I can shorten this up too...
mm_length = document.getElementById("input_9_60_1").value.length;
dd_length = document.getElementById("input_9_60_2").value.length;
yyyy_length = document.getElementById("input_9_60_3").value.length;
document.querySelector('input[id="input_9_60_1"]').onkeyup = function() {
mm_length = this.value.length;
if (mm_length==2 && dd_length==2 && yyyy_length==4) {
doFunction1();
}
else doFunction2();
}
document.querySelector('input[id="input_9_60_2"]').onkeyup = function() {
dd_length = this.value.length;
if (mm_length==2 && dd_length==2 && yyyy_length==4) {
doFunction1();
}
else doFunction2();
}
document.querySelector('input[id="input_9_60_3"]').onkeyup = function() {
yyyy_length = this.value.length;
if (mm_length==2 && dd_length==2 && yyyy_length==4) {
doFunction1();
}
else doFunction2();
}
function doFunction1() {
// stuff;
}
function doFunction2() {
// stuff;
}
I am working with angular and I am trying to create a "select all" button.
I have a list of items, each item has a toggle and what I am doing is, on change (everytime the toggle changes from true (selected) to false (not selected), I run a function to create an array with all the IDs of the selected elements.
This works almost perfectly, the problem is that I am facing some issues with the indexfOf method to check if the ID is already in the array.
var isInArray;
isInArray = function(arr, id) {
console.log("index of ", arr.indexOf(id));
return arr.indexOf(id);
};
scope.evtSelectAll = function() {
return angular.forEach(scope.listToDisplay, function(element) {
element.copyTo = true;
return scope.selectFromList(element.iID, element.copyTo);
});
};
scope.selectFromList = function(id, copy) {
if (copy === true && isInArray(scope.selected, id) === -1) {
scope.selected.push(id);
} else {
scope.selected.pop(id);
}
console.log("scope.selected - ", scope.selected);
if (scope.selected.length > 0) {
console.log("Emitted event: can proceed!");
scope.$emit('enough-elements');
} else {
console.log("Emitted event: can not proceed!");
scope.$emit('not-enough-elements');
}
return scope.result = scope.selected;
};
the problem I've got is when the array (scope.selected) has multiple IDs.
Let's say, for example, that my scope.selected looks like this:
scope.selected = [2,3,4,7]
if I click on select all, nothing gets added (and this is correct)
Now, let's say I untick 4 and 7 for example, and my scope.selected now looks like this:
scope.selected = [2,3]
If I now click on select all, my result is the following: [2,4,7].
I lose the 3
I think this is due to the fact that my array doesn't have one single item?
thanks for any help. Here's also a quick codepen to explain the problem. If you check the console and play with the toggles you should be able to see straight away what I am referring to.
Thanks in advance
Thanks to Matthias and Christian Bonato for their suggestions.
At the end, I solved using both of their suggestions and the final result seems to work as expected.
Here's a codepen with the final version: http://codepen.io/NickHG/pen/KNXPBb
Basically, I changed
scope.selected.pop(id);
with
$scope.selected.splice( isInArray($scope.selected, id),1);
and in the selectAll event function, I always empty scope.selected[] before adding elements to the array
$scope.evtSelectAll = function() {
$scope.selected = []
angular.forEach($scope.list, function(element) {
element.copyTo = true;
return $scope.selectFromList(element.id, element.copyTo);
});
};
thank you for your help!
I think mostly your code contains a logical error. You are using the function selectFromList to de-select (when done individually) and for the select all (which you don't want to use to de-select).
As someone pointed out in a for some reason now deleted answer, the pop.() function shouldn't be called with any arguments (it is only for removing the last element), you should use splice like this:
$scope.selected.splice( isInArray($scope.selected, id),1);
Unless you really need the emitted functionality to run on a select all, you can try if this is the answer for you:
var isInArray;
isInArray = function(arr, id) {
console.log("index of ", arr.indexOf(id));
return arr.indexOf(id);
};
scope.evtSelectAll = function() {
return angular.forEach(scope.listToDisplay, function(element) {
element.copyTo = true;
if (isInArray($scope.selected, element.id) === -1) {
$scope.selected.push(element.id);
}
});
};
scope.selectFromList = function(id, copy) {
if (copy === true && isInArray(scope.selected, id) === -1) {
scope.selected.push(id);
} else {
$scope.selected.splice(isInArray($scope.selected, id), 1);
}
console.log("scope.selected - ", scope.selected);
if (scope.selected.length > 0) {
console.log("Emitted event: can proceed!");
scope.$emit('enough-elements');
} else {
console.log("Emitted event: can not proceed!");
scope.$emit('not-enough-elements');
}
return scope.result = scope.selected;
};
Now the select all only adds to scope.selected if it doesn't find the id in the scope.selected list.
So far I have tried this:
if ($scope.flag) {
$scope.partialPricing.push([$scope.p]);
$scope.flag = false;
} else {
for (var i = 0; i < $scope.partialPricing.length; i++) {
console.log("finding object = " + $scope.partialPricing);
if ($scope.partialPricing[i].type != $scope.p.type) {
$scope.partialPricing.push([$scope.p]);
break;
} else {
console.log("Already Given . Please Clear .");
}
}
}
Problem is when it enters into the else condition, it gets
$scope.partialPricing = [Object Object] and also, $scope.partialPricing[i].type = undefined.
My goal is to prevent user from giving same type twice. Here type is Hourly , Monthly , Weekly.
He can set the value only once. Tell me the solution or any other way i can do it?
Your if condition in the loop won't work properly: it will add the element to the list if its type is different from the first element of the list, independently of all the rest of the list.
The easiest is to make a lookup function as follows:
function lookupByType(type) {
for (var i = 0; i < $scope.partialPricing.length; i++) {
if ($scope.partialPricing[i].type == type) {
return true;
}
}
return false;
}
And then use it as follows (no for loop here):
if (lookupByType($scope.p.type)) {
console.log("Already Given . Please Clear .");
} else {
$scope.partialPricing.push($scope.p);
}
About $scope.flag, I assume you're aware that you use it to bypass the verification, you probably have a good reason for this. However, if the goal is only to insert the first element, there's no need for it: the lookup function will always return false if the list so far is empty.
Edit: You also had a type problem: your pricing list was an array of array of objects, and you used it as an array of objects. You probably want to use the latter, so you need to push($scope.p) rather than push([$scope.p]).
You may switch your else part a bit and check for equality, because that is what you need for exiting the loop. Then break and make your next action according of the check.
if ($scope.flag) {
$scope.partialPricing.push([$scope.p]);
$scope.flag = false;
} else {
var found = false;
for (var i = 0; i < $scope.partialPricing.length; i++) {
console.log("finding object = " + $scope.partialPricing);
if ($scope.partialPricing[i].type === $scope.p.type) {
console.log("Already Given . Please Clear .");
found = true;
break;
}
}
if (!found) {
$scope.partialPricing.push([$scope.p]);
}
}
Otherwise, you could use Array#some and perform a check
if ($scope.flag) {
$scope.partialPricing.push([$scope.p]);
$scope.flag = false;
} else {
var found = false;
if ($scope.partialPricing.some(function (price) { return price.type === $scope.p.type; })) {
console.log("Already Given . Please Clear .");
} else {
$scope.partialPricing.push([$scope.p]);
}
}
Why you need to push "$scope.P" into the partialPricing. Any logic related you handled this line?
$scope.partialPricing.push([$scope.p]);
So, I have this little code in my js file:
window.onload = function Equal() {
var a = 'b1'
var b = 'box1'
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
var a = 'b2'
var b = 'box2'
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
The function itself is not important (it equals checkboxvalues set in the localstorage), but I execute it 2 times. First time with var a & b set to 'b1' & 'box1'. Then I run the script again (same script), but with var a & b set to 'b2' & 'box2'. Now, this code works, but my question is if there is a shorter way to write this? I can imagine some sort of array with a loop, but I could not get it to work for some reason. The 2 variables are pairs, and I know this might be a dumb question, but I can't find the answer anywhere.
You can use a second function which will accept the local storage key and the checkbox id like
window.onload = function Equal() {
setCheckboxState('box1', 'b1');
setCheckboxState('box2', 'b2');
}
function setCheckboxState(id, key) {
document.getElementById(id).checked = 1 == localStorage.getItem(key);
}
You might separate common logic into another function
window.onload = function Equal() {
function extractFromStorage(a, b) {
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
extractFromStorage('b1', 'box1');
extractFromStorage('b2', 'box2');
}
function doTheStuff(a, b) {
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
window.onload = function Equal() {
doTheStuff('b1', 'box1');
doTheStuff('b2', 'box2');
}
?
This is how I would do it.
There are several problems with your code.
You do not check that the element you are stetting an attribute to
exists. You do not check if the localStorage item you get is
defined.
You pollute the global name space with the function name Equal.
That function should not be named with a capital as it is not a Object generator.
There is no need to use setAttribute and removeAttribute, in
fact removeAttribute makes no sense in this case as you can not
remove the checked attribute from the element. BTW why use setAttribute here and not for window.onload?
The checked attribute is either true or false, it does not use the
string "checked"
Binding the load event via the onload attribute is not safe as you may
block 3rd party code, or worse 3rd party code may block you.
There is no error checking. DOM pages are dynamic environments, pages
have adverts and content from many places that can interfer with your
code. Always code with this in mind. Check for possible errors and deal with them in a friendly way for the end user. In this case I used an alert, not friendly for a normal user but for you the coder.
My solution.
// add an event listener rather than replace the event listener
window.addEventListener(
"load", // for the load event
function(){
// the update function that is called for each item;
var update = function(item){
// the right hand side equates to true if the localstorage
// is equal to "1". LocalStorage allways returns a string or
// undefined if the key is not defined.
item.element.checked = localStorage[item.storageName] === "1";
}
// safe element getter
var getElement = function(eId){
var e = document.getElementById(eId); // try and get the element
if(e === null){ // does it exist?
throw "Missing element:"+eId; // no then we can not continue
// the program stops here unless
// you catch the error and deal with
// it gracefully.
}
return e; //ok return the element.
}
// Item creator. This creates a new item.
// sName is the local storage name
// eId id the element ID
var item = function(sName, eId){
return {
storageName: sName, // set the loaclStorage name
element:getElement(eId); // get the element and check its safe
};
}
// make it all safe
try{
// create an array of items.
var items = [
item("b1","box1"),
item("b2","box2")
];
// for each item update the element status
items.forEach(update);
}catch(e){
alert("Could not update page?");
}
}
);