Relevant item not removing Angular 12 - javascript

Tried to remove an item from the array but it's removing the last index of that array, not the relevant one, below is my code. Ex: if I remove the first item from the list then it's removing the last item from the list.
component.ts code
this.items.splice(this.items.indexOf(id));
html code
<span class="close" style="cursor: pointer;" (click)="removeItems(item.Id)">
<i class="far fa-trash-alt fa-2x" style="color: red;"></i>
</span>
List items
I'm using *ngFor loop to get my items. I didn't put that code here.

If you pass negative value to splice the function will remove element(s) from the end.
You need to pass number of items to be removed. So you are looking for this : this.items.splice(this.items.indexOf(id), 1);

If items is a collection of objects then using indexOf with just the id is likely to return -1 every time, which would explain why the last item is being removed.
You would probably need something like
const itemToRemove = this.items.findIndex(i => i.id === id);
if(itemToRemove >= 0) {
this.items.splice(itemToRemove, 1);
}

Related

Trying to build an auto suggestion feature that will accept or parse multiple values and will only accept values that starts with a specific character

So I've been trying to build an auto suggestion feature that will display multiple suggestions that have partial match or value within the inputbox. It should also only start providing suggestions that starts with a specific character which is an # Symbol
Example: Task 1 #james #john
Much like how facebook starts making suggestions when you provide the text with an # Symbol
The code below listens to the values inserted in the inputbox during a keypress. Then it stores those values in array where it will filter values that only contains an # in the beginning. Then it makes a comparison with another array that has those values. Once a match is indicated it will then loop through the names-list element for a match then adds a class called display
The code below is still a work in progress and that I'm still trying to figure it how to finish it with the proper syntax.
let listenOnInput = () => {
let handles = [];
let names = ['#john', '#jake'];
$(".task-label input").on("change keyup paste", function() {
let names = ($(this).val());
let splitHandles = names.split(' ');
$.each(splitHandles, function(i, value) {
if (splitHandles[i].indexOf('#') == 0) {
handles.push(splitHandles[i]);
}
for (let elements of handles) {}
})
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="task-label">
<input type="text">
</div>
<ul class="name-list">
<li class="name name-john">#John</li>
<li class="name name-jake">#John</li>
</ul>
You are on the right track! And almost there.
After splitting all of the names you'll need to compare if one of the names given corresponds somehow with the right element in your HTML. You could do this multiple ways, like checking for the innerText, or the className or an id of one of the <li class="name name-x"> elements.
I've modified your HTML so that every one of your <li> elements has an id. This makes them unique and easy to select. I've also have given the <input> element an id for the same reason.
For the event listener listen to either change or input. Either of them triggers on whenever the value has been changed, so that means also when a user types, or pastes a string into it. For the example I've chosen input as you'll only want anything to happen whenever the text of the <input> is changed. Read this other SO post to learn what the difference between change and input entails.
Because I couldn't find the jQuery function necessary to build on your example I've chosen to do it with ES6. In you example I see you using a for... of loop and an arrow function, so I assumed that using modern JS is allowed.
I've tried to add as much hinting as possible in the example below explaining what every piece of code does. If you have any questions regarding the code below, feel free to ask.
I hope it helps you out.
// Select the input.
const input = document.getElementById('name');
// Select the <li class="name"> elements.
const names = document.querySelectorAll('.name');
// Listen to the 'input' event. This will be triggered whenever the value
// of the input has been changed.
input.addEventListener('input', event => {
// Get the value of the input and split it.
const value = event.target.value;
const splitValues = value.split(' ');
// Get only the values that start with '#' and return the remaining values
// in lowercase, without the '#' symbol, to match with the id attribute later on.
const targetValues = splitValues
.filter(value => value.indexOf('#') === 0) // Only values starting with '#'
.map(value => value.substring(1).toLowerCase()); // '#Name' => 'name'
// Loop over each name element. If one of the values corresponds,
// partially or fully, with the id of the a <li class="name"> element
// add the display class, if not remove the display class.
names.forEach(name => {
if (targetValues.some(value => name.id.includes(value))) { // id === 'name' ?
name.classList.add('display');
} else {
name.classList.remove('display');
}
});
});
.name {
display: none;
}
.name.display {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="task-label">
<input id="name" type="text" name="name">
</div>
<ul class="name-list">
<li id="john" class="name name-john">#John</li>
<li id="jake" class="name name-jake">#Jake</li>
<li id="alex" class="name name-alex">#Alex</li>
<li id="allison" class="name name-allison">#Allison</li>
</ul>

jquery all.parent().not(/*first parent*/).data('bar') returns undefined from the third parent onwards

I'm trying to figure out this problem since yesterday, and looks like a bug in jquery.
I'm writing a sorting script, there's a <header> with four columns (<li>) and each has a <span>, the ones I'm clicking to sort the data. I want the first <span> to reset the sorting when one of the other three columns are sorting (have a data-sortby attribute), so I need a condition (var isSet checks if any of the three <span> has the data attribute sortby):
// defaults (needed for getJSON())
var sort = 'coinranking', // this is data-sorton
order = 'desc' // this is data-sortby
$(function() {
$(document).on('click', 'article#coin-ranking > header > ul > li > span', function() {
sort = $(this).data('sorton')
var self = $(this),
all = $('article#coin-ranking > header > ul > li > span'),
isSet = all.parent().not('li.coin-profile').find('span').data('sortby')
console.log(all);
console.log(self);
console.log(self.data('sortby')); // no problem here, as expected
all.removeClass()
all.not(this).removeData('sortby')
console.log(isSet) // problem: returns undefined when it shouldn't
if (self.data('sortby') === 'asc' || (self.parent().hasClass('coin-profile') && isSet)) {
self.addClass('desc')
order = sort === 'coinranking' ? 'desc' : 'asc'
self.data('sortby', 'desc')
}
else {
self.addClass('asc')
order = sort === 'coinranking' ? 'asc' : 'desc'
self.data('sortby', 'asc')
}
//getJSON().then(LoadHomeList)
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<article id="coin-ranking">
<header>
<ul>
<li class="coin-profile"><span data-sorton="coinranking" aria-label="sort">Cryptocurrency</span></li>
<li class="coin-marketcap"><span data-sorton="marketCap" aria-label="sort">Market Cap</span></li>
<li class="coin-price"><span data-sorton="price" aria-label="sort">Price</span></li>
<li class="coin-change"><span data-sorton="change" aria-label="sort">24H Change</span></li>
</ul>
</header>
<ul></ul>
<footer></footer>
</article>
So if I click the <span> from the second <li> a couple of times, then I get the expected value printed in console (the last two console.log() return as expected). The problem starts from the third <li> onwards, if I click those <span> then the last console.log() returns undefined, but the one before returns the expected value (the one set in the if/else).
Clearly the problem lies in var isSet.
I've also prepared a fiddle that shows the problem: https://jsfiddle.net/chazy/su104cqz/
Thanks to #JJJ for finding the problem:
all.parent().not('li.coin-profile').find('span') finds always the same three elements, and .data() gets the data from the first of them.
So .data() is only returning the attribute for the first match in the set of matched elements. Reading the docs, it clearly states it:
Description: Return the value at the named data store for the first element in the jQuery collection, as set by data(name, value) or by an HTML5 data-* attribute.
To workaround this, I've decided to use .filter().length:
isSet = all.parent().not('li.coin-profile').find('span')
.filter(function() { return $(this).data('sortby') }).length

Ng-repeat and directive expand one list item at a time

I have an ng-repeat code with expandable list items. I would like to expand one item at a time.
The way I am trying to do it is
in the html file
<div data-ng-repeat="parts in data track by $index">
<li id="titleHelp" ng-click='setItem($index);">
and in the directive in the setItem function I want to collapse the previously expanded item and expand the new one. Is it possible to access one repeat element in the directive using index?
thanks!
How do you currently expand the list item?
What I would do is set a variable as soon as an item is clicked and in your repeated list do a
<div data-ng-repeat="parts in data track by $index">
<li id="titleHelp" ng-click='setItem($index);">
<div ng-show="$index = selected_item"> <!-- contents --></div>
In your setItem function:
$scope.setItem = function(i) {
$scope.selected_item = i;
}
Declare a object
$scope.obj={selected:null};
After that add a method in the ng repeat,
$scope.isHide = function (id) {
if (id == $scope.obj.selected)
return $scope.obj.selected = "all";
return $scope.obj.selected = id;
}
If you want to hide div, call this method with the id. Do the same thing for the li if you need.

Drag and drop lists based on certain conditions

I am using angular-drag-and-drop-lists (https://github.com/marceljuenemann/angular-drag-and-drop-lists) for my AngularJS project to create two lists that allow me to do the following:
Drag items from list A to list B
Drag items from list B to list A
Reorder items in list A
Reorder items in list B
Using a simple example on the library's site (http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/simple) I can see that these four conditions are easily achievable. However, things start to get hairy when I want to introduce slightly more complex behaviour:
When dragging items from list A to list B, I want something to occur (easily achieved with the dnd-drop callback. This currently works for me
When dragging items from list B to list A, I don't want anything to happen other than a regular drop (i.e. the item winds up in list A and nothing fancy happens). This currently works for me
When reordering items in list A, nothing should happen when an item is re-dropped into it's new position in the list (even if it is being dropped back into it's original position) This is where I am having issues - see below for further explanation
When reordering items in list B, nothing should happen when an item is re-dropped into it's new position in the list (even if it is being dropped back into it's original position). This currently works for me
I am largely adapting the sample code used in the example link provided above. The problem I am having is that the action I want to take place when moving items from list A to list B is also occurring when I reorder things in list A.
The current setup/pseudocode I have is the following:
My lists:
$scope.models.lists = {
"A": [],
"B": []
}
I populate these lists with information pulled from a database. Each item in the list also has a property that tracks how many children the item has.
My markup looks like the following:
<ul dnd-list="list"
dnd-allowed-types="['itemType']"
dnd-drop="myCallback(event, index, item, external, type, 'itemType')">
<li ng-repeat="item in list | filter:searchText"
dnd-draggable="item"
dnd-moved="list.splice($index, 1)"
dnd-effect-allowed="move"
dnd-selected="models.selected = item"
dnd-type="'itemType'"
ng-class="{'selected': models.selected === item}"
ng-dblclick="editProperties();">
{{item.label + ' - ' + item.description}}
</li>
</ul>
My callback looks like the following:
$scope.myCallback= function(event, index, item, external, type, allowedType) {
// If the item in question has no children then we don't need to do anything special other than move the item.
if (item.children.length == 0) {
return item;
}
// If moving items around list B then just move them.
for (var i = 0; i < $scope.models.lists.B.length; i++) {
if (item.label === $scope.models.lists.B.[i].label) {
return item;
}
}
// I only want this code to execute if we know we are moving from list A to list B and the item in question has > 0 children.
if (item.children.length > 0) {
// Code that I want to execute only when moving from list A to list B goes here
}
If anyone is able to assist me with this I will be very grateful.
Thanks!
If you're not totally committed to that library (which, btw, doesn't work with a touchscreen) RubaXa's Sortable is the truth: Sortable
The documentation is strong, it's quite performant, and I wish I hadn't wasted my time on other DnD libraries before this one.
Looking at the README, the dnd-drop event fires for any drop - regardless of whether it was within the same list or not.
Going by the script as you have it now, you need to check the event (or pass some additional information into your callback) to determine what list the event is firing on.
hi i have add a dragstartCallback that set variable in event.dataTransfer
for the information of the origin/source of my dragged obj:
$scope.dragstartCallback = function(event){
var id = event.target.id;
var parent = $('#' + event.target.id).closest('ul')[0].id;
var group = $('#' + event.target.id).closest('div')[0].id;
event.dataTransfer.setData("id", id);
event.dataTransfer.setData("parent", parent);
event.dataTransfer.setData("group", group);
return true;
}
And in the dropCallback i just check if is List A or List B
$scope.dropCallback = function(event, index, item, external, type, allowedType) {
$scope.logListEvent('dropped at', event, index, external, type);
var idDivSource = event.dataTransfer.getData("parent");
var idDivDestination = $(event.target).closest('ul')[0].id;
var groupSource = event.dataTransfer.getData("group");
var groupDestination = $('#' + event.target.id).closest('div')[0].id;
if(groupSource == groupDestination){
if(idDivSource != idDivDestination){
if(idDivDestination == 'IDLISTB'){
return item?
}
if(idDivDestination == 'IDLISTA'){
DO Something else
}
DO Something else
}
}
}
Html code:
<div class="associated-drag-and-drop DnD" id="GROUP-MyD&D1" ng-repeat="(listName, list) in models.lists" flex="">
<ul dnd-list="list" id="{{listName}}" dnd-drop="dropCallback(event, index, item, external, type, 'containerType')" flex="">
<md-list-item class="associated-list__item" ng-repeat="item in list | filter:searchFilter" id="{{item.id}}"
dnd-dragover="dragoverCallback(event, index, external, type)"
dnd-dragstart="dragstartCallback(event)"
dnd-draggable="item"
dnd-moved="list.splice($index, 1)"
dnd-effect-allowed="move"
dnd-selected="models.selected = item"
ng-class="{'selected': models.selected === item}"
class="noright associated-list__item"
>
CONTENT
</md-list-item>
</ul>
</div>
Do not use this type of filter "| filter:searchFilter"
But use ng-show="And put here the condition"
Alternative change the reference to $index in dnd-moved="list.splice($index, 1)"
If you dont make this change you will have a problem with the filtered list when drag and drop
Exemple
NOT FILTERED LIST
you will have 2 array in 2 ng-repeat
ListA ListB
index1 of value index2 of value
ng-repeat of list ng-repeat of list
0 aaa 0 ppp
1 bbb 1 qqq
2 ccc 2 www
3 dddaaa 3 eeerrr
4 eeeccc 4 mmmwww
ecc... ecc...
The drag and drop lists work on the index of ng-repeat
FILTERED LIST
Now if we make a filter for 'a' with "| filter:searchFilter" code
angular will make this list
List A List B
0 aaa
1 dddaaa
the index when you drag "dddaaa" will be 1 and not 3
so it will not be removed from listA when dropped in listB
becouse the index is not the same as the non filtered list
instead if you use the ng-show="condition"
it will keep the original index of list not filtered
<md-list-item ng-repeat="item in list"
ng-show="condition">
</md-list-item>
My item list:
$scope.models = {
lists: {
"LISTA": [{1},{2},{3}],
"LISTB": [{1},{2},{3}]
}
};

Javascript splice removing wrong items

I have following array of objects.
[{"name":"Rain"},{"name":"Storm"},{"name":"Forest"}]
Which has indexes [0, 1, 2].
I'm trying to delete the item on the given position using code:
$scope.selectedSounds.splice(index, 1);
But it is removing items wrong way, for example the last item cannot be deleted. If I'm trying to remove item with index 1, it removes item with index 2..
What can be wrong please?
I tried both ways:
$scope.removeSoundFromSelection = function(index) {
try {
// First
$scope.selectedSounds.splice(index, 1);
var indexNew = $scope.selectedSounds.indexOf(index);
console.log(indexNew);
if (indexNew > -1) {
$scope.selectedSounds.splice(indexNew, 1);
}
// Second
if ($scope.selectedSounds.hasOwnProperty(index)){
delete $scope.selectedSounds[index];
}
//delete $scope.selectedSounds[index];
} catch(e) {
$scope.showAlert();
}
};
ADDED TEMPLATE:
<div class="list">
<a class="item item-thumbnail-left" ng-repeat="sound in selectedSounds">
<img src="cover.jpg">
<h2>{{sound.name}}</h2>
<p>TEST</p>
<div class="customDeleteBtnInList">
<button ng-click="removeSoundFromSelection({{$index}})" class="button button-icon icon ion-close-circled"></button>
</div>
</a>
</div>
You are using interpolation for {{$index}} inside the ng-repeat expression removeSoundFromSelection({{$index}}). Just remove the interpolation and use only $index it will automatically be evaluated against the scope. And you just need $scope.selectedSounds.splice(index, 1).
Ideally using the interpolation there should cause parse error instead of this behavior though (Unless very old angular version, i.e < 1.2.0, is used).
Working Demo
angular.module('app', []).controller('ctrl', function($scope) {
$scope.selectedSounds = [{
"name": "Rain"
}, {
"name": "Storm"
}, {
"name": "Forest"
}];
$scope.removeSoundFromSelection = function(index) {
$scope.selectedSounds.splice(index, 1);
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div class="list">
<a class="item item-thumbnail-left" ng-repeat="sound in selectedSounds">
<img src="cover.jpg">
<h2>{{sound.name}}</h2>
<p>TEST</p>
<div class="customDeleteBtnInList">
<button ng-click="removeSoundFromSelection($index)" class="button button-icon icon ion-close-circled">Remove</button>
</div>
</a>
</div>
</div>
Even though this specific scenario in the question does not use ng-init the issue of wrong item removed can happen if you are using ng-init initialized index alias as well. Just adding that scenario as well to the answer for any future visitations on this question. i.e example:-
<a class="item item-thumbnail-left"
ng-repeat="sound in selectedSounds" ng-init="idx=$index">
....
<button ng-click="removeSoundFromSelection(idx)"
This will end up removing wrong items because ng-init'ed scope properties are not watched and updated during the digest cycle. So even if the item gets removed from DOM after splicing the array ng-inited idx will still have the old index of the item where as $index special property would have got updated to reflect the actual index. So in such cases as well use $index to pass the index instead of using cached ng-inited idx.
You are removing the item at that index twice.
Once here:
$scope.selectedSounds.splice(index, 1);
And once here:
// Second
if($scope.selectedSounds.hasOwnProperty(index)){
delete $scope.selectedSounds[index];
}
Just remove that second part and you should be fine, I can't see what you could be trying to do after that first splice line.
The following code works as expected for me, and seems to be what you are trying to achieve:
var sounds = [{"name":"Rain"},{"name":"Storm"},{"name":"Forest"}];
sounds.splice(1, 1);
console.log(sounds);
My guess is that you are (at some point) not using the correct index. Take a look at the code that creates that variable per #Alex J's answer
If you want the middle item to be deleted, index should equal 1. It's possible that whatever logic you are doing is giving you the wrong value for index
*Edit: After seeing your updated code, it looks like you are splicing twice. You are doing it the first time in the try statement, and then it goes to the if statement where that will also be true. If you are trying to write a function to just splice out an object at a given index, you could do:
$scope.removeSoundFromSelection = function(index) {
if($scope.selectedSounds[index]){
$scope.selectedSounds.splice(index, 1);
}
}
var season = [{"name":"Rain"},{"name":"Storm"},{"name":"Forest"}];
var seasoned= season.slice(0, 2);
console.log(seasoned); //it sliced rain and storm...

Categories

Resources