Vue Show more in deep array - arrays

I am trying to add a "show-more" button to an array(v-for) of an array (v-for).
<li v-for="(recruiter, key) in recruiters.data" :key="recruiter.id">
<div v-if="recruiter.cities" >
<div v-if="recruiter.cities.length <= 5 || recruiter.id in cities_expand">
<a :href="'/rekryterare/' + city.slug" v-for="(city, index) in recruiter.cities">{{ city.name }}</a>
</div>
<div v-else>
<template v-for="(city, index) in recruiter.cities">
<template v-if="index+1 <= 4">
<a :href="'/rekryterare/sverige/' + city.slug" >{{ city.name }}</a>
</template>
</template>
<a href="#" #click.prevent="cities_expand.push(recruiter.id)" >...</a>
</div>
</div>
</li>
Basically if there is more than 4 items in recruiter.cities -> show 4 items + "show more"
However - the recruiter.id in cities_expand does not work/update?
I'm sure there is a better way of solving this - but nothing else came to mind :)
Any ideas?

I'm suspect that the problem is the use of the js in operator. The in operator checks if an object has a key in the object. It does not check if an array has a value.
So when doing recruiter.id in cities_expand, this checks if recruiter.id is a key in the array cities_expand. The keys of an array are the array indices.
This would explain why spam clicking will work because if the recruiter.id is e.g. 9, then recruiter.id in cities_expand will only be true if cities_expand has key 9 i.e. length 10. Which means you would have to click ... 10 times.
Changing
<div v-if="recruiter.cities.length <= 5 || recruiter.id in cities_expand">
to
<div v-if="recruiter.cities.length <= 5 || cities_expand.indexOf(recruiter.id) > -1 ">
should do the trick.
In terms of an alternative solution, I would encapsulate the inner loop in a component and use a computed value for the list. E.g.
Component RecruiterCities:
<template>
<div v-if="displayCities" >
<a :href="'/rekryterare/' + city.slug" v-for="(city, index) in displayCities" :key="index">{{ city.name }}</a>
<a v-if="!showExpanded && totalCities > maxDisplayCities" href="#" #click.prevent="showExpanded = true" >...</a>
</div>
</template>
<script>
export default {
props: {
recruiterCities: {
type: Array,
default() {
return []
}
}
},
data() {
return {
showExpanded: false,
maxDisplayCities: 4
}
},
computed: {
totalCities() {
return (this.recruiterCities && this.recruiterCities.length) || 0;
},
displayCities() {
if (!this.recruiterCities) {
return [];
}
if (this.showExpanded || this.recruiterCities.length <= this.maxDisplayCities) {
return this.recruiterCities;
}
return this.recruiterCities.slice(0, this.maxDisplayCities);
}
}
}
</script>
and to use it:
<li v-for="(recruiter, key) in recruiters.data" :key="recruiter.id">
<RecruiterCities :recruiter-cities="recruiter.cities"/>
</li>

Related

ng-show is not working with ng-repeat

Below is my table markup
<tr ng-show="paginate({{$index+1}})" ng-repeat="x in ProductInfo_Pager | orderBy :sortType:sortReverse | filter:searchText | limitTo : rowPerPage" ng-class="$even?'table-danger':'table-info'">
<td>{{$index+1}}</td>
<td>{{x.Product}}</td>
<td>{{x.Location}}</td>
<td>{{x.Qty}}</td>
<td>{{x.UnitPrice | currency : '₹':2}}</td>
<td class="text-center">
<i class="fa fa-flag" aria-hidden="true" style="color:red" /> |
<i class="fa fa-bolt" aria-hidden="true" style="color:red" /> |
<i class="fa fa-users" aria-hidden="true"></i>
</td>
</tr>
and pager below it
<ul class="pagination">
<li class="page-item" ng-repeat="x in getNumber(rowPerPage) track by $index">
<a class="page-link" ng-click="pagerIndex=$index+1">{{$index+1}}</a>
</li>
</ul>
And AngularJs Code
$scope.ProductInfo_Pager = $scope.ProductInfo;
$scope.sortType = 'Product'; // set the default sort type
$scope.sortReverse = false; // set the default sort order
$scope.searchText = ''; // set the default search/filter term ,
$scope.totalItems = $scope.ProductInfo.length;
$scope.currentPage = 1;
$scope.rowPerPage = 5;
$scope.pagerIndex = 1;
$scope.getNumber = function (num) {
var pages = $window.Math.ceil($scope.totalItems / num);
return new Array(pages);
}
$scope.paginate = function (rowindex) {
var begin, end, index, flag ;
index = $scope.pagerIndex;
end = (index * $scope.rowPerPage) - 1; // -1 to sync it with zero based index
begin = end - $scope.rowPerPage + 1;
if(rowindex >=begin && rowindex <= end ){
flag=true;
}
else{
flag=false;
}
var d = 0;
return flag;
};
paginate function() return true or false based on logic which is used in ng-show in tr tag with ng-repeat, but its not doing show , hide functionality as expected
Logic is :
Suppose rowPerPage is 5 - [5 row can be show up in table at a time]
And we click on 4 in pager so it should show row from 16-20 .
In ng-show paginate function is bind which take row index as parameter , this function check if rowindex falls in between 16 - 20 , if yes than it return true (ng-show=true) else false and accordingly should hide that row.
As per mu understanding its two way binding so any change in ng-show should work perfectly but it does not show any effect
Can someone help me why this is happening
I am a newbie in angularjs
Thanks.
Well ! ng-show is not working here and the function is not getting called at all written in ng-show !
If i correctly understand you want to create a pagination :
So i am giving you a very simple solution of pagination using a pagination filter .
you need to add this filter to your app :
app.filter('pagination', function() {
return function(input, start) {
start = +start; //parse to int
if (input != undefined && Object.keys(input).length > 0) {
return input.slice(start);
}
}
});
In your html :
<tr ng-repeat="x in ProductInfo_Pager | pagination: currentPage * rowPerPage | limitTo: rowPerPage|orderBy :sortType:sortReverse | filter:searchText" ng-class="$even?'table-danger':'table-info'">
<td>{{$index+1}}</td>
<td>{{x.Product}}</td>
<td>{{x.Location}}</td>
<td>{{x.Qty}}</td>
<td>{{x.UnitPrice | currency : '₹':2}}</td>
<td class="text-center">
<i class="fa fa-flag" aria-hidden="true" style="color:red" /> |
<i class="fa fa-bolt" aria-hidden="true" style="color:red" /> |
<i class="fa fa-users" aria-hidden="true"></i>
</td>
</tr>
In your pagination ul below your table :
<ul class="pagination">
<li class="page-item" ng-repeat="x in getNumber(rowPerPage) track by
$index">
<a class="page-link" ng-click="pagerIndex=$index+1">{{$index+1}}
</a>
</li>
</ul>
in your controller :
$scope.numberOfPages = function() {
if ($scope.ProductInfo_Pager != undefined) {
return Math.ceil($scope.ProductInfo_Pager.length /
$scope.rowPerPage);
}
};
Hope it work ! if any doubt please let me know .

Add item in array repeatedly for ng-repeat

I have an array of objects which looks like:
{
id: "1234",
general: {
title: "lorem",
body: "..."
}
}, ...
This data is being shown with an ng-repeat:
<ul>
<li ng-repeat="item in items track by $index">
<h2>{{item.general.title}}</h2>
<p>{{item.general.body}}</p>
</li>
</ul>
Now what I want to achieve is to add items to this list. Every 15 items I want to add a new item to the array to display in my ng-repeat. The item has a different structure:
<li>
<p>a text</p>
<a>a link</a>
</li>
So far I got this in my controller:
var addLinks = function addLinks(interval, array) {
var newArray = array.slice();
for(var i = interval - 1; i < array.length; i += interval) {
newArray.splice(i, 0, {
// Here comes the item to add
});
}
return newArray;
};
$scope.items = addLinks(15, articleService.articles);
My question is how do I add the item without just copying the html?
You could use ng-repeat-start and ng-repeat-end and only add the other element if you are on $index+1 % 15, like this:
<ul>
<li ng-repeat-start="item in vm.array track by $index">{{item.a}}</li>
<li ng-repeat-end ng-if="$index>0 && $index+1 % 15 == 0"></li>
</ul>
Here a plunkr: http://plnkr.co/edit/qwkXGcBcxlDy0hNHTvAo?p=preview
I would add additional properties to the item in question:
var addLinks = function addLinks(interval, array) {
//don't return a new array!
//var newArray = array.slice();
for(var i = interval - 1; i < array.length; i += interval) {
array[i].text = "foo";
array[i].link = "bar";
}
return array;
};
And in your view:
<ul>
<li ng-repeat="item in items track by $index">
<h2>{{item.general.title}}</h2>
<p>{{item.general.body}}</p>
<p ng-show="item.text">item.text</p>
<p ng-show="item.link">item.link</p>
</li>
</ul>

How to get dynamic ng-model from ng-repeat in javascript?

I'm developoing a web app and stuck here:
Part of the HTML:
<div class="input-group">
<select name="select" class="form-control input-group-select" ng-options="key as key for (key , value) in pos" ng-model="word.pos" ng-change="addPos()">
<option value="">Choose a POS</option>
</select>
<span class="sort"><i class="fa fa-sort"></i></span>
</div>
<ul class="listGroup" ng-show="_pos.length > 0">
<li class="list" ng-repeat="item in _pos track by $index">
<span>
{{item.pos}}
<span class="btn btn-danger btn-xs" ng-click="delPos($index)">
<span class="fa fa-close"></span>
</span>
</span>
<!-- I wanna add the input which can add more list item to the item.pos-->
<div class="input-group">
<a class="input-group-addon add" ng-class=" word.newWordExp ? 'active' : ''" ng-click="addItemOne()"><span class="fa fa-plus"></span></a>
<input type="text" class="form-control exp" autocomplete="off" placeholder="Add explanation" ng-model="word.newWordExp" ng-enter="addExpToPos()">
{{word.newWordExp}}
</div>
</li>
</ul>
Part of the js:
$scope._pos = [];
$scope.addPos = function () {
console.log("You selected something!");
if ($scope.word.pos) {
$scope._pos.push({
pos : $scope.word.pos
});
}
console.dir($scope._pos);
//console.dir($scope.word.newWordExp[posItem]);
};
$scope.delPos = function ($index) {
console.log("You deleted a POS");
$scope._pos.splice($index, 1);
console.dir($scope._pos);
};
$scope.addItemOne = function (index) {
//$scope.itemOne = $scope.newWordExp;
if ($scope.word.newWordExp) {
console.log("TRUE");
$scope._newWordExp.push({
content: $scope.word.newWordExp
});
console.dir($scope._newWordExp);
$scope.word.newWordExp = '';
} else {
console.log("FALSE");
}
};
$scope.deleteItemOne = function ($index) {
$scope._newWordExp.splice($index, 1);
};
So, what am I wannt to do is select one option and append the value to $scope._pos, then display as a list with all of my selection.
And in every list item, add an input filed and add sub list to the $scope._pos value.
n.
explanation 1
explanation 2
explanation 3
adv.
explanation 1
explanation 2
So I don't know how to generate dynamic ng-model and use the value in javascript.
Normaly should like ng-model="word.newExplanation[item]" in HTML, but in javascript, $scope.word.newExplanation[item] said "item is not defined".
can any one help?
If I've understood it correclty you could do it like this:
Store your lists in an array of object this.lists.
The first object in the explanation array is initialized with empty strings so ng-repeat will render the first explanation form.
Then loop over it with ng-repeat. There you can also add dynamically the adding form for your explanation items.
You can also create append/delete/edit buttons inside the nested ng-repeat of your explanation array. Append & delete is already added in the demo.
Please find the demo below or in this jsfiddle.
angular.module('demoApp', [])
.controller('appController', AppController);
function AppController($filter) {
var vm = this,
explainTmpl = {
name: '',
text: ''
},
findInList = function (explain) {
return $filter('filter')(vm.lists, {
explanations: explain
})[0];
};
this.options = [{
name: 'option1',
value: 0
}, {
name: 'option2',
value: 1
}, {
name: 'option3',
value: 2
}];
this.lists = [];
this.selectedOption = this.options[0];
this.addList = function (name, option) {
var list = $filter('filter')(vm.lists, {
name: name
}); // is it in list?
console.log(name, option, list, list.length == 0);
//vm.lists
if (!list.length) {
vm.lists.push({
name: name,
option: option,
explanations: [angular.copy(explainTmpl)]
});
}
};
this.append = function (explain) {
console.log(explain, $filter('filter')(vm.lists, {
explanations: explain
}));
var currentList = findInList(explain);
currentList.explanations.push(angular.copy(explainTmpl));
}
this.delete = function (explain) {
console.log(explain);
var currentList = findInList(explain),
index = currentList.explanations.indexOf(explain);
if (index == 0 && currentList.explanations.length == 1) {
// show alert later, can't delete first element if size == 1
return;
}
currentList.explanations.splice(index, 1);
};
}
AppController.$inject = ['$filter'];
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="appController as ctrl">
<select ng-model="ctrl.selectedOption" ng-options="option.name for option in ctrl.options"></select>
<input ng-model="ctrl.listName" placeholder="add list name" />
<button class="btn btn-default" title="add list" ng-click="ctrl.addList(ctrl.listName, ctrl.selectedOption)"><i class="fa fa-plus"></i>
</button>
<div class="list-group">Debug output - current lists:<pre>{{ctrl.lists|json:2}}</pre>
<div class="list-group-item" ng-repeat="list in ctrl.lists">
<h2>Explanations of list - {{list.name}}</h2>
<h3>Selected option is: {{ctrl.selectedOption}}</h3>
<div class="list-group" ng-repeat="explain in list.explanations">
<div class="list-group-item">
<p class="alert" ng-if="!explain.title">No explanation here yet.</p>
<div class="well" ng-if="explain.title || explain.text">
<h4>
{{explain.title}}
</h4>
<p>{{explain.text}}</p>
</div>Title:
<input ng-model="explain.title" placeholder="add title" />Text:
<input ng-model="explain.text" placeholder="enter text" />
<button class="btn btn-primary" ng-click="ctrl.append(explain)">Append</button>
<button class="btn btn-primary" ng-click="ctrl.delete(explain)">Delete</button>
</div>
</div>
</div>
</div>
</div>

How to detect my position in an ng-repeat loop?

I want to output a list of <li> elements using ng-repeat="obj in links", where links is an array of objects with href and text properties:
$scope.links = [
{ href: '/asdf', text: 'asdf'},
{ href: '/qwer', text: 'qwer'},
/* etc. */
{ href: '/zxcv', text: 'zxcv'}
];
But I want the ng-repeat loop to change what it does when it reaches a certain object in that array. Specifically, I want the loop to create hyperlinks for every object until obj.href==location.path() -- and after that, I just want to write out the text inside a <span>.
Currently, I'm solving this by creating both links and spans each time in the loop:
<ul>
<li ng-repeat="obj in links" ng-class="{active: location.path()==obj.href}">
<a ng-href="{{obj.href}}">{{obj.text}}</a>
<span>{{obj.text}}</span>
</li>
</ul>
plunkr
I then use CSS to hide all hyperlinks after the active class and hide all spans before it. But I don't want to just hide the links after the condition matches -- I want them to be completely removed from the DOM.
So there are two things you must do.
Find the index of the active element
Only show links up to the active index, and after that only show spans
What about this:
In your controller
$scope.lastIndex = 0;
$scope.$watch('links', function(newVal, oldVal){
for(var i=0; i< newVal.length; i++){
if (newVal[i].href == location.path()){
$scope.lastIndex = i
break;
}
}
}
In your HTML :
<ul>
<li ng-repeat="obj in links">
<a ng-if="$index <= {{lastIndex}}" ng-href="{{obj.href}}">{{obj.text}}</a>
<span ng-if="$index > {{lastIndex}}">{{obj.text}}</span>
</li>
</ul>
please see that example http://jsbin.com/cifef/1/edit
for your solution you need to replace $scope.location.href by location.path()
$scope.isLast = false;
$scope.getValue = function(obj)
{
if( obj.href==$location.path() || $scope.isLast )
{
$scope.isLast = true;
obj.isLast = true;
}
};
HTML:
<ul>
<li ng-repeat="obj in links" ng-class="{active: location.href==obj.href}" ng-init="getValue(obj)">
<a ng-href="{{obj.href}}" ng-hide="obj.isLast">{{obj.text}}</a>
<span ng-show="obj.isLast">{{obj.text}}</span>
</li>
</ul>

AngularJS Items selection and DOM manipulation

I have a list of items populated from a JSON, then i need to pick their values and populate another list of selected items, but, in case the particular item is already selected, add one more to count.
app.controller("NewPizza", function($scope, ingredients) {
$scope.selectedIngredients = [];
ingredients.get().then(function(response){
$scope.ingredients = response.data;
});
function _ingredientExist(id) {
return $scope.selectedIngredients.some(function(el) {
return el._id === id;
});
}
function _addMore(selectedIngredient) {
console.log(selectedIngredient)
}
$scope.addIngredient = function(selectedIngredient) {
if($scope.selectedIngredients.length == 0) {
$scope.selectedIngredients.push(selectedIngredient);
}else{
if(_ingredientExist(selectedIngredient._id)) {
_addMore(selectedIngredient._id);
return false;
}
$scope.selectedIngredients.push(selectedIngredient);
}
};
});
The result should be like this
Items to select
Cheese
Bacon
ham
Items selected
2 Cheese (In case user select cheese multiple times)
Bacon
HTML
<div class="">
<h1>New Pizza</h1>
<input ng-model="name"/>
<a>Save pizza</a>
<ul >
<li class="selectIngredients" ng-repeat="ingredient in ingredients" ng-click="addIngredient(ingredient)" id="{{ingredient._id}}">
{{ingredient.name}}
</li>
</ul>
<ul ng-model="selectedIngredients">
<li data-id="{{selectedIngredient._id}}" ng-repeat="selectedIngredient in selectedIngredients track by $index">
<span>1</span> {{selectedIngredient.name}}
</li>
</ul>
</div>
The problem is i dont know how exactly approach this feature because inside a controller DOM manipulation is considered a bad practice, but if i make a directive to deal with i dont know how to populate $scope.selectedIngredients properly.
Thanks!!
One way is that you can add a count to your items model, then copy that model and increment the number.
Created a fiddle: http://jsfiddle.net/ztnep7ay/
JS
var app = angular.module('itemsApp',[]);
app.controller('ItemsCtrl',function($scope) {
$scope.items = [
{name:'Cheese',num:0},
{name:'Bacon',num:0},
{name:'Ham',num:0}
];
$scope.my_items = angular.copy($scope.items);
$scope.addItem = function(item) {
var idx = $scope.items.indexOf(item);
var num = $scope.my_items[idx].num;
$scope.my_items[idx].num = num + 1;
};
$scope.removeItem = function(my_item) {
var idx = $scope.my_items.indexOf(my_item);
var num = my_item.num;
if (num > 0) {
$scope.my_items[idx].num = num -1;
}
};
});
HTML
<div ng-app="itemsApp" ng-controller="ItemsCtrl">
<h4>Available Items</h4>
<table>
<tr ng-repeat="i in items">
<td>{{i.name}}</td>
<td><button ng-click="addItem(i)">+</button></td>
</tr>
</table>
<hr>
<h4>My Items</h4>
<table>
<tr ng-repeat="i in my_items" ng-show="i.num > 0">
<td>{{i.name}} ({{i.num}})</td>
<td><button ng-click="removeItem(i)">Remove 1</button></td>
</tr>
</table>
</div>
You are right that it is considered wrong to update the DOM from a controller in the Angular world.
The reason for that is because you don't need to -- if you update your data - for example, the selectedIngredients array -- angular will update the DOM for you.
One way to accomplish this is to keep track of the count of each ingredient as well as what ingredient was added. You can do this without touching the Ingredient json that you get back from the server.
Then, when you change the count, angular will update the DOM for you.
Here's am example: Live Plnkr Example
HTML
<!DOCTYPE html>
<html ng-app="test">
<head>
<script data-require="angular.js#1.3.0-beta.5" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="PizzaCtrl">
<h4>Toppings</h4>
<ul>
<li ng-repeat="i in ingredients">
{{i.name}} - <a href ng-click="addIngredient(i)">Add</a>
</li>
</ul>
<h4>Selected Toppings</h4>
<ul>
<li ng-repeat="t in toppings">
{{t.ingredient.name}}
<span ng-if="t.count > 1">
x{{t.count}}
</span>
<a href ng-click="removeTopping(t, $index)">Remove</a>
</li>
</ul>
</body>
</html>
JS
angular.module('test', [])
.controller('PizzaCtrl', function($scope) {
/* let's protend we called ingredients.get() here */
$scope.ingredients = [
{ name: 'Cheese', id: 1 },
{ name: 'Bacon', id: 2 },
{ name: 'Ham', id: 3 }
];
/* this will hold both an Ingredient and a Count */
$scope.toppings = [];
/* Check if the ingredient already exists in toppings[], and if so,
* return it. */
function findTopping(ingredient) {
for(var i = 0; i < $scope.toppings.length; ++i) {
if ($scope.toppings[i].ingredient.id == ingredient.id) {
return $scope.toppings[i];
}
}
return null;
}
/* If the ingredient is already there, increment it's count,
* otherwise add it. */
$scope.addIngredient = function(ingredient) {
var t = findTopping(ingredient);
if (t) {
t.count++;
} else {
$scope.toppings.push({ingredient: ingredient, count: 1});
}
};
/* Opposite of the above! */
$scope.removeTopping = function(t, index) {
if (t.count > 1) {
t.count--;
} else {
$scope.toppings.splice(index, 1);
}
};
})

Resources