I'm working on a simple project where I need to make an item in a list editable and then update a JS item to store this.
I'm using Content Editable = True, and it works when I comment out my handleItemEdit function, but when I turn it on, I can only insert one character at a time, forcing me to keep clicking to edit.
Clearly this problem stems from my function, but I can't seem to figure out why.
//Responsible for listening for an edit and updating my object with the new text.
function handleEditItem() {
$('.js-shopping-item').on('input', function(event) {
const itemIndex = getItemIndexFromElement(event.currentTarget); //assigning the index of the the editted item to itemIndex
const updatedItem = STORE.items[itemIndex];
updatedItem.name = event.currentTarget.innerHTML;
renderShoppingList();
});
}
//Returns the index of an Item in the Store
function getItemIndexFromElement(item) {
const itemIndexString = $(item)
.closest('.js-item-index-element')
.attr('data-item-index');
return parseInt(itemIndexString, 10);
}
//Function responsible for returning the template HTHML to insert into the html.
function generateItemElement(item) {
let itemIndex = STORE.items.indexOf(item);
return `
<li class="js-item-index-element" data-item-index="${itemIndex}">
<span contentEditable='true' class="shopping-item js-shopping-item ${item.checked ? 'shopping-item__checked' : ''}">${item.name}</span>
<div class="shopping-item-controls">
<button class="shopping-item-toggle js-item-toggle">
<span class="button-label">check</span>
</button>
<button class="shopping-item-delete js-item-delete">
<span class="button-label">delete</span>
</button>
</div>
</li>`;
}
Related
In my angular application I have some iteration items and saving the items based on adding the items.
.component.html
<ng-container *ngFor="let categoryDetail of selectedCategoryDetails">
<div class="__header">
<div>
<b>{{ categoryDetail.category }}</b>
</div>
</div>
<div
class="clinical-note__category__details"
*ngIf="categoryDetail.showDetails">
<ul>
<li class="habit-list"
*ngFor="let habits of categoryDetail.habitDetails" >
<div class="target-details">
<b>{{ clinicalNoteLabels.target }}: </b
><span class="habit-list__value">{{ habits.target }}</span>
</div>
</li>
</ul>
<div class="habit-footer">
<span class="m-l-10"
[popoverOnHover]="false"
type="button"
[popover]="customHabitPopovers"><i class="fa fa-trash-o" ></i> Delete</span>
</div>
<div class="clinical-note__popoverdelete">
<popover-content #customHabitPopovers [closeOnClickOutside]="true">
<h5>Do you want to delete this habit?</h5>
<button
class="btn-primary clinical-note__save" (click)="deletedata(index);customHabitPopovers.hide()">yes </button>
</popover-content></div>
</div>
</ng-container>
.component.ts
public saveHealthyHabits() {
let isCategoryExist = false;
let categoryDetails = {
category: this.clinicalNoteForm.controls.category.value,
habitDetails: this.healthyHabits.value,
showDetails: true,
};
if (this.customHabitList.length) {
categoryDetails.habitDetails = categoryDetails.habitDetails.concat(
this.customHabitList
);
this.customHabitList = [];
}
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category === categoryDetails.category) {
isCategoryExist = true;
selectedCategory.habitDetails = selectedCategory.habitDetails.concat(
categoryDetails.habitDetails
);
}
});
}
if (!this.selectedCategoryDetails || !isCategoryExist) {
this.selectedCategoryDetails.push(categoryDetails);
}
this.clinicalNoteForm.patchValue({
category: null,
});
this.healthyHabits.clear();
}
public deletedata(index:number){
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
this.selectedCategoryDetails.splice(index, 1);
}}
From the above code I have saved the data based on adding the items as above and my requirement is when we click on the delete(it will show the popup having the button yes implemented in anbove code).
when we click on the yes button from list of items, I have to remove the particular item
When I tried removing ,It is only deleting the first item instead of clicked one
Can anyone help me on the same
The logic for deletion is incorrect. The splice mutates the original array, and you are applying the loop for deletion, which keeps on iterating over the array and deleting the array elements based on index, instead of deleting single matched index element.
Example -
const categories = [
1,
2,
3,
4
];
function removal(i) {
categories.forEach((category, index) => {
categories.splice(i, 1);
});
console.log('----Categories-->', categories);
}
removal(0);
Categories Array
First Iteration [index = 0]
[1,2,3,4]
Loop Starts Iterating from 1
Second Iteration [index = 1]
[2,3,4]
Loop Starts Iterating from 3
Third Iteration [index = 2]
[3,4]
Stop
Instead you can use filter function.
public deletedata(index:number){
this.selectedCategoryDetails = this.selectedCategoryDetails.filter((_, i) => i! == index);
}
Note - I would recommend to delete the categories based on some identifier like id instead of index because the array elements position can get changed.
Instead of passing index to deleteData method, you can pass the category object.
public deletedata(category){
this.selectedCategoryDetails = this.selectedCategoryDetails.filter((c) => c.id! == category.id);
}
I'm building a To Do list app and have a question regarding OOP and JavaScript. I want to create a value in the Constructor that holds my taskBody which contains the HTML and template literal that will be assigned by either the input value or an eventual population from local storage. My goal is to re-use this HTML in two separate functions, but I'm stuck with the template literal.
class Task {
constructor() {
let taskValue //Initializing a variable
this.taskBody = `<div class="task">
<span>${taskValue}</span> //Template Literal
<span class="actions">
<a class="edit-button" title="Edit Task">Edit</a>
<button class="complete-button" title="Complete Task"><i class="fas fa-check"></i></button>
</span>
</div>`;
}
addTask = () => {
//Prevent empty task
if (this.input.value == "") {
this.setPlaceholder("Please Enter A Task");
return;
}
this.taskValue = this.input.value; //Trying to reassign taskValue to the input value
this.results.innerHTML += this.taskBody; //Trying to grab the HTML from the constructor and populate with taskValue able
ls.setLS(this.taskValue); //setting the Local Storage the Task Value, which works
};
}
I expect if I type "Stack Overflow" in the to-do list, "Stack Overflow" populates in the HTML and the Local Storage, however, it only populates in the Local Storage. The todo item is either undefined, null, or empty.
I've tried using this.taskValue, let taskValue = "", and let taskValue = null, but I get the results described above. Where am I going wrong, and more specifically, how can I reuse the HTML in different functions?
Here's a CodePen where you can see the issue:
Codepen
When you first instantiate the Task, the value of the this.taskBody is set as below:
<div class="task">
<span>undefined</span>
<span class="actions">
<a class="edit-button" title="Edit Task">Edit</a>
<button class="complete-button" title="Complete Task"><i class="fas fa-check"></i></button>
</span>
</div>
with undefined value, because at the moment of instantiation, the taskValue is undefined.
If your todo list items are added dynamically (which is the case), consider having a function which will enable dynamic replacement, like:
getTaskBody = item => `<div class="task">
<span>${item}</span>
<span class="actions">
<a class="edit-button" title="Edit Task">Edit</a>
<button class="complete-button" title="Complete Task"><i class="fas fa-check"></i></button>
</span>
</div>`;
and use it later in line 123, instead of:
this.results.innerHTML += this.taskBody;
do:
this.results.innerHTML += getTaskBody(this.taskValue);
Please excuse my spaghetti code. I'm trying to learn.
When I click on the add food button, it adds a food item to the nutrition list. However, when I choose another food and add that, the previous food item is added along with the current chosen food. Again, if I continue the process, all of the previously added items are added, including the current item. So the result is a list of previously added food and it's terrible. It seems that whenever I click on a food item, all the previously clicked items are being "held" or still referenced somehow. I don't understand why this is happening.
I need to make it so that only one food item is added at a time. I've researched a lot but can't find solutions similar to my problem.
Here is my code. I've left comments around the problem area.
JavaScript:
const callApi = (url) => {
fetch(url).then(function(response) {
return response.json();
})
.then(function (data) {
if (data.hits.length > 0) {
data.hits.forEach((foodItem) => {
makeFoodInstance(foodItem);
});
} else {
displayResults.innerHTML = "No results match this search."
}
});
}
const makeFoodInstance = (f) => {
let food = new Food(f.fields.item_id, f.fields.item_name);
food.carbohydrates(f.fields.nf_dietary_fiber, f.fields.nf_sugars,
f.fields.nf_total_carbohydrate);
food.fats(f.fields.nf_saturated_fat, f.fields.nf_polyunsaturated_fat,
f.fields.nf_monounsaturated_fat, f.fields.nf_trans_fatty_acid,
f.fields.nf_total_fat);
food.minerals(f.fields.nf_calcium_dv, f.fields.nf_sodium,
f.fields.nf_iron_dv);
food.vitamins(f.fields.nf_vitamin_a_dv, f.fields.nf_vitamin_c_dv);
food.protein(f.fields.nf_protein);
food.calories(f.fields.nf_calories, f.fields.nf_calories_from_fat);
food.water(f.fields.nf_water_grams);
food.servings(f.fields.nf_servings_per_container,
f.fields.nf_serving_size_qty, f.fields.nf_serving_size_unit);
displayResults.children[0].innerHTML = '';
resultsList.insertAdjacentHTML('afterbegin', `<li class="results-list-
item">${food.name}</li>`);
getNutrition(food);
}
const getNutrition = (nutrition) => {
let foodResultItem = document.querySelector('.results-list-item');
let nutritionListItem =
`<li class="nutrition-list-item">
<p>${nutrition.name}</p>
<p>Sugars ${nutrition.sugars}</p>
<p>Total Carbohydrates ${nutrition.total_carbs}</p>
<p>Saturated Fat ${nutrition.satured}</p>
<p>Polyunsaturated Fat ${nutrition.polyunsaturated}</p>
<p>Monounsaturated Fat ${nutrition.monounsaturated}</p>
<p>Trans Fat ${nutrition.trans}</p>
<p>Total Fat ${nutrition.total_fat}</p>
</li>
`;
/*
this is where the problem is starting (i think)
passing the nutrition variable to storeFood()
*/
foodResultItem.addEventListener('click', function(e) {
nutritionList.innerHTML = '';
nutritionList.insertAdjacentHTML('afterbegin', nutritionListItem);
storeFood(nutrition);
}, false);
}
const storeFood = (f) => {
addFood.addEventListener('click', (a) => {
if (localStorage) {
localStorage.setItem(f.id, f.name.toString());
userAddedFood.insertAdjacentHTML('afterbegin', `<li class="selected-
food-item">${f.name}</li>`);
} else {
userAddedFood.append('Sorry, storage is full');
}
}, false);
}
HTML:
<div class="container">
<section class="search">
<h1>Health Tracker</h1>
<input type="text" id="search-bar">
<input type="submit" value="search" id="search-btn">
</section>
<br>
<br>
<br>
<section class="results">
<h3>Search results will display here</h3>
<ul id="results-list"></ul>
</section>
<section class="nutrition-results">
<h3>Nutrition Facts</h3>
<ul id="nutrition-list"></ul>
<input type="button" value="add-food" id="add-food-button">
</section>
<section class="selected-food">
<h3>Foods you've eaten today</h3>
<ul class="users-food-result"></ul>
</section>
<section class="user-nutrition-facts">
<h3>Your daily nutrition totals</h3>
<div class="users-nutrition-totals"></div>
</section>
</div>
it looks like every time a foodResultItem gets clicked, you call storeFood() passing in the current nutrition item. storeFood itself creates a click handler on addFood that adds that specific food item to the list. These click handlers for each specific food stick around on the addFood element, even when you create new ones by clicking on a different foodResultItem. So when you click the addFood button, ALL of the event handlers you have added to it run, giving you the unwanted results.
In Vuejs 2.0 I've a following set of data:
const tags = {
Investor:[
{display:"Mutual Fund", value:"Investor - Mutual Funds"},
{display:"Insurance", value:"Investor - Insurance"},
{display:"FII", value:"Investor - FII"},
],
Research:[
{display:"Research - Tier I", value:"Research - Tier I"},
{display:"Research - Tier II", value:"Research - Tier II"},
]
}
I'm having following set of buttons which shows these tags:
<div class="col-sm-4 border-right">
<div>
<button v-for="(obj, key) in tags"
:key="key"
#click.prevent="currentTag = key"
class="btn btn-primary btn-xs">
{{key}}
</button>
</div>
</div>
<div class="col-sm-6">
<div>
<button v-for="tag in tags[currentTag]"
:key="tag"
class="btn btn-xs"
:class="tagClass(tag)"
#click.prevent="selectedTag = tag">
{{tag.display}}
</button>
</div>
</div>
To get the selected tags I'm having a variable named:
currentTag: '',
selectedTag: '',
Now i'm having tagClass(tag) to toggle the class:
tagClass(tag){
return {
'btn-warning': this.selectedTag === tag,
'btn-primary': !(this.selectedTag === tag)
}
},
Now while getting the update page I'm putting values in current tag and selectedTag like this:
this.currentTag = response.data.contact.parentTag
this.selectedTag = response.data.contact.selectedTag
Now I'm able to view the child tags selected from the parent but I'm unable to have selected class in it. I want the data which is being set to seletedTag should have class btn-warning
In backend PHP I'm calculating and passing the value as
$selectedTag['value'] = $contact->tag;
$tags = explode('-', $contact->tag);
$contact->parentTag = $tags[0];
$selectedTag['display'] = $tags[1];
$contact->selectedTag = $selectedTag;
The issue here is that the tagClass method is checking to see if the tag is equal to the selectedTag. Since tag and selectedTag are objects, when you set it manually, selectedTag is never going to be equal to any of your tags. Instead, look for the tag that you have locally that matches the tag that you received from the server.
Wherever you are doing this
this.currentTag = response.data.contact.parentTag
this.selectedTag = response.data.contact.selectedTag
Change it to this
this.currentTag = response.data.contact.parentTag
const selectedTag = response.data.contact.selectedTag
this.selectedTag = this.tags[this.currentTag].find(t => t.value === selectedTag.value)
Example.
I'm using angularJS to build a SPA. I am trying to delete an object from an array in my controller. I am using ng-repeat and can't seem to get my head around this. Here is the related html:
<div class="cat-button" ng-repeat="category in cats" category="category">
<button class=" close-button" ng-click="removeCat()">
<span class="glyphicon glyphicon-remove-sign" aria-hidden=true> </span> </button>{{category.name}}
</div>
This created a div with a button for every object that gets saved to my $scope.cats array. It works fine but I cant figure out how do I use the button in each div to delete that specific object.
When I click on the button , the function on my controller gets called, but this is where I get lost, how do I delete the specific object created dynamically by the user.
This is the related code on my controller:
//Function to delete category
$scope.removeCat = function () {
//I know I have to use splice on my array but how do I Identify the object that needs to be deleted from my array?
};
You can either pass on $index like so:
<button class=" close-button" ng-click="removeCat($index)">
and in your function:
$scope.removeCat = function (index) {
$scope.cats.splice(index,1);
}
or pass the whole item and use indexOf (the saver way)
<button class=" close-button" ng-click="removeCat(category)">
$scope.removeCat = function (item) {
$scope.cats.splice(myArray.indexOf(item), 1);
}
You can pass the index of the item you want to delete in the ng-click function:
<div class="cat-button" ng-repeat="category in cats" category="category">
<button class=" close-button" ng-click="removeCat($index)">
<span class="glyphicon glyphicon-remove-sign" aria-hidden=true> </span> </button>{{category.name}}
</div>
Then you can use this in your Angular controller like this:
$scope.removeCat = function (index) {
$scope.cats.splice(index, 1);
};
Update
Incase you don't want to pass in the index, instead you can also pass in the entire object and locate the index in your controller. The code below is setup to work on all browsers. (Just haven't tested it ;) )
$scope.removeCat = function (cat) {
// Using underscore
var index = _.indexOf($scope.cats, cat);
// Or using a for loop
for(var i = 0; i < $scope.cats.length; i++) {
//Assuming your cat object has an id property
if($scope.cats.id === cat.id) {
index = i;
break;
}
}
};
Or any other way to locate the index of an object in an array.
ng-click="removeCat(category)"
$scope.removeCat = function (categoryToDelete) {
var index = $scope.cats.indexOf(categoryToDelete);
$scope.cats.splice(index, 1);
};