I am trying to generate a form based on these settings...
let formItems = new Form("form-items", {
items: [
{ id: "companyName" },
{ id: "documentKey" },
{ id: "documentDate" }
],
});
Inside, I generate each input, and try to add an eventListener, but it donesn't work. What am I doing wrong?
module.exports = function Form(formElementId, options) {
this.state = {}
options.items.map(item => {
renderInput(item.id);
this.state[item.id] = ""
})
function renderInput(id) {
let html = `<input id="${id}" />`
document.getElementById(formElementId).innerHTML += html;
document.getElementById(id).addEventListener("input", (e) => {
console.log(e); // <--------- Doesn't work
this.state[id] = e.target.value; // <--------- Doesn't work
console.log(this.state); // <--------- Doesn't work
})
}
}
Instead of having your variable as template literal, you can just dynamically create an HTML input element and attach to it the event, also instead of add the HTML to using += just have appended to the container
I would use this snippet instead:
module.exports = function Form(formElementId, options) {
this.state = {};
self = this;
options.items.map(item => {
renderInput(item.id);
this.state[item.id] = "";
});
function renderInput(id) {
let input = document.createElement("input");
input.setAttribute("id", id);
document.getElementById(formElementId).append(input);
input.addEventListener("input", e => {
self.state[id] = e.target.value;
});
}
};
Related
I've created a store with a list of products that are generated from js and i attached an event listener to every product.
For sorting purposes, i've decided to recreate the dom to put the products in the order that i want but the problem is that the click event doesnt work and i dont know why. My line of thinking is that if something is declared globally, it should be accesable from all corners of the aplication. Am i right?
const grid = document.querySelector('.grid');
//arr of products
const productsArr = [{
name: 'Aname1',
price: 200
},
{
name: 'Cname2',
price: 2000
},
{
name: 'Zname3',
price: 28
},
{
name: 'Pname4',
price: 5
}
];
const paintProducts = function() {
productsArr.forEach(product, () => {
let price = product.price;
let name = product.name;
//create html
productsHtml = `<div product data-price="${price}" data-name = "${name}">
<h6 class="price">${price} coco</h6>
<p class="product-descr">${description}</p>
</div>`
});
//insert html
grid.insertAdjacentHTML('beforeend', productsHtml);
};
paintProducts();
// filter
const aZBtn = document.querySelector('.a-z');
const filterAlphabetically = () => {
allInstruments.sort(function(i, j) {
if (i.name < j.name) {
return -1;
}
if (i.name > j.name) {
return 1;
}
return 0;
});
};
//clean the dom
const cleanGrid = function() {
grid.innerHTML = '';
};
aZBtn.addEventListener('click', function() {
filterAlphabetically();
cleanGrid();
paintProducts();
clickOnProduct();
});
const products = document.querySelectorAll(".product")
const clickOnProduct = function() {
products.forEach(function(product) {
product.addEventListener('click', () => {
console.log("something here")
})
})
}
<div class="grid-container">
<div class="a-z">Alphabetically</div>
<div class="grid">
</div>
</div>
First I found a logical flaw in paintProducts
The following line was outside the loop, so only 1 item could ever be rendered, the last one.
grid.insertAdjacentHTML('beforeend', productsHtml);
Second was in filterAlphabetically. There a mistery variable pops up allInstruments, replaced it with productsArr.
Third problem was const products = document.querySelectorAll(".product"). This was outside the clickOnProduct function.
As every repaint generates new DOM elements, after sorting, the events need to be bound to the new elements.
Fourth problem is the product div itself, it countained <div product ... but you are using a queryselector referring a class so this should be <div class="product" ...
When fixing all the above, we result in this:
<html>
<body>
<div class="grid-container">
<div class="a-z">Alphabetically</div>
<div class="grid">
</div>
</div>
</body>
<script>
const grid = document.querySelector('.grid');
//arr of products
const productsArr = [{
name: 'Aname1',
price: 200
},
{
name: 'Cname2',
price: 2000
},
{
name: 'Zname3',
price: 28
},
{
name: 'Pname4',
price: 5
}
];
const paintProducts = function() {
productsArr.forEach(product => {
let price = product.price;
let name = product.name;
//create html
productsHtml = `<div class="product" data-price="${price}" data-name = "${name}">
<h6 class="price">${price} coco</h6>
<p class="product-descr">${name}</p>
</div>`
//insert html
grid.insertAdjacentHTML('beforeend', productsHtml);
});
};
paintProducts();
// filter
const aZBtn = document.querySelector('.a-z');
const filterAlphabetically = () => {
productsArr.sort(function(i, j) {
if (i.name < j.name) {
return -1;
}
if (i.name > j.name) {
return 1;
}
return 0;
});
};
//clean the dom
const cleanGrid = function() {
grid.innerHTML = '';
};
aZBtn.addEventListener('click', function() {
filterAlphabetically();
cleanGrid();
paintProducts();
clickOnProduct();
});
const clickOnProduct = function() {
var products = document.querySelectorAll(".product")
products.forEach(function(product) {
product.addEventListener('click', () => {
console.log("something here")
});
});
}
clickOnProduct()
</script>
</html>
I am trying to provide functionality in my webpage for editing state data.
Here is the state structure
state = {
eventList:[
{
name: "Coachella"
list: [
{
id: 1,
name: "Eminem"
type: "rap"
}
{
id: 2,
name: "Kendrick Lamar"
type: "rap"
}
]
}
]
}
I want to be able to edit the list arrays specifically the id, name, and type properties but my function doesn't seem to edit them? I currently pass data I want to override id name and type with in variable eventData and an id value specifying which row is selected in the table which outputs the state data.
Here is the function code:
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList;
eventListNew.map((event) => {
event.list.map((single) => {
if (single.id == id) {
single = eventData;
}
});
});
this.setState({
eventList: eventListNew,
});
};
When I run the code the function doesn't alter the single map variable and I can't seem to pinpoint the reason why. Any help would be great
edit:
Implementing Captain Mhmdrz_A's solution
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
event.list.map((single) => {
if (single.id == id) {
single = eventData;
}
});
});
this.setState({
eventList: eventListNew,
});
};
I get a new error saying Cannot read property list of undefined in another file that uses the map function to render the state data to the table?
This is the part of the other file causing the error:
render() {
const EventsList = this.props.eventList.map((event) => {
return event.list.map((single) => {
return (
map() return a new array every time, but you are not assigning it to anything;
editPickEvent = (eventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
event.list.forEach((single) => {
if (single.id == id) {
single = eventData;
}
});
return event
});
this.setState({
eventList: eventListNew,
});
};
const editPickEvent = (eventData, id) => {
const updatedEventList = this.state.eventList.map(event => {
const updatedList = event.list.map(single => {
if (single.id === id) {
return eventData;
}
return single;
});
return {...event, list: updatedList};
});
this.setState({
eventList: updatedEventList,
});
}
Example Link: https://codesandbox.io/s/crazy-lake-2q6ez
Note: You may need to add more checks in between for handling cases when values could be null or undefined.
Also, it would be good if you can add something similar to the original data source or an example link.
Turns out primitive values are pass by value in javascript, which I didn't know and why the assignment wasn't working in some of the previous suggested answers. Here is the code that got it working for me:
editEvent = (EventData, id) => {
const eventListNew = this.state.eventList.map((event) => {
const newList = event.list.map((single) => {
return single.id == id ? EventData : single;
});
return { ...event, list: newList };
});
this.setState({
eventList: eventListNew,
});
};
I have two buttons in my expense manager: 'Add Income' and 'Add Expense' which allow the user to add their income and expenses but they don't work and I'm not sure why. I have onclick listeners for both of the 'add expense' and 'add income buttons. What do I need to change in my JS code to solve this?
const uuidv4 = require('uuid/v4')
// Read existing expenses from localStorage
const getSavedExpenses = () => {
const expensesJSON = localStorage.getItem('expenses')
try {
return expensesJSON ? JSON.parse(expensesJSON) : []
} catch (e) {
return []
}
}
const expenses = getSavedExpenses()
// Save expenses to localStorage
const saveExpenses = (expenses) => {
localStorage.setItem('expenses', JSON.stringify(expenses))
}
const displayExpenses = (title, expensesJSON) => {
let html = `<div class="text-expense">`
for (let i = 0; i < expensesJSON.length; i++) {
html += `<textarea type="text" id="description" name="description"></textarea>`
}
html += `</div>`
title.innerHTML = html;
}
// Create account
const account = {
name: 'aaa',
expenses: [],
income: [],
addExpense: function (description, amount) {
this.expenses.push({
description: description,
amount: amount
})
},
addIncome: function (description, amount) {
this.income.push({
description: description,
amount: amount
})
},
getAccountSummary: function () {
let totalExpenses = 0
let totalIncome = 0
let accountBalance = 0
this.expenses.forEach(function (expense) {
totalExpenses = totalExpenses + expense.amount
})
this.income.forEach(function (income) {
totalIncome = totalIncome + income.amount
})
accountBalance = totalIncome - totalExpenses
}
}
account.addExpense('Rent', 850)
account.addExpense('Food Shopping', 60)
console.log(account.getAccountSummary())
// Listen for new expense to be created
$('#add-expense').on('click', function() {
location.href = '/expense.html'
})
// Listen for expense to be submitted
$('#submit-expense').on('click', function() {
saveExpenses(expenses)
location.href = '/index.html'
})
$('#income').on('click', function() {
location.href = '/income.html'
})
$('#submit-income').on('click', function() {
location.href = '/index.html'
})
$('#add-expense').on('click', function () {
const id = uuidv4()
expenses.push({
id: id,
title: '',
body: ''
})
saveExpenses(expenses)
location.href = `/expense.html#${id}`
displayExpenses(targetElem, getSavedExpenses())
})
Make sure your onclick event handlers are wrapped inside a onload handler (or jQuery's $(document).ready), so that you are sure your buttons are loaded before $(some_selector) is called, something like:
onload = function() {
// Listen for new expense to be created
$('#add-expense').on('click', function() {
location.href = '/expense.html'
})
// Listen for expense to be submitted
$('#submit-expense').on('click', function() {
saveExpenses(expenses)
location.href = '/index.html'
})
$('#income').on('click', function() {
location.href = '/income.html'
})
$('#submit-income').on('click', function() {
location.href = '/index.html'
})
$('#add-expense').on('click', function () {
const id = uuidv4()
expenses.push({
id: id,
title: '',
body: ''
})
saveExpenses(expenses)
location.href = `/expense.html#${id}`
displayExpenses(targetElem, getSavedExpenses())
})
}
I am using Choices library for dropdown. What I need to do is to retain the selected value of dropdown and show it as soon as the page reloads before submitting. Right now I am able to store the value using sessionStorage. But how can I show it in the choices dropdown as the default option once the page reloads? I read the documentation but not able to figure out how to pass the default value.
document.querySelectorAll('.myDropdown').forEach(selectBox => {
choicesElements = new Choices(selectBox, { addItemText: ['Yes'], sortFn: (a, b) => a < b } );
selectBox.addEventListener('change', () => {
// code to populate choices
}
}
let marks_dropdown = document.querySelector('.myDropdown');
marks_dropdown_id.addEventListener("change",function() {
var choices_item_selectable = document.querySelector('.choices__item.choices__item--selectable')
storeSelectedItem(choices_item_selectable.innerText);
}
function storeSelectedItem(innertext) {
sessionStorage.setItem('innertext', innertext);
}
let innertext = sessionStorage.getItem('innertext');
if (innertext) {
let new_choices_item_selectable = document.querySelector('.choices__item.choices__item--selectable');
new_choices_item_selectable.innerText = innertext;
}
I solved the issue as below.
assignChoicesElements = () => {
document.querySelectorAll('.myDropdown').forEach(selectBox => {
choicesElements['some-key'] = new Choices(selectBox, { sortFn: (a, b) => a < b });
selectBox.addEventListener('change', () => {
let reqdValue = selectBox.value;
if(reqdValue != '') {
storeSelectedItem(reqdValue);
}
});
let elementsObject = {};
document.querySelectorAll('unMarked').forEach(unmarkedElement => {
elementsObject['some_key'] = //whatever value to be stored;
});
choicesElements['some-key'].clearStore();
choicesElements['some-key'].setChoices(getPossibleCombinations(elementsObject), 'value', 'label', false);
};
getPossibleCombinations = (jsonObject) => {
var possibleCombinations = {}
Object.entries(jsonObject).forEach(([key, value]) => {
var newPossibleCombinations = possibleCombinations
Object.entries(possibleCombinations).forEach(([key, value]) => {
let newValue = value
newPossibleCombinations['some-value'] = newValue
})
newPossibleCombinations['key'] = ['some-value']
possibleCombinations = newPossibleCombinations
})
var formatedPossibleCombinations = []
Object.entries(possibleCombinations).forEach(([key, value]) => {
// Here is the change. Get the stored value and while creating the list of values, add selected: true to the value if it is found in sessionStorage.
let sessionStorageValue = sessionStorage.getItem('stored_item')
if (sessionStorageValue) {
formatedPossibleCombinations.push({ label: key, value: value, selected: true })
}
})
return formatedPossibleCombinations
}
function storeSelectedItem(value) {
sessionStorage.clear();
sessionStorage.setItem('stored_item', value);
}
This code is more than required for the question. But I have added it just in case if anyone finds it useful.
Hey guys I have this code that fetches data from database usin axios, and in the .then() function I set a data property, watch doesnt trigger. Here is some code that I currently have. And thank you in advance!
export default {
name: '..',
data() {
return {
autocompleteOn: false
}
},
watch: {
autocompleteOn(oldVal, newVal) {
console.log('autocomplet') // doesnt trigger this
}
},
methods: {
fetchAutocompleteResults: _.debounce((filter) => {
let $this = this;
let data = {
filter: filter,
page: $this.page
};
filter.resources.response = [];
filter.loading = true;
axios.post(BASE_URL + '/search/filter', data).then(function(response) {
if (response.data.length) {
filter.autocompleteOn = true;
$this.autocompleteOn = true;
filter.resources.response = filter.resources.response.concat(response.data);
$this.currentFilter = filter;
$this.page++;
console.log($this.autocompleteOn); // this is correct
}
filter.loading = false;
});
}, 300)
}
}
The debounce with an arrow function is making the this be something other than the Vue instance (e.g. window).
Instead of:
methods: {
fetchAutocompleteResults: _.debounce((filter) => {
Use:
methods: {
fetchAutocompleteResults: _.debounce(function (filter) {
// ^^^^^^^^ ^^^
Demo:
new Vue({
el: '#app',
data() {
return {
autocompleteOn: false
}
},
watch: {
autocompleteOn(oldVal, newVal) {
console.log('autocomplet') // doesnt trigger this
}
},
methods: {
fetchAutocompleteResults: _.debounce(function (filter) { // CHANGED from arrow function
let $this = this;
let data = {
filter: filter,
page: $this.page
};
filter.resources.response = [];
filter.loading = true;
// changed data for demo
data = [{title: 'foo', body: 'bar', userId: 1}];
// changed URL for demo
axios.post('https://jsonplaceholder.typicode.com/posts', data).then(function(response) {
if (response.data.length) {
filter.autocompleteOn = true;
$this.autocompleteOn = true;
filter.resources.response = filter.resources.response.concat(response.data);
$this.currentFilter = filter;
$this.page++;
console.log($this.autocompleteOn); // this is correct
}
filter.loading = false;
});
}, 300)
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>
<div id="app">
<button #click="fetchAutocompleteResults({resources: {}})">fetchAutocompleteResults</button>
</div>