How to add only unique options to datalist element? - javascript

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]
}

Related

How to remove duplicate object from array in JavaScript

I am working on an array, Actually, I have a dropdown list. I am pushing that object into an array but I am facing one issue actually when I select something it shows two elements at the same time like in dropdown let suppose if I have options ( option1, option2, option3). For example, I have selected option1 it pushed object value as option1 but when I select option2 it also pushed it into an array. I want unique values like if I select options2 it should be select recent value.
generateExtraFieldData = (data, name, index, type, value, values) => {
const { projectFloorData, unitModifiedData } = this.state
let obj = projectFloorData[name]
obj['data'] = data
let tempArr = [...unitModifiedData]
tempArr.push(obj)
this.setState({ unitModifiedData: this.uniqueFiles(tempArr) })
// projectFloorData[name].data = data
}
uniqueFiles = (data) => {
let unique = _.uniqBy(data, 'index')
console.log('## array', unique)
return unique
}
I have used lodash but it just return ist selected value, I need a recent value like if the user selected a second time
Try using yourArray.length = 1;, this limit you array to an int limit. And if you select another option, just do yourArray.length = 0 to clear the array and then again yourArray.length = 1; to store the new option.
why are you pushing your selected value into your array if you want to select a single value at a time try an object instead of an array and change the value/ reassign the value on selection.

How to use a call multiple varibles in a sinlge if statement?

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);
}
});

Loop through each element and add values to a new object. Add these objects to array & access their values

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.

Angular2 bind ngModel from ngFor

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

Building menu or ComboBox with data stocked in a "dynamic" array

I want to add a comboBox or menu button to my interface which will have data in it that comes from an array. The data in the array won't always be the same so I need the button to be "dynamic". How can I achieve that ? I can't specify a model if the array changes...
Edit: I'm using ExtJS 6, so I need to be able to feed the menu parameter:
menu: [{
text:'Menu Item 1'
},{
text:'Menu Item 2'
},{
text:'Menu Item 3'
}]
Example:
1) Users selects a menu item and click on a send button.
2) Depending on the value of the item the user clicked on, a javascript array is created with sometimes 2, sometimes 10 fields in it.
3) The fields are shown in a new menu button or combobox.
Thanks for your help !
HTML
<input type="text" name="example" list="exampleList">
<datalist id="exampleList">
<option value="A">A</option>
<option value="B">B</option>
</datalist>
Javascript
function generateOptionsFromDynamicArray (arr) {
// Get the datalist by id, cache option variable
var datalist = document.querySelector('#exampleList'),
option;
// Remove old options
while( datalist.firstChild ) {
datalist.removeChild(datalist.firstChild);
}
// Loop through array
for (var i = 0, l = arr.length; i < l; ++i) {
option = document.createElement('option');
option.setAttribute('value', arr[i]);
option.innerText = arr[i];
datalist.appendChild(option);
}
}
I created a function that you can pass that dynamic array into in order to update the combobox, in this case I'm using a datalist.
A few points about this code, you should add a check to ensure arr is an array or "array-like" (typedarrays I'm looking at you). You should also remove the datalist from the DOM while these changes are happening so you dont redraw the DOM on each iteration
I've created a fiddle for testing.
You cannot listen to an array change event, so whenever you change the array, you would also have to call following function:
function arrayToStore(array) {
var store = Ext.getCmp("myComboId").getStore();
//var store = Ext.getStore("myComboStore");
store.removeAll();
store.add(array.map(function(item) {
// This maps the array entry into a model you can add to the store.
// Instead of "field1", use your field name.
// If your array contains an object, you can preprocess it here.
// "field1" would be the default value if you have defined
// an implicit store by providing an array in the combo config:
// store:['item1','item2','item3'],
return {
field1:item
};
});
}

Categories