I have made a TODO app and added a counter to keep a count of the items in the list. If the counter hits zero, I've set it to re-show a message 'You currently have no tasks. Use the input field above to start adding.'
if(count === 0){
noTasksText.classList.remove('d-none');
}
In the console I print out the div and it doesn't have d-none in the class list any more which is what I want, however, in the actual DOM it does.
Here is a full example - https://codepen.io/tomdurkin/pen/LYdpXKJ?editors=1111
I really can't seem to work this out. I can't seem to interact with that div when the counter becomes zero, however I can get console logs etc to show when expected.
Any help would be appreciated!
const mainInput = document.querySelector('#main-input');
const todoContainer = document.querySelector('#todo-container');
const errorText = document.querySelector('#js-error');
const noTasksText = document.querySelector('.js-no-tasks')
let tasks = [];
let count = 0;
// focus input on load
window.onload = () => {
mainInput.focus();
const storedTasks = JSON.parse(localStorage.getItem('tasks'));
if (storedTasks != null && storedTasks.length > 0) {
// set count to number of pre-existing items
count = storedTasks.length
// hide the 'no tasks' text
noTasksText.classList.add('d-none');
// overwrite tasks array with stored tasks
tasks = storedTasks;
tasks.forEach(task => {
// Build the markup
const markup = `
<div class="js-single-task single-task border-bottom pt-2 pb-2">
<div class="row">
<div class="col d-flex align-items-center js-single-task-name">
<h5 class="mb-0" data-title="${task}">${task}</h5>
</div>
<div class="col d-flex justify-content-end">
<button class="js-remove-task d-block btn btn-danger">Remove Item</button>
</div>
</div>
</div>`;
// Append it to the container
todoContainer.innerHTML += markup;
});
} else {
if (noTasksText.classList.contains('d-none')) {
noTasksText.classList.remove('d-none');
}
}
};
// event listener for 'enter on input'
mainInput.addEventListener("keydown", e => {
// if error is showing, hide it!
if (!errorText.classList.contains('d-none')) {
errorText.classList.add('d-none');
}
if (e.key === "Enter") {
// Get the value of the input
let inputValue = mainInput.value;
if (inputValue) {
// Build the markup
const markup = `
<div class="js-single-task border-bottom pt-2 pb-2">
<div class="row">
<div class="col d-flex align-items-center js-single-task-name">
<h5 class="mb-0" data-title="${inputValue}">${inputValue}</h5>
</div>
<div class="col d-flex justify-content-end">
<button class="js-remove-task d-block btn btn-danger">Remove Item</button>
</div>
</div>
</div>`;
// hide 'no tasks' text
noTasksText.classList.add('d-none');
// Append it to the container
todoContainer.innerHTML += markup;
// Push value to 'tasks' array
tasks.push(inputValue);
// Put in localStorage
textTasks = JSON.stringify(tasks);
localStorage.setItem("tasks", textTasks);
// Reset the value of the input field
mainInput.value = '';
// add 1 to the count
count++
} else {
// Some very basic validation
errorText.classList.remove('d-none');
}
}
});
// remove task
todoContainer.addEventListener('click', (e) => {
// Find the button in the row that needs removing (bubbling)
const buttonIsDelete = e.target.classList.contains('js-remove-task');
if (buttonIsDelete) {
// Remove the HTML from the screen
e.target.closest('.js-single-task').remove();
// Grab the name of the single task
let taskName = e.target.closest('.js-single-task').querySelector('.js-single-task-name h5').getAttribute('data-title');
// filter out the selected word
tasks = tasks.filter(item => item != taskName);
textTasks = JSON.stringify(tasks);
localStorage.setItem("tasks", textTasks);
// update counter
count--
// check if counter is zero and re-show 'no tasks' text if true
if (count === 0) {
noTasksText.classList.remove('d-none');
console.log(noTasksText);
}
}
});
body {
background: #e1e1e1;
}
<div class="container">
<div class="row d-flex justify-content-center mt-5">
<div class="col-10 col-lg-6">
<div class="card p-3">
<h2>To dos</h2>
<p>
Use this app to keep a list of things you need to do
</p>
<input class="form-control" id="main-input" type="text" placeholder="Type your todo and hit enter..." class="w-100" />
<small id="js-error" class="text-danger d-none">
Please type a value and press enter
</small>
<hr />
<h4 class="mb-5">Your 'To dos'</h4>
<div id="todo-container">
<!-- todos append in here -->
<div class="js-no-tasks">
<small class="d-block w-100 text-center mb-3">
<i>
You currently have no tasks. Use the input field above to start adding
</i>
</small>
</div>
</div>
</div>
<!-- /card -->
</div>
</div>
</div>
Upon setting innerHTML by using += innerHTML the node noTasksText is lost, because browser processes the whole new set innerHTML and creates new objects. You can either retrieve noTasksText again after that, or append nodes using todoContainer.appendChild. I forked your pen and solved it with the latter solution.
https://codepen.io/aghosey/pen/wvmGwWd
You can do the following, it will work (here innerHTML is changing the DOM, so I added an extra function to recalculate elements after DOM is changed due to innerHTML):
var mainInput = document.querySelector("#main-input");
var todoContainer = document.querySelector("#todo-container");
var errorText = document.querySelector("#js-error");
var noTasksText = document.querySelector(".js-no-tasks");
let tasks = [];
let count = 0;
function getAllElements() {
mainInput = document.querySelector("#main-input");
todoContainer = document.querySelector("#todo-container");
errorText = document.querySelector("#js-error");
noTasksText = document.querySelector(".js-no-tasks");
}
// focus input on load
window.onload = () => {
mainInput.focus();
var storedTasks = JSON.parse(localStorage.getItem("tasks"));
if (storedTasks != null && storedTasks.length > 0) {
// set count to number of pre-existing items
count = storedTasks.length;
// hide the 'no tasks' text
noTasksText.classList.add("d-none");
// overwrite tasks array with stored tasks
tasks = storedTasks;
tasks.forEach((task) => {
// Build the markup
const markup = `
<div class="js-single-task single-task border-bottom pt-2 pb-2">
<div class="row">
<div class="col d-flex align-items-center js-single-task-name">
<h5 class="mb-0" data-title="${task}">${task}</h5>
</div>
<div class="col d-flex justify-content-end">
<button class="js-remove-task d-block btn btn-danger">Remove Item</button>
</div>
</div>
</div>`;
// Append it to the container
todoContainer.innerHTML += markup;
getAllElements();
});
} else {
if (noTasksText.classList.contains("d-none")) {
noTasksText.classList.remove("d-none");
}
}
};
// event listener for 'enter on input'
mainInput.addEventListener("keydown", (e) => {
// if error is showing, hide it!
if (!errorText.classList.contains("d-none")) {
errorText.classList.add("d-none");
}
if (e.key === "Enter") {
// Get the value of the input
let inputValue = mainInput.value;
if (inputValue) {
// Build the markup
const markup = `
<div class="js-single-task border-bottom pt-2 pb-2">
<div class="row">
<div class="col d-flex align-items-center js-single-task-name">
<h5 class="mb-0" data-title="${inputValue}">${inputValue}</h5>
</div>
<div class="col d-flex justify-content-end">
<button class="js-remove-task d-block btn btn-danger">Remove Item</button>
</div>
</div>
</div>`;
// hide 'no tasks' text
noTasksText.classList.add("d-none");
// Append it to the container
todoContainer.innerHTML += markup;
getAllElements();
// Push value to 'tasks' array
tasks.push(inputValue);
// Put in localStorage
textTasks = JSON.stringify(tasks);
localStorage.setItem("tasks", textTasks);
// Reset the value of the input field
mainInput.value = "";
// add 1 to the count
count++;
} else {
// Some very basic validation
errorText.classList.remove("d-none");
}
}
});
// remove task
todoContainer.addEventListener("click", (e) => {
// Find the button in the row that needs removing (bubbling)
const buttonIsDelete = e.target.classList.contains("js-remove-task");
if (buttonIsDelete) {
// Remove the HTML from the screen
e.target.closest(".js-single-task").remove();
// Grab the name of the single task
let taskName = e.target
.closest(".js-single-task")
.querySelector(".js-single-task-name h5")
.getAttribute("data-title");
// filter out the selected word
tasks = tasks.filter((item) => item != taskName);
textTasks = JSON.stringify(tasks);
localStorage.setItem("tasks", textTasks);
// update counter
count--;
// check if counter is zero and re-show 'no tasks' text if true
if (count === 0) {
noTasksText.classList.remove("d-none");
console.log(noTasksText);
}
}
});
body {
background: #e1e1e1;
}
<div class="container">
<div class="row d-flex justify-content-center mt-5">
<div class="col-10 col-lg-6">
<div class="card p-3">
<h2>To dos</h2>
<p>
Use this app to keep a list of things you need to do
</p>
<input class="form-control" id="main-input" type="text" placeholder="Type your todo and hit enter..." class="w-100" />
<small id="js-error" class="text-danger d-none">
Please type a value and press enter
</small>
<hr />
<h4 class="mb-5">Your 'To dos'</h4>
<div id="todo-container">
<!-- todos append in here -->
<div class="js-no-tasks">
<small class="d-block w-100 text-center mb-3">
<i>
You currently have no tasks. Use the input field above to start adding
</i>
</small>
</div>
</div>
</div>
<!-- /card -->
</div>
</div>
</div>
Related
I have one button that shows all my products in the webpage, each product is loaded from an array and inserted with innerHTML, each product is inserted with one button, i tried to assing one function for those buttons, but nothing happens:
Show.addEventListener('click', () => {
flag = flag + 1
if (flag == 2) {
document.getElementById('ItemsID').disabled = true
}
console.log(ItemsID.options[ItemsID.selectedIndex].value)
if (ItemsID.options[ItemsID.selectedIndex].value == "Vuelos") {
document.getElementById('ShowItems').disabled = true
VUELOSPROMOCIONES.forEach((VUELOSPROMOCIONES, indice) => {
div_Vuelos.innerHTML += `
<div class="card" id="persona${indice + 1}" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">Vuelo Promocional ${indice + 1}</h5>
<p>Origen: ${VUELOSPROMOCIONES.origen}</p>
<p>Destino: ${VUELOSPROMOCIONES.destino}</p>
<p>Fecha Ida: ${VUELOSPROMOCIONES.FechaIda}</p>
<p>Fecha Vuelta: ${VUELOSPROMOCIONES.FechaVuelta}</p>
<p>Precio: ${VUELOSPROMOCIONES.precio}</p>
</div>
</div>
**<button id="Enviar">Click me</button>**
`
})
} else if (ItemsID.options[ItemsID.selectedIndex].value == "Autos") {
PROMOCIONESAUTOS.forEach((PROMOCIONESAUTOS, indice) => {
document.getElementById('ShowItems').disabled = true
div_Autos.innerHTML += `
<div class="card" id="Autos Disponibles ${indice + 1}" style="width: 18rem;">
<div class="card-title">Persona ${indice + 1}</h5>
<p>Modelo: ${PROMOCIONESAUTOS.modelo}</p>
<p>Marca: ${PROMOCIONESAUTOS.marca}</p>
<p>Color: ${PROMOCIONESAUTOS.color}</p>
</div>
</div>
<button id="Enviar">Click me</button>
`
})
}
})
if i insert the same button in the html, it works with the same id
In the below link, there is a add more button, i want the add more to create the same input field with a delete button associated with it, but i would like to do it all with native js if possible.
https://codepen.io/aazim-khaki/pen/vYZmMRq
Current JS :
$(function() {
$(".btn-copy").on('click', function() {
var ele = $(this).closest('.example-2').clone(true);
ele.find('input').val('')
if (ele.find('button').length < 2) {
let btn = document.createElement("button");
btn.innerHTML = "Delete";
btn.onclick = (e) => {
e.preventDefault()
ele.remove()
}
ele[0].appendChild(btn);
}
$(this).closest('.example-2').after(ele);
})
})
Delegate
I moved the form tag and gave the button a delete class
window.addEventListener("load", function() {
document.querySelector(".row").addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains('delete')) {
tgt.closest('.example-2').remove()
} else if (tgt.classList.contains('btn-copy')) {
const ele = tgt.closest(".example-2").cloneNode(true);
ele.querySelector("input").value = "";
if (ele.querySelectorAll("button").length < 2) {
let btn = document.createElement("button");
btn.innerHTML = "Delete";
btn.classList.add("delete");
ele.appendChild(btn);
}
tgt.closest(".card-body").appendChild(ele)
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">Add Class</h5>
</div>
<form action="#">
<div class="card-body">
<div class="example-2 form-group row">
<!--<label class="col-form-label col-md-2">Input Addons</label>-->
<div class="col-xs-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Class Name</span>
</div>
<input class="form-control" type="text">
<div class="input-group-append">
<button class="btn-copy btn btn-primary" type="button">Add More</button>
</div>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xs-2">
<button class="btn btn-primary" type="button">Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
Use a container and event delegation so you only use one listener rather than attaching a listener to each remove button.
This is a very simple example but the principles are the same.
// Cache your elements
const container = document.querySelector('#container');
const add = document.querySelector('button');
// Add your container an add button listeners
container.addEventListener('click', handleEvent, false);
add.addEventListener('click', handleAdd(), false);
// If a remove button is clicked, find the
// the closest div wrapper and remove it from
// the container
function handleEvent(e) {
const { id } = e.target.dataset;
const row = e.target.closest('.row');
container.removeChild(row);
}
// `handleAdd` returns a function (closure) that
// is used for the add listener rather than
// maintaining a global variable.
// We initialise the id at this point
function handleAdd(id = 0) {
// And now return the function that will be called
// when the add button is clicked
// For the purposes of this example it simply adds new HTML
// to the container, and then increases the id
return function() {
const html = `<div class="row"><input value="${id}" /><button data-id="${id}">Remove</button></div>`;
container.insertAdjacentHTML('beforeend', html);
++id;
}
}
<button>Add</button>
<div id="container"></div>
sorry I am new in angular and StackOverflow
I have 3 divs and I want to add background-color: red to all divs which status is requesting payment. It should make background color to the second and the third div but it makes to only the third
html
<div class="col-md-4 mb-4" *ngFor="let invoice of Invoices let i=index">
<div id="{{'activeInvoice'+i}}" class=" d-flex justify-content-between align-items-center w-100 card-header ">
<strong class="text-gray-dark">{{langVar.Invoice}} {{invoice.invoiceNumber}}</strong>
<i class="fa fa-info-circle" title="show Details" aria-hidden="true" (click)="showInvoiceDetailsModal(invoice.id)"></i>
</div>
</div>
for (var i = 0; i <= this.Invoices.length-1; i++) {
console.log("invoice num", this.Invoices[i].invoiceNumber);
console.log("order", this.Invoices[i].order.length);
for (var j = 0; j <= this.Invoices[i].order.length - 1; j++) {
console.log("STATUS", this.Invoices[i].order[j].status);
if (this.Invoices[i].order[j].status == 'requestingPayment') {
var elementValue = 'activeInvoice'+i;
setTimeout(() => {
document.getElementById(elementValue).classList.add("cardBackGround");
}, 1000);
}
else {}
}
}
you can conditionally add CSS classes in angular using ngClass
<div [ngClass]={'cardBackGround': true }> </div>
// apply your condition instead of true
i have this HTML for show list of users :
<div *ngFor="let item of users " [ngClass]="{'highlight': item.isDeleted }"
class="d-flex selected-list-items mt-3">
<div class="col-md-5 col-lg-5 col-xs-5 col-sm-5 col-xl-5">
<label>{{item.displayName}}</label>
</div>
<div class="col-md-5 col-lg-5 col-xs-5 col-sm-5 col-xl-5">
<label> {{ getEnumTranslate(item.title)}}</label>
</div>
<div class="justify-content-center col-md-2 col-lg-2 col-xs-2 col-sm-2 col-xl-2">
<button (click)="deleteUser(item.userId)" mat-button>
<mat-icon aria-label="Delete" color="accent">delete</mat-icon>
</button>
</div>
</div>
and i create a validation with private transport: BehavorSubject, for do this :
when the transport is changes must be execute this code :
this.transport.listValue$.subscribe(data => {
if (data != null) {
data.forEach(element => {
let user = this.users.find(x => x.userId = element);
if (user != null) {
user.isDeleted = true;
}
});
}
});
and in html change the background color of find user in this code let user = this.users.find(x => x.userId = element); .
now my problem is here :
when transport is change it run code for change background and find that user but it just change background of first element in this HTML code , specific user in the Third element but it change first element In the event that need change Third element in HTML .
how can i solve this problem ?
I have updated the code that should work for your scenario. Here I am mutating the actual data inside users array. Please check and let me know if it resolves your issue.
this.transport.listValue$.subscribe(data => {
if(data != null) {
for (const user of this.users) {
if(data.includes(user.userId)) {
user.isDeleted = true
}
}
}
});
I am making angular application with angular dynamic form where i am using ng-select library.
The HTML with select:
<div *ngFor="let question of questions" class="form-row {{question.class}}">
<ng-container *ngIf="question.children">
<div [formArrayName]="question.key" class="w-100">
<div *ngFor="let item of form.get(question.key).controls; let i=index" [formGroupName]="i" class="row mt-1">
<div *ngFor="let item of question.children" class="{{item.class}} align-middle">
<div class="w-100">
<dynamic-form-builder [question]="item" [index]="i" [form]="form.get(question.key).at(i)"></dynamic-form-builder>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-6 col-sm-12 col-lg-6 col-md-6">
<div class="form-label-group" *ngIf="showTemplateDropdown">
<ngi-select placeholder="Select Template" [required]="true" [hideSelected]="false" [multiple]="true" [items]="templateList"
dropdownPosition="down" bindLabel="name" bindValue="id" (add)="getTemplateValues($event)" (remove)="onRemove($event)">
</ngi-select>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-6 col-sm-12 col-lg-6 col-md-6">
</div>
<div class="col-6 col-sm-12 col-lg-6 col-md-6 text-right">
<div class="btn-group float-right">
<button class="btn btn-primary btn-round btn-fab mat-raised-button" mat-min-fab="" mat-raised-button="" type="button"
(click)="addControls('template_properties')">
<span class="mat-button-wrapper"><i class="material-icons mt-2">add</i></span>
<div class="mat-button-ripple mat-ripple" matripple=""></div>
<div class="mat-button-focus-overlay"></div>
</button>
<button class="btn btn-primary btn-round btn-fab mat-raised-button" mat-min-fab="" mat-raised-button="" type="button"
(click)="removeControls('template_properties')">
<span class="mat-button-wrapper"><i class="material-icons mt-2">remove</i></span>
<div class="mat-button-ripple mat-ripple" matripple=""></div>
<div class="mat-button-focus-overlay"></div>
</button>
</div>
</div>
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="!question.children">
<div class="w-100">
<dynamic-form-builder [question]="question" [form]="form"></dynamic-form-builder>
</div>
</ng-container>
</div>
Here the [items]="templateList" has the following,
[{"id":"5bebba2c20ccc52871509d56","name":"Template One"},
{"id":"5bebba5720ccc52871509d57","name":"Template Two"},
{"id":"5bebba8d20ccc52871509d5d","name":"Template Three"}]
I am having (change)="getTemplateValues($event)" event for detecting each change happen when we select an item from dropdown.
The change event function Edited,
getTemplateValues(e) {
this.dynamicFormService.getRest("url" + '/' + e.id").subscribe(res => {
try {
if (res.status === "true") {
res.data.template_properties.forEach(element => {
this.templateArray.push(element);
});
this.form = this.qcs.toFormGroup(this.questions);
for (let i = 0; i < this.templateArray.length; i++) {
this.addControls('template_properties');
}
let propertiesArray = [];
this.templateArray.forEach(element => {
propertiesArray.push(element);
});
this.form.patchValue({
'template_properties': propertiesArray
});
} else {
}
}
catch (error) {
}
})
}
console.log(this.propertiesArray) gives the following,
[{"property_name":"Property one","property_type":4,"property_required":true,"property_origin":1},{"property_name":"Property one","property_type":5,"property_required":true,"property_origin":1}]
In the below image i have deleted template three but the template three properties still showing in it..
Here first i am filtering the data first and ignoring the duplicates and each time i am sending the newly selected values alone to the service and fetching the data related to the id element.id.
And using this.addControls('template_properties') to make open the number of rows, and elements will get patched to the form template_properties.
this.form.patchValue({
'template_properties': propertiesArray
});
As of now everything working fine..
The problem actually arise from here:
If we delete a selected list from dropdown, (say i have selected all three template and i have deleted the template two then that particular template's template_properties needs to get deleted..
I have tried with (remove)="onRemove($event)" but its not working because while remove data, the (change) function also calls..
How can i remove the template_properties with this.removeControls('template_properties'); of particular deleted template name in the change event or remove event..
Remove Function:
onRemove(e) {
console.log(e);
this.dynamicFormService.getRest("url" + '/' + e.value.id").subscribe(res => {
try {
if (res.status === "true") {
for (let i = 0; i < res.data.template_properties.length; i++) {
this.removeControls('template_properties');
}
} else {
}
}
catch (error) {
}
})
}
Remove Control:
removeControls(control: string) {
let array = this.form.get(control) as FormArray;
console.log(array)
array.removeAt(array.length);
}
console.log(array) gives,
It should be pretty easy fix. Use (add) instead of (change) in ngi-select.
onRemove(e) {
console.log(e);
this.dynamicFormService.getRest("url" + '/' + e.value.id").subscribe(res => {
try {
if (res.status === "true") {
this.form = this.qcs.toFormGroup(this.questions);
// Issue is here, you should remove only specific record
// which is being passed from function `e`
for (let i = 0; i < res.data.template_properties.length; i++) {
this.removeControls('template_properties');
}
let propertiesArray = [];
this.templateArray.forEach(element => {
propertiesArray.push(element);
});
this.form.patchValue({
'template_properties': propertiesArray
});
} else {
}
}
catch (error) {
}
})
}
Pass the index in removeControls where you want to remove the element from.
removeControls(control: string, index:number) {
let array = this.form.get(control) as FormArray;
console.log(array)
array.removeAt(index);
}
console.log(array) gives,