VueJS v-for: remove duplicates - arrays

Is there a simple solution I can use to remove the duplicates from my v-for loop which is looping through the JSON data.
I'm using this to populate a Select option and would rather not import https://lodash.com/docs#uniq
Codepen with issue: https://codepen.io/anon/pen/JaZJmP?editors=1010
Thanks

Create a computed property that returns only those items from your array which you want/need. To remove duplicates from info, you can do new Set(this.info.map(i=>i.title.rendered) and destructure that back into an array using [...new Set(this.map(i=>i.title.rendered))]:
var vm = new Vue({
el: "#wp-vue-app",
data() {
return {
info: [{
id: 1,
status: "publish",
link: "",
title: {
rendered: "Test Name One"
},
acf: {
employee_details: {
employee_name: "Test Name",
employee_email: "Test-Email#email.co.uk",
employee_number: "123",
cost_centre_manager: "Manager Name",
manager_email: "Manager-Email#email.co.uk"
}
}
},
{
id: 2,
status: "publish",
link: "",
title: {
rendered: "Test Name"
},
acf: {
employee_details: {
employee_name: "Test Two Name",
employee_email: "Test-Two-Email#email.co.uk",
employee_number: "1234",
cost_centre_manager: "Manager Two Name",
manager_email: "Manager-Two-Email#email.co.uk"
}
}
},
{
id: 3,
status: "publish",
link: "",
title: {
rendered: "Test Name"
},
acf: {
employee_details: {
employee_name: "Test Three Name",
employee_email: "Test-Three-Email#email.co.uk",
employee_number: "12345",
cost_centre_manager: "Manager Three Name",
manager_email: "Manager-Three-Email#email.co.uk"
}
}
}
],
loading: true,
errored: false,
emp_manager: "All",
emp_cost_centre: "All"
};
},
computed: {
info_title: function() {
return [...new Set(this.info.map(i => i.title.rendered))]
},
info_employee_name: function() {
return [...new Set(this.info.map(i => i.acf.employee_details.employee_name))]
},
},
});
.container {
padding: 20px;
width: 90%;
max-width: 400px;
margin: 0 auto;
}
label {
display: block;
line-height: 1.5em;
}
ul {
margin-left: 0;
padding-left: 0;
list-style: none;
}
li {
padding: 8px 16px;
border-bottom: 1px solid #eee;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.min.js"></script>
<div id="wp-vue-app">
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-else>
-
<div class="row">
<div class="col">
<select class="form-control" v-model="emp_manager">
<option>All</option>
<option v-for="item in info_title" :value="item">{{ item }}</option>
</select>
<span>Selected: {{ emp_manager }}</span>
</div>
<div class="col">
<select class="form-control" v-model="emp_cost_centre">
<option>All</option>
<option v-for="item in info_employee_name" :value="item">{{ item }}</option>
</select>
<span>Selected: {{ emp_cost_centre }}</span>
</div>
</div>
<br />
</section>
</div>
https://codepen.io/anon/pen/bxjpKG

You can use a computed property to filter your info (let's say filteredInfo). Then use v-for on the filteredInfo property.

Related

Vue.js - show more objects from array if they have same title

I have an array of objects:
[{
title: 'foo'
id: 1,
name: 'anne'
},
{
title: 'example',
id: 2,
name: 'anne'
},
{
title: 'ex',
id: 3,
name: 'deb'
}]
The page is already showing the first object:
{
title: 'foo'
id: 1,
name: 'anne'
},
<div class="essay">
<p class="title" >{{ selectedItem.title }}</p>
<p class="id">{{ selectedItem.id }}</p>
<p class="name"> {{ selectedItem.name }} </p>
</div>
selectedItem comes from the created() life cycle
What is the best way to display the object with the same name?
I want to have a button which if clicked, will show another title from the person name 'anne'.
Try to get the filtered array.
created() {
this.selectedItem = arr.filter(item => item.name === 'anne');
// arr is the array source variable.
}
<div v-for="item in selectedItem" :key="item.id" class="essay">
<p class="title" >{{ selectedItem.title }}</p>
<p class="id">{{ selectedItem.id }}</p>
<p class="name"> {{ selectedItem.name }} </p>
</div>
I've made a snippet, maybe it would help You :-)
We need to store state somewhere, so I've created owners variable with shown counter.
There is also a computed property that groups items by owners.
Maybe there is an easier method but...
new Vue({
el: '#app',
data: {
items: [
{ owner: "John", name: "Box" },
{ owner: "John", name: "Keyboard" },
{ owner: "John", name: "Plate" },
{ owner: "Ann", name: "Flower" },
{ owner: "Ann", name: "Cup" }
],
owners: {},
},
methods: {
more(owner) {
if (this.owners[owner].shown < this.items.filter(i => i.owner == owner).length) {
this.owners[owner].shown++;
}
}
},
computed: {
ownersItems() {
let map = {};
this.items.forEach(i => {
map[i.owner] = map[i.owner] || [];
if (this.owners[i.owner].shown > map[i.owner].length) {
map[i.owner].push(i);
}
});
return map;
},
},
created() {
let owners = {};
this.items.forEach(i => {
owners[i.owner] = owners[i.owner] || {};
owners[i.owner].shown = 1;
});
this.owners = owners;
}
})
#app {
font-family: sans-serif;
padding: 1rem;
}
.header {
font-weight: bold;
margin-bottom: .5rem;
}
sup {
background: #000;
color: #fff;
border-radius: .15rem;
padding: 1px 3px;
font-size: 75%;
}
.btn {
padding: .25rem .5rem;
border: 1px solid #ddd;
background: #eee;
border-radius: .15rem;
margin: 2px;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="header">Owners</div>
<template v-for="(cfg, owner) in owners">
<strong>{{ owner }} <sup>{{cfg.shown}}</sup>:</strong>
<a v-for="(item, i) in ownersItems[owner]" :key="`${owner}_${i}`" #click="more(owner)" class="btn">{{ item.name }}</a>
|
</template>
</div>

Focus custom md-checkbox in md-select control

I have implemented select control with select all option, When I open the control, it focuses on the first option. I would like to focus on the select option or at least disable the focus.
HTML
<div ng-app="selectDemoOptGroups" ng-controller="SelectOptGroupController" >
<md-select md-selected-text="selectedText" ng-model="selectedToppings" multiple>
<div class="select-all-div" >
<md-checkbox class="select-selectAll"
>Select all</md-checkbox > </div>
<md-option ng-value="topping.name" ng-repeat="topping in toppings">{{topping.name}}</md-option>
</md-select>
JS
angular
.module('selectDemoOptGroups', ['ngMaterial'])
.controller('SelectOptGroupController', function($scope) {
$scope.toppings = [
{ category: 'meat', name: 'Pepperoni' },
{ category: 'meat', name: 'Sausage' },
{ category: 'meat', name: 'Ground Beef' },
{ category: 'meat', name: 'Bacon' },
{ category: 'veg', name: 'Mushrooms' },
{ category: 'veg', name: 'Onion' },
{ category: 'veg', name: 'Green Pepper' },
{ category: 'veg', name: 'Green Olives' }
];
$scope.selectedToppings = [];
});
https://jsfiddle.net/AlexLavriv/ya6eu8kz/5/
You can't use a div for single opotion and options for the rest. You can use the same option to display the option select all.
<md-select>
<md-option>Select all</md-option>
<md-option ng-value="topping.name" ng-repeat="topping in toppings">{{topping.name}}</md-option>
</md-select>
Working Demo: JSFiDDLE
Working Demo
Try this below way
angular
.module('selectDemoOptGroups', ['ngMaterial'])
.controller('SelectOptGroupController', function($scope) {
$scope.toppings = [
{ category: 'meat', name: 'Pepperoni' },
{ category: 'meat', name: 'Sausage' },
{ category: 'meat', name: 'Ground Beef' },
{ category: 'meat', name: 'Bacon' },
{ category: 'veg', name: 'Mushrooms' },
{ category: 'veg', name: 'Onion' },
{ category: 'veg', name: 'Green Pepper' },
{ category: 'veg', name: 'Green Olives' }
];
$scope.toppings.unshift( { category: 'select', name: 'Select all' })
$scope.selectedText = $scope.toppings[0].name;
});
.select-selectAll{
text-align: left;
padding-left: 10px;
/* padding-right: 16px; */
display: flex;
cursor: pointer;
position: relative !important;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
width: 95%;
-webkit-transition: background .15s linear;
transition: background .15s linear;
/* padding: 0 16px; */
height: 48px;
}
.select-selectAll div.md-icon{
left:10px;
}
.select-all-div:hover{
background: rgb(238,238,238);
}
<div ng-app="selectDemoOptGroups" ng-controller="SelectOptGroupController" >
<md-select md-selected-text="selectedText" ng-model="selectedToppings" multiple>
<md-option ng-value="topping.name" ng-repeat="topping in toppings">{{topping.name}}</md-option>
</md-select>
</div>

Twitter Bootstrap Columns and Angular Order By

I keep running into this problem and haven't found a good solution that doesn't cause layout issues. What's the best way to display an array of items, sorted alphabetically, in columns? I'm using ng-repeat to iterate over an array and display a checkbox for each item. I want the data to be displayed in n columns, alphabetically i.e., not alphabetically in rows.
alphabetically in colums
|item a| |item d| |item g|
|item b| |item e| ...
|item c| |item f| ...
current implementation - alphabetically in rows
<div class="checkbox col-xs-12 col-sm-12 col-md-3 col-lg-3" ng-repeat="user in user.results | orderBy:'lastName' track by user.id">
<input id="{{ user.id }}" type="checkbox">
{{ user.lastName }}, {{ user.firstName }}
<label for="{{ user.id }}" class="pull-left checkbox-label"></label>
</div>
Edit
I originally went with the dynamic bootstrap method but this actually screwed up the checkbox behavior i.e., clicking a checkbox resulted in the incorrect checkbox being checked. I'm trying fix this using flexbox but I haven't used it before and don't understand how to dynamically change the column count without having to set a fixed height on the flex container. I would like to have one column on small/extra small screens and three columns for medium/large screens.
.flex-box {
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
.flex-item {
background: green;
width: 33%;
}
/* Small screens */
#media all and (max-width: #container-tablet) {
.flex-item {
width: 100%;
}
}
<div class="flex-box">
<div class="flex-item" ng-repeat="country in region.countries | orderBy:'name' track by $index">
<input id="{{ country.isoCode }}" checklist-model="vm.selectedCountries.value[region.name]" checklist-value="country" type="checkbox" ng-change="vm.setRegionCountry(region, country, checked)">
<label for="{{ country.isoCode }}" class="pull-left checkbox-label"></label>
<span>{{ country.name | limitTo:17 }}{{country.name.length > 17 ? '...' : ''}}</span>
</div>
</div>
Solution
.flex-box {
display: flex;
flex-flow: column wrap;
}
/* Small screens */
#media all and (min-width: #screen-sm-min) {
.flex-box {
max-height: 375px;
}
}
/* Medium screens */
#media all and (min-width: #screen-md-min) {
.flex-box {
max-height: 550px;
}
}
/* Large screens */
#media all and (min-width: #screen-lg-min) {
.flex-box {
max-height: 375px;
}
}
You can tweak little bit as below if you want to use with bootstrap. Otherwise you can use either flex-box or column-count.
var app = angular.module('app', []);
app.controller('TestController', function($scope){
$scope.fixedColumn = 3;
$scope.getColumns = function(){
return new Array($scope.fixedColumn);
};
$scope.getColumnWidth = function(){
return Math.floor(12 / $scope.fixedColumn);
};
$scope.getRowCount = function(){
return Math.ceil($scope.user.results.length / $scope.fixedColumn);
};
$scope.user = {
results: [
{
id: 1,
firstName: 'FirstName1',
lastName: 'LastName1'
},
{
id: 2,
firstName: 'FirstName2',
lastName: 'LastName2'
},
{
id: 3,
firstName: 'FirstName3',
lastName: 'LastName3'
},
{
id: 4,
firstName: 'FirstName4',
lastName: 'LastName4'
},
{
id: 5,
firstName: 'FirstName5',
lastName: 'LastName5'
},
{
id: 6,
firstName: 'FirstName6',
lastName: 'LastName6'
},
{
id: 7,
firstName: 'FirstName7',
lastName: 'LastName7'
},
{
id: 8,
firstName: 'FirstName8',
lastName: 'LastName8'
}
]
};
});
angular.bootstrap(document, ['app']);
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="container" ng-controller="TestController">
<div class="row" ng-repeat="u in user.results | orderBy:'lastName' track by u.id" ng-init="pIndex=$index" ng-if="$index < getRowCount()">
<div ng-repeat="col in getColumns() track by $index" ng-init="usr = user.results[pIndex + ($index * getRowCount())]" ng-class="'col-xs-'+ getColumnWidth() + ' col-sm-'+ getColumnWidth() + ' col-md-'+ getColumnWidth()" ng-if="user.results.length > (pIndex + ($index * getRowCount()))">
<input id="{{ usr.id }}" type="checkbox">
{{ usr.lastName }}, {{ usr.firstName }}
<label for="{{ usr.id }}" class="pull-left checkbox-label"></label>
</div>
</div>
</div>
<style>
.flex-box {
height: 100px;
overflow: hidden;
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
.flex-item {
background: green;
text-align: center;
}
</style>
<div class="container">
<div class="row">
<div class="col-xs-12">
<div class="flex-box">
<div class="flex-item" ng-repeat="user in user.results | orderBy:'lastName'">{{user.id}}</div>
</div>
</div>
</div>
</div>
Using CSS flexbox you can dynamically add as many column as required for your data set https://css-tricks.com/snippets/css/a-guide-to-flexbox/
You could do this without bootstrap using columns. See this fiddle: https://jsfiddle.net/ojzdxpt1/1/
#wrapper {
column-count:3;
-webkit-column-count:3;
-moz-column-count:3;
}
.col {
background:#ccc;
border:1px solid #000;
}
#wrapper contains each repeater. See more about css columns
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Columns/Using_multi-column_layouts
https://css-tricks.com/almanac/properties/c/columns/

Iterate through array via ngRepeat with breaks

I need your help.
I decided to make an attempt to create some kind of online market and I am somehow new to some parts of AngularJS and this is first time when I faced such a problem with ng-repeat.
I have the following html-structure (it is only a required part of it):
<div class="goods">
<div class="goods-row" ng-repeat="???">
<div class="good" ng-repeat="???">
<button id="add_to_cart">+</button>
<div class="descr">
<div class="descr-top">
<h5 class="g-name">
NAME IS HERE
</h5>
<span class="g-price">
PRICE IS HERE
</span>
</div>
<div class="descr-mid" ng-bind="good.description"></div>
</div>
</div>
</div>
</div>
"Goods"(goods-class) includes "rows of goods"(goods-row). Every row should include (by default) 4 goods (good-class), but, it should be also noticed that I will use filter, that could change number of visible goods in a row, because of that I need a flexible solution.
What should I enter in ng-repeat?
P.S. I created a blueprint with JS-code (posted below), that somehow shows what I want, but how to make it in AngularJS?
var arr = [];
for (i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) {
arr.push({
number: i
});
}
var divider = 4;
for (var i = 0; i < arr.length;) {
for (var j = 0; j < divider; j++) {
if ((j + i) < arr.length) {
console.log(arr[j + i]);
}
}
i = i + divider;
console.log(" ");
}
With lodash library you can use chunk function to split an array into chunks and then build a list that suits your needs.
So basically just iterate as you would do for a normal list of arrays (which represent your rows containing goods) and implement a function that build this list according to the divider. That way you can invoke the function when you need to rebuild your list (on keyup of the input in the example bellow) and let AngularJS do the rest.
(function(angular) {
'use strict';
angular.module('ngRepeat', [])
.controller('repeatController', function($scope) {
// the goods
$scope.goods = [
{ name: "name-1", price: 1.01, description: 'desc-1' },
{ name: "name-2", price: 2.02, description: 'desc-2' },
{ name: "name-3", price: 3.03, description: 'desc-3' },
{ name: "name-4", price: 4.04, description: 'desc-4' },
{ name: "name-5", price: 5.05, description: 'desc-5' },
{ name: "name-6", price: 6.06, description: 'desc-6' },
{ name: "name-7", price: 7.07, description: 'desc-7' },
{ name: "name-8", price: 8.08, description: 'desc-8' },
{ name: "name-9", price: 9.09, description: 'desc-9' },
{ name: "name-10", price: 10.10, description: 'desc-10' },
{ name: "name-11", price: 11.11, description: 'desc-11' },
{ name: "name-12", price: 12.12, description: 'desc-12' }
];
// divider determines how many goods per row (defaulted to 4)
$scope.divider = 4;
// function that build the rows of goods
$scope.dividerChanged = function() {
$scope.rows = _.chunk($scope.goods, $scope.divider);
};
// initialize rows on first load
$scope.dividerChanged();
});
})(window.angular);
.divider {
margin-bottom: 10px;
}
.goods-row {
border: 1px solid blue;
padding: 10px;
text-align: center;
}
.good {
border: 1px solid red;
display: inline-block;
padding: 10px;
margin: 10px;
width: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<body ng-app="ngRepeat">
<div ng-controller="repeatController">
divider: <input type="text" ng-model="divider" ng-keyup="dividerChanged()" class="divider">
<div class="goods">
<div class="goods-row" ng-repeat="row in rows">
<div class="good" ng-repeat="good in row">
<button id="add_to_cart">+</button>
<div class="descr">
<div class="descr-top">
<h5 class="g-name">{{ good.name }}</h5>
<span class="g-price">{{ good.price | currency }}</span>
</div>
<div class="descr-mid">{{ good.description }}</div>
</div>
</div>
</div>
</div>
</div>
</body>
I can't fully understand your question, are you trying to build a filtered array but always want to display the top 4 entries?
If yes, then it can be done with ng-repeat filter (custom or not) and limitTo filter
<div ng-repeat="data in test.data | customFilter:test.selectedFilter | limitTo:4">{{data}}</div>
https://jsbin.com/wajapabaro/1/edit?html,js,output

backbone : Collection and View rendering

I am really confused on this one and need help. (unrelated : "up for about 36 hours now to finish somthing and this last part is just making me go crazy")
My Code
<body>
<div id="mycontainer" style="margin: 20px; ">
</div>
<script type="text/template" id="Myelement_template">
<% _.each( results, function( item, i ){ %>
<div id="Myelement" style="width: 200px; height:325px; padding: 10px; background-color: #2980b9;">
<div id="image" style="width: 190px; height: 200px; margin: auto; background-color: #f1c40f;">
<img src="<%= item.get('category').url %>" style="max-width: 90%;margin-top: 10px;">
</div>
<div id="type" style="float:left;width: 90px; height: 25px; margin-left: 5px;margin-top: 5px;background-color: #f1c40f;">
<%= item.get("category").type %>
</div>
<div id="name" style="float:left;width: 90px; height: 25px; margin-left: 10px; margin-top: 5px;background-color: #f1c40f;">
<%= item.get("category").name %>
</div>
</div>
<% }); %>
</script>
<script type="text/javascript">
$(document).ready(function() {
CategoryModel = Backbone.Model.extend({
defaults: {
url: '',
type: '',
name: ''
}
});
MyListModal = Backbone.Model.extend({
url: 'myrestapi',
defaults: {
category: CategoryModel
}
});
MyElementView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render'); // bind 'this' in 'render'
this.model = new MyListModal();
this.model.bind('change', this.render);
this.model.fetch(this.render);
},
el: $("#mycontainer"),
render: function(){
console.log(this.model.get("category"));
}
});
var myModelView = new MyElementView();
});
</script>
Question
My rest api will return many BaseModel objects. All of them need to be rendered. How do I do that?
console.log(this.model.get("category")); - code reaches here. not printing. debugging shows that rest api call was made and cata has been returned
How do I render all returned elements?
Sample Data returned by rest API
[
{
"category": {
"id": 1,
"name": "name1",
"type": "mytype1",
"url": "myurl1"
},
"user": {
"id": 153
},
"status": 1
},
{
"category": {
"id": 1,
"name": "name2",
"type": "type2",
"url": "url2"
},
"user": {
"id": 153
},
"status": 1
},
]

Resources