I have multiple variables like so
var a0001 = document.getElementById("a0001");
var a0002 = document.getElementById("a0002");
var a0003 = document.getElementById("a0003");
that I use for
a0001.blur= function() {
localStorage.setItem("a0001", a0001.innerHTML);
};
a0002.blur = function() {
localStorage.setItem("a0002", a0002.innerHTML);
};
a0003.blur = function() {
localStorage.setItem("a0003", a0003.innerHTML);
};
My question is I will be working with 100's of variables and I cant figure out how to place a "loop place holder" to process the variables like such (*** represent the place holder that will cycle throw the variables and parse them out)
***.blur= function() {
localStorage.setItem("***", ***.innerHTML);
};
I am obviously new and this is a single page localstorage project and not sure if I just am not using the right keywords to find the documentation I am looking for or ? so any help will be appreciated.
edit; markup added below
<table>
<thead>
<tr>
<th id="a0001" contenteditable='true' spellcheck="false">ID
</th><th id="a0002" contenteditable='true' spellcheck="false">First Name
</th><th id="a0003" contenteditable='true' spellcheck="false">Last Name
</th></tr></thead>
</table>
The used markup is not entirely clear, but I'm assuming you have a form with some input elements (because blur fires only on form controls and window objets by default).
Despite of the real HTML, you're making a lot of unnecessary work. You've a ton of elements, which you need to handle as a group of elements, not one by one. Using id to identify a group is error prone and also very hard to maintain, and on the top of that, a global variable of each id is created, which floods yet the strongly crowded object with unneeded variables. Also, hundreds of event listeners are not needed, you can use a single listener, which can handle all the elements. Here's a simple code showing how to handle a group of elements:
const form = document.querySelector('#list'),
storingList = new Map(),
inputs = form.querySelectorAll('.to-storage');
inputs.forEach((input, idx) => {
const itemName = 'a' + (idx.toString()).padStart(4, '0');
storingList.set(input, itemName);
});
form.addEventListener('focusout', e => {
if (e.target.className !== 'to-storage') {return;} // Quit, not a target element blurred
const itemName = storingList.get(e.target),
value = e.target.value;
console.log(itemName, value);
// localStorage.setItem(itemName, value); // Commented out to not populate localStorage of SO visitors
});
<form id="list">
Item 1 <input class="to-storage"><br>
Item 2 <input class="to-storage"><br>
Item 3 <input class="to-storage"><br>
Item 4 <input class="to-storage"><br>
Item 5 <input class="to-storage"><br>
Item 6 <input class="to-storage"><br>
Item 7 <input>
</form>
In the snippet, all the inputs which of values are stored in localStorage, are grouped with a class named to-storage. The grouped inputs and the identifiers are collected to a Map object, hence they're easy to identify in an event handler (Map object can take an element as a key). Notice the dynamic item name creation, which takes care of the running index automatically.
The focusout event listener is delegated (blur doesn't bubble and can't be delegated in a simple way) to the form, which contains all the input elements. This way you can edit and update the HTML of the page, no matter how many inputs needs to be removed or added, you don't have to make any changes to the JavaScript which stores the values.
If your markup is different and you can't apply the snippet, please add an example of your markup to the question.
You can have your variables in an array like const arr = [] then loop through each of them. The entire thing would look something like:
const arr = []
// add variables to array
arr.forEach(a => {
a.blur = function() {
localStorage.setItem(a.key, a.innerHTML);
}
})
You may want to define a unique identifier for the variable, like key or something
Do not need to create multiple variables!
Native JS
var elements = document.querySelectorAll('[id^=a000]');
Jquery
$('[id^="a000"]')
Selects elements which IDs start with a000
I will suggest two methods to solve this.
Select element by different ids and store in a array
Ex:
// Add more id of elements on this array
let selectableElementsId = ['a0001', 'a0002', 'a0003'];
selectableElementsId.forEach(elementId => {
let element = document.querySelector(`#${elementId}`);
if(!element) return;
element.blur = function(){
localStorage.setItem(element.id, element.innerHTML);
}
});
add same class to all three or more elements.
// Replace yourClassName with class name of elements
let selectedElements = Array.from(document.querySelectorAll(".yourClassName"));
selectedElements.forEach(element => {
element.blur = function(){
localStorage.setItem(element.id || selectedElements.indexOf(element), element.innerHTML);
}
});
Related
I am trying to fetch a set of objects from a server and add their attributes as options to a datalist element whenever a user clicks on the input field. I want to display unique options in the list only, however, anytime the input field comes into focus, my code will keep adding every option to the datalist even though I am making a check for it not to do so.
<form action="">
<label for="">Step 1: Select or create a theme: </label>
<input type="input" list="themes" name="themes" onfocus="fetchThemes()" />
<datalist id="themes">
</datalist>
</form>
<script>
let host = "http://localhost:3002"
function fetchThemes(){
fetch(host + "/contents")
.then((response) => response.json())
.then((data) => addToDatalist(data))
}
function addToDatalist(data){
let datalist = document.getElementById('themes');
for (let object of data){
let option = document.createElement("option")
option.value = object.name
if (datalist.contains(option) === false){
datalist.appendChild(option)
}
}
console.log(document.getElementById('themes'))
}
</script>
I know i'm missing something small, but im not sure what it is that i'm doing wrong. Are DOM elements similar to objects in Python or Java, where even though two objects can have the same values, they're considered different since they are stored in separate memory locations? How can I go about fixing this?
i have made two arrays, first one is the check the array which contains all the unique values and the second one is the response of the server "object".
here i am comparing all the values of the check array with the one value of the object to get the unique value !!!
i hope this helps you !!!
//consider object as an array of the options and you want to include only the unique one
var check = []
var object = []
for(const i=0;i<=object.length;i++)
{
for(const j=0;j<=check.length;j++)
{
if(check[j] ===option[i])
{
console.log("Value exists")
}
else{
option.value = check[j]
}
}
}
for(const i=0;i<=check.length;i++)
{
option.value = check[i]
}
I have an element called lineNumber that contains some form inputs. All the values of the inputs are stored in variables.
<div class="lineNumber">
<div class="input-container">
<label>Example Line 1 :</label>
<input type="text" name="lineText" value="John's Electrical" class="lineText">
</div>
<!-- more inputs... -->
</div>
On page load there is one instance of this element.I add new instances of lineNumber dynamically with a button.
At this stage I have retrieved all the values of the first instance and placed in variables that update onchange. The challenge is iterating for each new instance.
I would like to loop through each instance of lineNumber and store the values of each variable in an object. I would also like the object values to be updated when the variables are as well.
I have eventListeners that detect a change to the input and update the value of the variable.
//wrap this in lineNumber.length loop?
let lineText = lineNumber[i].querySelector('.lineText');
let lineTextValue = lineNumber[i].querySelector('input[name="lineText"]').value;
//end of loop?
//add event listener and update lineTextValue
lineText.addEventListener("keyup", liveTextFunction);
//store value in variable lineTextValue;
function liveTextFunction(){
lineTextValue = lineNumber[i].querySelector('input[name="lineText"]').value;
}
//createLineObject() runs when new lineNumber element added
//store values *** //
let lineObject;
function createLineObject(){
lineObject = { };
lineObject[lineNumber] = lineNumberValue;
lineObject[lineText] = lineTextValue;
};
Hoping to end up with multiple lineObjects that I can access the values from eg
lineObject[1].lineNumberValue or Object.entries(lineObject[1]);
Not sure if the objects would then go in an array? eg
objectArray[ lineObject[1], lineObject[2] ];
Hoping for some guidance on how to achieve this or constructive criticism on my approach. I'm just not sure if I'm approaching this the right way and could use some feedback.
Is every <input/> you're trying to query going to have the name="linetext"? If so, you could iterate through your "lineNumber" divs and iterate through the "linetext" inputs below them with something like this:
lineObjects = Array.from(document.querySelectorAll('div.lineNumber')).map(div => {
return Array.from(div.querySelectorAll('input[name="linetext"]')).map(input => {
// do whatever you need to do with each div and input
const lineObject = {
div: div,
input: input,
value: null, // set automatically by listener
removeListener: null // call when lineObject no longer needed
};
const listener = event => {
lineObject.value = event.target.value;
}
input.addEventListener('keyup', listener);
lineObject.removeListener = () => input.removeEventListener('keyup', listener);
return lineObject;
});
});
lineObjects = lineObjects.flat(); // convert array of arrays to flat array
As an aside, this is already pretty unwieldy and it is for a single piece of functionality. You'd also have to run this again on adding new lineNumbers after cleaning up the listeners on the existing lineObjects. I'd suggest using a framework like React if possible.
I need to create an empty JQuery element to be the outer scope list of a loop, for example :
const forms_data = ['Name', 'Email'];
const form = $();
for (const input_name of forms_data){
form.insertAfter(`<input placeholder="${input_name}" / >`)
form.append(`<input placeholder="${input_name}" / >`)
form.after(`<input placeholder="${input_name}" / >`)
}
$('form').append(form);
But none of the ways of inserting new elements into it worked for me, according to other answers here, $() seems to be the correct way.
I don't want an outer div, I want all the inserted element to be siblings.
What am I doing wrong?
jsFiddle : https://jsfiddle.net/Lusfektu/16/
You should create form element like this $('<form>) and then you can use append method.
const forms_data = ['Name', 'Email'];
const form = $('<form>');
forms_data.forEach(name => {
form.append($('<input>', {placeholder: name}))
})
$('body').append(form);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Update: You could use reduce to make array of inputs or jquery objects and then append that array to form element at once.
const forms_data = ['Name', 'Email'];
const inputs = forms_data.reduce((r, name) => {
r.push($('<input>', {placeholder: name}))
return r;
}, [])
$('form').append(inputs)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form action=""></form>
If all you are trying to do is create the inputs and append them to the form, you can use a simple array, or map the elements. $.map can be used to loop over an array, create new elements, and return them as a list at the end. Using this we can create the array, and then append them all at once to the form.
const forms_data = ['Name', 'Email'];
$('form').append($.map(forms_data, function(element){
return `<input placeholder="${element}" / >`;
}));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form></form>
You're right to use $(), but those APIs won't work. Instead use .add():
form = form.add($(`<input placeholder="${input_name}" / >`));
As the other answer notes, if you initialize the element to a <form> element then you can use the other APIs, but if you've already got a <form> on the page then that will be problematic. You could in that case initialize the container to something harmless like a <div> or a <span>.
This should do it:
const form = $('<form>');
Then simply use append on it like you did on the second line of your sample.
So I'm rendering my textarea dynamically using ngFor however I'm not sure how I can pass the ngModel to bind it in my function.
<div *ngFor="let inputSearch of searchBoxCount; let i = index" [ngClass]="{'col-sm-3': swaggerParamLength=='3', 'col-sm-9': swaggerParamLength=='1'}">
<textarea name="{{inputSearch.name}}" id="{{inputSearch.name}}" rows="3" class="search-area-txt" attr.placeholder="Search Product {{inputSearch.name}}"
[(ngModel)]="inputSearch.name"></textarea>
</div>
textarea example:
textarea is render based on the length of the response I get from api call in my case searchBoxCount is basically searchBoxCount.length, so if it length is = 1 then it will only render 1 textarea if its 3 then it will show 3 textareas. The objs have different names (example: id/email/whatever), so ngModel is based on the obj name from the json object.
How do I bind inputSearch.name to my function getQueryString()
getQueryString() {
this.isLoading = true;
let idInputValue = inputSearch.name; //bind it here
return "?id=" + idInputValue
.split("\n") // Search values are separated by newline and put it in array collection.
.filter(function(str) {
return str !== ""
})
.join("&id=");
}
Search func where getQueryString() is called
searchProduct() {
let queryString1 = this.getQueryString();
this._searchService.getProduct(queryString1)
.subscribe(data => {
console.log(data);
});
}
I know how to do it if the ngModel is not coming from the ngFor, is there another way to get the value from the textarea without ngModel? maybe that's the only way or if I can still use ngModel.
Summary of current state
First, let me summarize where your data is. You have a list of one or more objects named searchBoxCount. Each of the elements in the list is an object which has a name property, so you could, for example, call let name = this.searchBoxCount[0].name; to get the name of the first object in the list.
In the HTML template you use ngFor to loop through all of the objects in the searchBoxCount list, and in each iteration you assign the object to a local (to the ngFor) variable named inputSearch. You then bind the input from the textarea created in each loop iteration to the name property for that iteration's inputSearch object.
How to get your data
The key here is that the inputSearch is the same Object as is stored in searchBoxCount at some particular index (index 0 for the first object, etc...). So when the ngModel is tied to inputSearch.name it is also bout to searchBoxCount[n].name. External to the ngFor, you would loop through the searchBoxCount list to get each name you need.
As a consequence
Based on the comments on the original post, it sounds like you can have one or
more names that you need to include in the query string output. That means for your getQueryString() to work, you have to loop through the list (or as in this case, let the list loop for us):
getQueryString() {
this.isLoading = true;
let result : string = "?id=";
this.searchBoxCount.forEach(
(inputSearch:any) => { //Not the same variable, but same objects as in the ngFor
result = result + inputSearch.name + "&id=";
});
result = result.slice(0, result.length - 4); //trim off the last &id=
return result;
}
Edit: Multiple different fields with different names
From the comments on this post, it now is clear each inputSearch has its own key to be used in the query string, that is stored in the name property. You need to preserve that name, which means you can't bind the ngModel to it. Otherwise the user will destroy the name by typing in their own text and there will be no way to get the correct key back. To that end, you need to store bind the ngModel to some other property of the inputSearch object. I am going to assume the object has a value property, so it looks like this:
{
name: "id",
value: "33\n44"
}
That is, each inputSearch has a name, and the value will have one or more values, separated by new line. You would then have to change the HTML template to this:
<div *ngFor="let inputSearch of searchBoxCount; let i = index"
[ngClass]="{'col-sm-3': swaggerParamLength=='3', 'col-sm-9':
swaggerParamLength=='1'}">
<textarea name="{{inputSearch.name}}"
id="{{inputSearch.name}}" rows="3" class="search-area-txt"
attr.placeholder="Search Product {{inputSearch.name}}"
[(ngModel)]="inputSearch.value"></textarea>
</div>
Notice that I changed the ngModel from inputSearch.name to inputSearch?.value (the ? allows for null if there is no value to begin with) inputSearch.value. The getQueryString() method then looks something like this:
getQueryString() {
let result:string = "?";
//for each of the input search terms...
this.searchBoxCount.forEach( (inputSearch:any) => {
// first reparse the input values to individual key value pairs
let inputValues:string = inputSearch.value.split("\n")
.filter(function(str) { return str !== "" })
.join("&" + inputSearch.name + "=");
// then add it to the overall query string for all searches
result = result +
inputSearch.name +
"=" +
inputValues +
"&"
});
// remove trailing '&'
result = result.slice(0, result.length - 1);
return result;
}
Note, using RxJs this is probably easier but I am testing vanilla javascript.
Using this, if the user entered two IDs (33 and 44), a single sku, and two emails, the result would be ?id=33&id=24&sku=abc123&email=name#compa.ny&email=an.other#compa.ny
My aim is to replace the teacher-id(f_teacher) of one outputted array with the teacher name of another array. I wrote a custom filter, that should do the job:
angular.module('core')
.filter('replaceId', function () { //filter, which replaces Id's of one array, with corresponding content of another array
return function (t_D, s_D, t_prop, s_prop) { //data of target, data of source, target property, source property
var replacment = {};
var output = [];
angular.forEach(s_D, function (item) {
replacment[item.id] = item[s_prop]; //replacment - object is filled with 'id' as key and corresponding value
});
angular.forEach(t_D, function (item) {
item[t_prop] = replacment[item[t_prop]]; //ids of target data are replaced with matching value
output.push(item);
});
return output;
}
});
I use a 'ng-repeat' like this:
<tr ng-repeat="class in $ctrl.classes | filter:$ctrl.search | replaceId:$ctrl.teachers:'f_teacher':'prename' | orderBy:sortType:sortReverse">
<td>{{class.level}}</td>
<td>{{class.classNR}}</td>
<td>{{class.f_teacher}}</td>
</tr>
But it only outputs an empty column. Now the strange thing: If I follow the steps with the debugger, it works for the first time the filter is performed. But when it is performed a second time it outputs an empty column.
I noticed that the returned object of the filter overwrites the $ctrl.classes - array, but normally this shouldn't be the case?
Here is a plnkr:
https://plnkr.co/edit/EiW59gbcLI5XmHCS6dIs?p=preview
Why is this happening?
Thank you for your time :)
The first time through your filter the code takes the f_teacher id and replaces it with the teacher name. The second time through it tries to do the same thing except now instead of getting a teachers ID in f_teacher it finds the teacher's name so it doesn't work. You could fix it by making a copy of the classes instead of modifying them directly. e.g.
angular.forEach(t_D, function (item) {
var itemCopy = angular.copy(item);
itemCopy[t_prop] = replacment[itemCopy[t_prop]];
output.push(itemCopy);
});
https://plnkr.co/edit/RDvBGITSAis3da6sWnyi?p=preview
EDIT
Original solution will trigger an infinite digest because the filter returns new instances of objects every time it runs which will cause angular to think something has changed and retrigger a digest. Could you just have a getter function that gets a teachers name instead of using a filter?
$scope.getTeacherName = function(id) {
var matchingTeachers = $scope.teachers.filter(function(teacher) {
return teacher.id == id;
})
//Should always be exactly 1 match.
return matchingTeachers[0].prename;
};
And then in the HTML you could use it like
<tr ng-repeat="class in classes">
<td>{{class.level}}</td>
<td>{{class.classNR}}</td>
<td>{{getTeacherName(class.f_teacher)}}</td>
</tr>
https://plnkr.co/edit/gtu03gQHlRIMsh9vxr1c?p=preview