How do I remove duplicate values ​in a v-for array? - javascript

I coded like this to remove duplicate values.
vue
<div class="col-md-6" style="float: left">
<ul class="list-group">
<li class="list-group-item"
:class="{ active: index1 == currentIndex1 }"
v-for="(member, index1) in uniqueMemName"
v-bind:value="member.mem_name"
:key="index1"
#click="setActiveMember(member, index1)"
>
<strong style="margin-bottom: 5px"> {{member.mem_name}} </strong>
</li>
</ul>
</div>
vue (script)
computed: {
uniqueMemName() {
return _.uniqBy(this.members, function(m) {
return m.mem_name;
});
}
},
I also installed lodash. But I get an error. Which part is wrong?
Please let me know if there is another method other than the one I wrote.
++) error console
console window
++) array information:
I have tables A and B. Table A imports only the mem_name column. Table B imports all columns.
Example ->
a.mem_name
b.col1
b.col2
mem1
10
20
mem1
30
40
mem2
50
60
I'm working on making duplicate mem_names into one at this time. Using lodash's unique features.

If you want to use lodash just for this, which sounds like the case, I suggest that there may be a better way without it, only using newer vanilla JS:
...
computed: {
uniqueMemName() {
return [...new Set(this.members.map(m => m.mem_name))]
}
}
Sets are always unique, so mapping and converting to a set and then back to array gives us a unique array.

Related

Use angularjs nested ng-repeat to construct complex table

I'm having trouble making proper table with nested ng-repeat.
What I wanted is this https://jsbin.com/razamagabo/1/edit?output
but I'm stuck at here https://plnkr.co/edit/d5voXIpzYL81sSl9BSY2?p=preview
I don't mind my markup is not table but I'm still stuck with div
<div class="row">
<div class="col-xs-6" ng-repeat="obj in data">
{{obj.date}}
<div ng-repeat="user in obj.users">
<br>
{{user.name}}
<br>
{{user.mark}}
</div>
</div>
</div>
In order for you to be able to display your data in the desired way, it will probably be easiest if you restructure your data in the JS before trying to render it.
It will be very complicated to try and match on the user names when they are in separate objects in the data array.
I would suggest processing your scope.data in the controller. (I'm assuming that you don't have much control on how you are receiving the data).
For example after you get your data...
$scope.data = [
{
date:'1-1-2016',
users:[
{
'name':'james',
'mark':18
},
{
'name':'alice',
'mark':20
}
]
},
{
date:'2-1-2016',
users:[
{
'name':'james',
'mark':60
},
{
'name':'alice',
'mark':55
}
]
}
]
var userData = {};
var possibleDates = [];
for (dataObj of Object.entries($scope.data)) {
for (userObj of dataObj) {
if ( !userData[userObj.name] ) {
userData[userObj.name] = {};
}
userData[userObj.name][dataObj.date] = userObj.mark;
if (dates.indexOf(dataObj.date) < 0) {
dates.push(dataObj.date);
}
}
}
$scope.users = userData;
$scope.dates = possibleDates;
this will give you an object like this on your scope
$scope.users = {
'james': {
'1-1-2016': 18,
'2-1-2016': 60
},
'alice': {
'1-1-2016': 20,
'2-1-2016': 55
}
};
$scope.dates = ['1-1-2016', '2-1-2016'];
This to me seems easier to structure for your template. Though this assumes each user has an entry for each date.
<div>
<div id='header-row'>
<div id='empty-corner></div>
<div class='date-header' ng-repeat='date in $scope.dates></div>
</div>
<div class='table-row' ng-repeat='{key, value} in $scope.users'>
<div class='user-name'>{{ key }}</div>
<div class='user-data' ng-repeat='date in $scope.dates>
{{ value[date] }}
</div>
</div>
</div>
As long as you apply inline-block styles to the rows/elements this should give you what you are looking for.
Though you can also think of ways to simplify your data even further. You could instead of having each user have an object where the dates are keys, you could just push the values into an array.
With your current data structure it is not possible to display it like you want. You are trying to loop over date-users objects in data array but then you want to display user from inside users array in separate rows. With ng-repeat you can loop through rows tr but not through columns. First you would need to map your data array to group elements that are supposed to be visible in 1 row into 1 object in array. Currently you have them in 2 separate objects:
James mark: 18 and James mark: 60.

Vue.js: How to map a list of keys to Firebase objects?

I develop a small web-app based on Vue.js using Firebase to store and sync the data. I store items (e.g. with attributes title and subtitle) and lists with an attribute listitems, where an array of keys (those generated from Firebase) of items is stored. The structure looks like this:
Now the problem: I want to display a list and show the items from the listitems attribute and I'm doing it like this:
Compontent:
var ShowList = Vue.extend({
template: '#show-list',
firebase: {
// get all existing items from firebase
items: firebase.database().ref('items')
},
data: function () {
// get list item keys of list 'list_id' and bind it to this.list
this.$bindAsObject('list', listsRef.child(this.$route.params.list_id));
return {
list: this.list
};
}
});
Template:
<!-- show a list -->
<template id="show-list">
<ul v-if="list.items != ''">
<li v-for="key in list.items"> <!-- I would like to not being forced to -->
<template v-for="item in items"> <!-- iterate the whole list of existing items -->
<span v-if="item['.key'] == key">
{{ item.title }}
</span>
</template>
</li>
</ul>
<div v-else>No items.</div>
</template>
As you can see, I have to use two iterations where I iterate the full items list for every entry in list.items.
My question: Is there a more efficient way to map the actual objects to the list of object keys? For a huge number of item records, my approach will be very slow. Maybe I'm just too blind to see a simpler solution?
Thanks for your time!
I think you have to denormalize/duplicate some data there. I had a similar situation and this Firebase video cleared a lot of things up for me: https://youtu.be/ran_Ylug7AE?t=2m22s (Link updated to passage at 2:22. The whole serie is worth watching btw.)
My shot at it would be adding (Firebase) keys in "listitems", just like you have done in "items", with only the most crucial data there, so that you can link to a full description
Is your data read only? In which case you could move the filter logic from your template to your data module, like so (I expect I have unintended side-effects):
data: function () {
// get list item keys of list 'list_id' and bind it to this.list
this.$bindAsObject('list', listsRef.child(this.$route.params.list_id));
var items = firebase.database().ref('items')
var activeItems = this.list.items.map(function(key) {
return items[key]
})
return {
activeItems: activeItems;
};
}

AngularJS: displaying an array of objects in separate rows

Let's say, I have an array of objects and I want to display it in several rows. Each row should consist of a specific number of objects. Basically, it should look like this:
<div class="row">
<div class="col-md-4">item</div>
<div class="col-md-4">item</div>
<div class="col-md-4">item</div>
</div>
<div class="row">
<div class="col-md-4">item</div>
<div class="col-md-4">item</div>
<div class="col-md-4">item</div>
</div>
...
I've implemented it with a very dirty trick, generating an additional array of numbers and iterating through it (4 is a number of objects in a row):
<div class="row titles-row" ng-repeat="row in outRange(series.length, 4)">
<project-preview class="col-md-3" ng-repeat="project in series" ng-if="$index < (row + 1)*4&& $index >= (row)*4"></project-preview>
</div>
and outRange function:
$scope.outRange = function(items_num, row_width) {
items_num = items_num > 0 ? items_num : 0;
var rows_num = Math.ceil(items_num / row_width);
return Array.apply(null, Array(rows_num)).map(function (_, i) {return i;});
};
It works, but I feel like there should be a better way to do it.
If this is just a matter of presentation, bootstrap (which it seems you might be using) will automatically put the other objects on a separate row when the sum of columns is more than 12 (it uses floats). If however the objects have significantly different sizes, this might not look so good indeed. Still, I would tend to leave this under control of CSS, rather than in javascript.
One approach would be to use a display: flexbox on the container, which should have the effect you want automatically. Lookup this CSS property to discover the true strength of flexbox.
If you really want to do it in javascript, you could have a template like:
<div ng-class='{row: $index % 4 == 0}' ng-repeat='...'>
<div class='col-md-4'>
..
</div>
</div>
This will generate extra divs, but that is likely acceptable.
Instead of outRange, use a filter to create chunks out of the series array. Lodash has a chunk method. Or you can implement one yourself.
Thanks for your ideas. I came up with this solution:
mainApp.filter('slice', function() {
return function(array, row_width, scope) {
if(array == undefined)
return;
if(scope.sliceResult != undefined)
return scope.sliceResult;
scope.sliceResult = [];
var rows_num = Math.ceil(array.length / row_width);
for(var i = 0; i < rows_num; i++) {
scope.sliceResult.push(array.slice(i * row_width, i * row_width + row_width));
}
return scope.sliceResult;
};
});
And here how I use it:
<div class="row titles-row" ng-repeat="row in series | slice: 4 : this">
<project-preview class="col-md-3" ng-repeat="project in row"></project-preview>
</div>
Still I don't like that I need to pass the scope inside the filter.

Sorting alphabetically in JQuery with two groups

I've got a todo list. Each row has a star icon that you can click, exactly like gmail. The difference here is that if you click a star it should sort to the top (higher priority), but also re-sort within the starred group by ascending alpha. Unstarred items sort below, also sorted by ascending alpha. Everything is working as expected except for the alpha sorting. Below is the sort function where I'm doing that. I've verified that everything works below except the //sort the arrays by alpha bit...
Sort fail:
function sortTasks(currList) {
var starredTasks = [];
var unstarredTasks = [];
//create arrays
$('li.task').each(function(){
if ($(this).children('img.star').attr('src') == "images/star_checked.gif") {
starredTasks.push($(this));
} else {
unstarredTasks.push($(this));
}
});
//sort the arrays by alpha
starredTasks.sort( function(a,b){ ($(a).children('p.task-name').text().toUpperCase() > $(b).children('p.task-name').text().toUpperCase()) ? 1 : -1;});
unstarredTasks.sort( function(a,b){ ($(a).children('p.task-name').text().toUpperCase() > $(b).children('p.task-name').text().toUpperCase()) ? 1 : -1;});
//draw rows starred first, unstarred second
$(currList).empty();
for (i=0; i < starredTasks.length; i++) {
$(currList).append(starredTasks[i]);
}
for (i=0; i < unstarredTasks.length; i++) {
$(currList).append(unstarredTasks[i]);
}
}
This array has been populated with the task rows in the order they were originally drawn. The data renders fine, but basically stays in the same order.
Example task row:
<div id="task-container" class="container">
<form name="enter-task" method="post" action="">
<input id="new-task" name="new-task" type="text" autofocus>
</form>
<h2 id="today">today</h2>
<ul id="today-list">
<li id="457" class="task">
<img class="star" src="images/star_checked.gif">
<p class="task-name" contenteditable>buy milk</p>
<p class="task-date"> - Wednesday</p>
</li>
</ul>
<h2 id="tomorrow">tomorrow</h2>
<ul id="tomorrow-list">
</ul>
<h2 id="future">future</h2>
<ul id="future-list">
</ul>
<h2 id="whenever">whenever</h2>
<ul id="whenever-list">
</ul>
</div>
Each item in the starredTasks array is an entire task row. I'm assuming that $(a) is the same level as $(li)?
and here's the function that triggers the sort:
$('body').on('click', 'img.star', function(){
var thisList = '#' + $(this).parent('li').parent('ul').attr('id');
if ($(this).attr('src') == 'images/star_checked.gif') {
$(this).attr('src', 'images/star_unchecked.gif');
} else {
$(this).attr('src', 'images/star_checked.gif');
}
sortTasks(thisList);
});
Also, I doubt it's worth mentioning, but the data is stored in mySQL and prepopulated via php.
I wasn't sure of a way to use .sort() directly on the $('li') without splitting it into separate arrays...
Anybody see my goof?
I don't see where you're adding the sorted list back into the DOM. If you're not, then that's the problem. Sorting an array of elements doesn't update the DOM at all.
Furthermore, your sorting is very expensive. It's better to map an array of objects that have the elements paired with the actual values to sort.
Finally, you appear to be using the same ID multiple times on a page. That's just wrong. it may work with jQuery's .children(selector) filter, but it's still wrong. You need to change that.
Here I map an array of objects that contain a text property holding the text to sort and a task property that holds the element.
I changed p#task-name to p.task-name, so you should change that to class="task-name" on the elements.
Then I do the sort using .localeCompare(), which returns a numeric value.
Finally, the .forEach() loop appends the elements to the DOM.
var data = starredTasks.map(function(t) {
return { task: t,
text: $(t).children('p.task-name').text().toUpperCase()
};
}).sort(function(obj_a, obj_b) {
obj_a.text.localeCompare(obj_b.text);
}).forEach(function(obj) {
original_container.append(obj.task);
});
This assumes starredTasks is an actual Array. If it's a jQuery object, then do starredTasks.toArray().map(func....
The original_container represents a jQuery object that is the direct parent of the task elements.

Using splice on dynamic array in Angular repeater

I have an issue when trying to delete an object from an array using splice. I have an array that is dynamically created through a UI and stored in a scope variable called $scope.productAttributes.Products. This is an example of what it looks like...
[
{
"ProductLabel":"Net",
"Code":"ela",
"Site":"SITE1"
},
{
"ProductLabel":"Link",
"Code":"eli",
"Site":"SITE1"
},
{
"ProductLabel":"24-port managed PoE switch",
"Code":"24p",
"Site":"SITE2"
},
{
"ProductLabel":"Dedicated Firewall",
"Code":"ded",
"Site":"SITE2"
},
{
"ProductLabel":"Link",
"Code":"eli",
"Site":"SITE3"
},
{
"ProductLabel":"IPv4 Addresses",
"Code":"ip4",
"Site":"SITE3"
}
]
I then display that array in an angular repeater and group it by 'site' (which might be part of the problem)...
<div ng-repeat="(key, value) in productAttributes.Products | groupBy: 'Site'">
<strong>{{key}}</strong>
<div ng-repeat="site in value">
<h4>{{site.ProductLabel}}</h4>
<sapn href="" ng-click="deleteItem($index)" class="text-danger">Remove {{site.ProductLabel}}</span>
</div>
</div>
</div>
On the delete button I pass in the index of the object and use the splice function...
$scope.deleteItem = function (index) {
$scope.productAttributes.Products.splice(index, 1);
};
So the issue is that the $index is always zero (I noticed this from a console.log) as I mentioned that I think it might be down to the groupBy but I am not sure. anyone know whats going wrong? Thanks
UPDATE:
It would seem the problem is with the $index in the nested repeater. So if the json above the structure would be...
SITE1:
Product: Net - $index: 0
Product: Link - $index: 1
SITE2:
Product: 24-port - $index: 0
Product: Dedicated - $index: 1
SITE3:
Product: Link - $index: 0
Product: IPV4 - $index: 1
So if I try to delete the IPV4 product in SITE3, it removes the LINK product in Site1 as it has the same $index. any ideas how I can fix that?
We can not rely on $index as it does not contain the updated value after you remove an item from array.
Pass the object dynamically from UI and delete it from model using below code:
In Html:
<div ng-repeat="(key, value) in productAttributes.Products | groupBy: 'Site'">
<strong>{{key}}</strong>
<div ng-repeat="site in value">
<h4>{{site.ProductLabel}}</h4>
<sapn href="" ng-click="deleteItem(site)" class="text-danger">Remove {{site.ProductLabel}}</span>
</div>
</div>
</div>
In JavaScript:
$scope.productAttributes.Products.splice
($scope.productAttributes.Products.indexOf(site), 1);
This causes model to update with updates values in repeater and re-renders it on UI.
OK - I ended up doing it this way and it seems to work
$scope.deleteItem = function (item) {
var index = $scope.productAttributes.Products.indexOf(item);
$scope.productAttributes.Products.splice(index, 1);
};
So passing in the whole object seems to have worked. I'm not sure why.

Categories

Resources