orderBy in inner loop on nested ng-repeat - javascript

I want to filter websites that are in the filtered categories and display them in order of their rank.The code snippet is:
<div ng-repeat="category in categories | filter:{will return more than one category}">
<div ng-repeat="website in websites | orderBy:'rank'| filter:{ parent_id : category.id }:true">
{{website.title}},{{website.rank}}
</div>
</div>
But in the nested loop, since orderby is in inner loop, it works for each iteration of outer loop but the overall result is not sorted according to rank. Say there are three categories and filter gives cat1 &cat2. If websites with rank 6,2,5 are is cat1 and 9,1 in cat2 then the result will be 2,5,6,1,9.I want the result to be 1,2,5,6,9.How should I do that ?
Should I pass the category in some function and write the js code to get the array of filtered website and then sort them and return them to template or is there any other better way to do that in template itself?

I think what you want to do, can not be done as is. Anyway you could use a custom filter.
New Answer
This approach gives you a category selection mechanism as another example of how you could use this custom filter.
angular.module('app',[])
// categories
.value('categories', [ { id: 0, title:"first" }, { id: 1, title:"second" }, { id: 2, title:"third" } ])
// websites
.value('websites', [ { rank: 3, parent_id: 2, title: "Alice" },
{ rank: 1, parent_id: 1, title: "Bob" },
{ rank: 9, parent_id: 1, title: "Carol" },
{ rank: 2, parent_id: 0, title: "David" },
{ rank: 4, parent_id: 0, title: "Emma" },
{ rank: 5, parent_id: 0, title: "Foo" } ])
// controller,
.controller('ctrl', ['$scope', 'categories', 'websites', function($scope, categories, websites) {
// categories injected
$scope.categories = categories;
// websites injected
$scope.websites = websites;
// categories selection helper, useful for preselection
$scope.selection = { 0: true, 1:false } // 2: false (implicit)
}])
// categories filter, categories injected.
.filter('bycat', ['categories', function(categories) {
// websites is the result of orderBy :'rank', selection helper passed as paramenter.
return function(websites, selection) {
// just an Array.prototype.filter
return websites.filter(function(website) {
// for each category
for(var i=0; i < categories.length; i++) {
var cat = categories[i];
// if category is selected and website belongs to category
if (selection[cat.id] && cat.id == website.parent_id) {
// include this website
return true;
}
}
// exclude this website
return false;
});
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<span ng-repeat="category in categories">
<label class="checkbox" for="{{category.id}}">
<input type="checkbox" ng-model="selection[category.id]" name="group" id="{{category.id}}" />
{{category.title}}
</label>
</span>
<div ng-repeat="website in websites | orderBy:'rank'| bycat: selection">
<p>Rank:{{website.rank}} - {{website.title}} ({{categories[website.parent_id].title}})</p>
</div>
</div>
Old Ansewer
Se code comments.
angular.module('app',[])
// categories will be injected in custom filter.
.value('categories', [ { id: 1, title:"first" }, { id: 2, title:"second" } ])
.controller('ctrl', function($scope) {
// sample websites
$scope.websites = [ { rank: 1, parent_id: 2, title: "Site w/rank 1" },
{ rank: 9, parent_id: 2, title: "Site w/rank 9" },
{ rank: 2, parent_id: 1, title: "Site w/rank 2" },
{ rank: 4, parent_id: 1, title: "Site w/rank 4" },
{ rank: 5, parent_id: 1, title: "Site w/rank 5" } ];
})
// custom filter, categories injected.
.filter('bycat', ['categories', function(categories) {
// websites is the result of orderBy :'rank'
return function(websites, catText) {
// just an Array.prototype.filter
return websites.filter(function(website) {
// if no filter, show all.
if (!catText) return true;
for(var i=0; i < categories.length; i++) {
var cat = categories[i];
// if matches cat.title and id == parent_id, gotcha!
if (cat.title.indexOf(catText) != -1 && cat.id == website.parent_id) {
return true;
}
}
// else were
return false;
});
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<input type="text" ng-model="filterText">
<p>Try "first" and "second"</p>
<div ng-repeat="website in websites | orderBy:'rank'| bycat: filterText ">
{{website.title}},{{website.rank}}
</div>
</div>

Related

How get a load more button for a li list vue js

I'm trying to implement a load more button to my code. I would be able to this in javascript but I can't find a similar way in vue.
This is my vue code. I've tried asking the element with the company id but it's not reactive so I can't just change the style.
<main>
<ul>
<li v-for="company in companiesdb" :key="company.id" v-bind:id="company.id" ref="{{company.id}}" style="display: none">
{{company.name}}<br>
{{company.email}}
</li>
</ul>
</main>
this is my failed atempt of doing it in javascript but as I've mentioned before ref is not reactive so I can't do it this way
limitView: function (){
const step = 3;
do{
this.numberCompaniesVis ++;
let li = this.$refs[this.numberCompaniesVis];
li.style = "display: block";
}while (this.numberCompaniesVis % 3 != step)
}
I think the way you are approaching this problem is a little complex. Instead, you can create a computed variable that will change the number of lists shown.
Here's the code
<template>
<div id="app">
<ul>
<li v-for="(company, index) in companiesLoaded" :key="index">
{{ company }}
</li>
</ul>
<button #click="loadMore">Load</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
companiesdb: [3, 4, 1, 4, 1, 2, 4, 4, 1],
length: 5,
};
},
methods: {
loadMore() {
if (this.length > this.companiesdb.length) return;
this.length = this.length + 3;
},
},
computed: {
companiesLoaded() {
return this.companiesdb.slice(0, this.length);
},
},
};
</script>
So instead of loading the list from companiesdb, create a computed function which will return the new array based on companiesdb variable. Then there's the loadMore function which will be executed every time user clicks the button. This function will increase the initial length, so more lists will be shown.
Here's the live example
Just use computed property to create subset of main array...
const vm = new Vue({
el: '#app',
data() {
return {
companies: [
{ id: 1, name: "Company A" },
{ id: 2, name: "Company B" },
{ id: 3, name: "Company C" },
{ id: 4, name: "Company D" },
{ id: 5, name: "Company E" },
{ id: 6, name: "Company F" },
{ id: 7, name: "Company G" },
{ id: 8, name: "Company H" },
{ id: 9, name: "Company I" },
{ id: 10, name: "Company J" },
],
companiesVisible: 3,
step: 3,
}
},
computed: {
visibleCompanies() {
return this.companies.slice(0, this.companiesVisible)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="company in visibleCompanies" :key="company.id" :id="company.id">
{{company.name}}
</li>
</ul>
<button #click="companiesVisible += step" v-if="companiesVisible < companies.length">Load more...</button>
</div>

angularjs - Change the order of a list with update of the other list entries

I have a Key/Value List with 3 Entries, e.g.
0, Value1
1, Value2
2, Value3
3, Value4
If I change 2 to 0, the entry with index 2 should slip to the position 0 and the additional elements slip around a position to the back.
eg from 0-1-2-3 will then be 2-0-1-3
HTML
<div ng-controller="MyCtrl">
<div class="row-sort" ng-repeat="(key1, value1) in selectList">
<select class="row-select">
<option ng-repeat="(key2, value2) in selectList" value={{key2}} ng-selected="key1 == key2">{{key2}}</option>
</select>
<span class="row-label">{{value1.name}}</span>
</div>
</div>
JavaScript
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.selectList = [{
idx: 0,
name: 'Value1'
}, {
idx: 1,
name: 'Value2'
}, {
idx: 2,
name: 'Value3'
}, {
idx: 3,
name: 'Value4'
}]
}
JS Fiddle Link
EDIT 1: It sould work like the jQuery Sortable Plugin, but only with Select-Boxes ...
Jquery Sortable
Anyone a idea how getting this work properly and correct with AngularJS?
Its required for Angular UI Grid (Reorder Columns)
Thank you very much!
You can add to your list selected key to bind it to ng-models for select and use orderBy:
$scope.selectList = [{
idx: 0,
selected: '0',
name: 'Value1'
}, {
idx: 1,
selected: '1',
name: 'Value2'
}, {
idx: 2,
selected: '2',
name: 'Value3'
}, {
idx: 3,
selected: '3',
name: 'Value4'
}];
HTML
<div class="row-sort" ng-repeat="(key1, value1) in selectList | orderBy : 'selected'">
<select class="row-select" ng-model="value1.selected">
<option ng-repeat="(key2, value2) in selectList"
value={{key2}}>{{key2}}</option>
</select>
<span class="row-label">{{value1.name}}</span>
</div>
EDIT
You can write watcher and replace positions for your items:
$scope.$watch(function () {
return $scope.selectList;
},
function (newVal, oldVal) {
var selectedItemId;
var selected;
angular.forEach($scope.selectList, function(item){
if(item.idx !== parseInt(item.selected)){
selectedItemId = item.idx;
selected = item.selected;
}
});
angular.forEach($scope.selectList, function(item){
if(item.selected === selected){
item.selected = ''+selectedItemId;
item.idx = selectedItemId;
selectedItemId = undefined;
selected = undefined;
}
if(item.idx !== parseInt(item.selected)){
item.idx = parseInt(item.selected);
}
});
},true);
Demo Fiddle 2

How to filter values in ng-repeat by partial string property?

I have this controller with products and filterStr variables:
app.controller('FooCtrl', function($scope) {
$scope.products = [
{ id: 1, name: 'Michael', data: 'Berlin' },
{ id: 2, name: 'Maria', data: 'Moscow'}
{ id: 3, name: 'Alex', data: 'Aragon'},
{ id: 4, name: 'Mara', data:'Paris' }
/*... etc... */
];
$scope.filterStr = 'ar';
});
Here ng-repeat:
<div ng-repeat="product in products | filter: { name: filterStr }">
In ng-repeat above I want to display objects only that has inside name property this combination of letters ar:
id: 2, name: 'Maria', data: 'Berlin'
id: 4, name: 'Mara', data:'Paris'
What is the best way to implement the desired filter in angularjs?
You can try custom filter
<div ng-repeat="product in products | search:'name':'ar' ">
{{product.name}}
</div>
app.filter('search', function() {
return function(input,key,searchText) {
var out = [];
angular.forEach(input, function(obj) {
if(obj[key]){
var text = obj[key].search(searchText)
if(text > 0) {
out.push(obj);
}
}
});
return out;
}
});
First: Many thanks to #vignesh for a great answer! The code #vignesh provided encourage me to make such an implementation in my own project!
Second: While trying to implement this filter myself, I've noticed that this solution can be improved a little bit more...
app.filter('search', function()
{
return function(list, field, partialString)
{
// if the search string is invalid or empty - return the entire list
if (!angular.isString(partialString) || partialString.length == 0) return list;
var results = [];
angular.forEach(list, function(item)
{
if (angular.isString(item[field]))
{
if (item[field].search(new RegExp(partialString, "i")) > -1)
{
results.push(item);
}
}
});
return results;
}
});
I then used it with a search input and a repeater
<input ng-model="myPartialSearchString" />
<div ng-repeat="item in items | search:'name':myPartialSearchString ">
{{item.name}}
</div>
you can use the filter function from angular:
<input type="text" placeholder="Search" ng-model="search.$">
<input type="checkbox" ng-model="strict"> Exact match only
<div ng-repeat="product in products | filter: filter:search:strict">

Angular js filter array inside array

I am having a hard time doing an Angular filter to solve a problem as below.
The filter logic is as below:
1) If all listItem of that item has qtyLeft != 0, do not display that item
2) If any of the listItem of that item has qtyLeft == 0, display the item title as well as coressponding listItem that have qtyLeft == 0
Here's a basic example of my data structure, an array of items:
$scope.jsonList = [
{
_id: '0001',
title: 'titleA',
list: {
listName: 'listNameA',
listItem: [
{
name: 'name1',
qtyLeft: 0
},
{
name: 'name2',
qtyLeft: 0
},
]
}
},
{
_id: '0002',
title: 'titleB',
list: {
listName: 'listNameB',
listItem: [
{
name: 'name3',
qtyLeft: 2
},
{
name: 'name4',
qtyLeft: 0
},
]
}
},
{
_id: '0003',
title: 'titleC',
list: {
listName: 'listNameC',
listItem: [
{
name: 'name5',
qtyLeft: 2
},
{
name: 'name6',
qtyLeft: 2
},
]
}
},
]
Here is the final expected outcome:
<div ng-repeat="item in jsonList | filter: filterLogic">
<div> </div>
</div>
// final outcome
<div>
<div>Title: titleA, ListItem: Name1, Name2</div>
<div>Title: titleB, ListItem: Name4</div>
</div>
Created working Plunkr here. https://plnkr.co/edit/SRMgyRIU7nuaybhX3oUC?p=preview
Do not forget to include underscore.js lib in your project if you are going to use this directive.
<div ng-repeat="jsonItem in jsonList | showZeroElement track by $index">
<div>Title:{{ jsonItem.title}}, ListItem:<span ng-repeat="item in
jsonItem.list.listItem track by $index" ng-if="item.qtyLeft==0">
{{item.name}}</span>
</div>
</div>
And
app.filter('showZeroElement', function() {
return function(input) {
var items = []
angular.forEach(input, function(value, index) {
angular.forEach(value.list.listItem, function(val, i) {
var found = _.findWhere(items, {
'title': value.title
})
if (val.qtyLeft === 0 && found === undefined) {
items.push(value)
}
})
})
return items
}
})

Angularjs map array to another array

I have two arrays, Users and Employments like so:
Users = [{id:1, name: "ryan"}, {id:2, name:"Julie"}]
Employments = [{user_id: 1, title: "manager"}, {user_id: 2, title: "Professor"}]
I'd like to display the Employments array in an ng-repeat like so:
<li ng-repeat="employment in Employments">
{{employment.user.name}}
</li>
How do I map the Users array to the Employments array?
If you want the employee name to get displayed based on id, the simplest way is just pass that id to a function and return the name, like as shown below
Working Demo
html
<div ng-app='myApp' ng-controller="ArrayController">
<li ng-repeat="employment in Employments">{{getEmployeeName(employment.user_id)}}
</li>
</div>
script
var app = angular.module('myApp', []);
app.controller('ArrayController', function ($scope) {
$scope.Users = [{
id: 1,
name: "ryan"
}, {
id: 2,
name: "Julie"
}];
$scope.Employments = [{
user_id: 1,
title: "manager"
}, {
user_id: 2,
title: "Professor"
}];
$scope.getEmployeeName = function (empId) {
for (var i = 0; i < $scope.Users.length; i++) {
if ($scope.Users[i].id === empId) {
return $scope.Users[i].name;
}
};
};
});
UPDATE 2
If you want to embed the User array in the Employments array, try the following stuff
$scope.Users = [{id: 1, name: "ryan"}, {id: 2, name: "Julie"}];
$scope.Employments = [{user_id: 1, title: "manager"},
{user_id: 2, title: "Professor"}
];
code for flattening Employments array by adding User properties
angular.forEach($scope.Users, function (user, userIndex) {
angular.forEach($scope.Employments, function (employee, employeeIndex) {
if (employee.user_id === user.id) {
employee.name = user.name;
}
});
});
Output
$scope.Employments = [ { user_id: 1, title: "manager", name: "ryan" },
{ user_id: 2, title: "Professor", name: "Julie" }
]
Working Demo
UPDATE 3
Code for making a nested employee structure like as shown below from $scope.Users and $scope.Employments
$scope.employees = [];
angular.forEach($scope.Employments, function (employee, employeeIndex) {
var employeeObject = {};
employeeObject.title = employee.title;
angular.forEach($scope.Users, function (user, userIndex) {
if (employee.user_id === user.id) {
employeeObject.user = user;
}
});
$scope.employees.push(employeeObject);
});
Output
[ { title: "manager", user: { "id": 1, "name": "ryan" } },
{ title: "Professor", user: { "id": 2, "name": "Julie" } }
]
Working Demo
If you wanted to match up the two following arrays purely with a template you could take the following arrays
Users = [{id:1, name: "ryan"}, {id:2, name:"Julie"}]
Employments = [{user_id: 1, title: "manager"}, {user_id: 2, title: "Professor"}]
And nest a repeat like:
<li ng-repeat="employment in Employments">
<div ng-repeat="user in Users" ng-if="user.id === employment.user_id" >
{{user.name}}:{{employment.title}}
</div>
</li>
Two more nice little thing to do to avoid any risk of getting those brackets showing on a slow page load is to use the ng-bind and prefix the attributes with data so its with the html spec
<li data-ng-repeat="employment in Employments">
<div data-ng-repeat="user in Users" data-ng-if="user.id === employment.user_id" >
<span data-ng-bind="user.name"></span>:<span data-ng-bind="employment.title"></span>
</div>
</li>
I know you didn't have the need for anything but the name, but figured a quick example of using the outer loop in the inner still could be helpful. Also this would be the case for ng-init if you needed to reference the the $index of the outer ng-repeat from the inner, but that might be more than you're looking for here.
Plunker
This sorts the users names into the employments array:
var sortUsers = function() {
var i = 0;
for (i; i < $scope.users.length; i++) {
console.log($scope.users[i].id)
for(var z = 0; z < $scope.employments.length; z++) {
if($scope.employments[z].user_id === $scope.users[i].id) {
$scope.employments[z].name = $scope.users[i].name;
}
}
}
}
HTML:
<ul>
<li ng-repeat="employment in employments">
{{employment.name}}
</li>
</ul>
I dealt similar problem yesterday. If you want to use js, have to loop twice.
I recommend to use the best way is to select in one query by join table if data come from single database.
You select User by one query, and Employment for another query in database. Then, twice ng-repeat to re-arrange. Here is my solution.
select users.*, employments.title from `users` inner join `employments` where users.id = employments.user_id;
Hope be be helpful.

Categories

Resources