VueJs recursive infinite v-for loop - javascript

I'm trying to achieve a hierarchy tree where I have a user list and for each of them I set a "senior", so it defines who the senior is. This is how I'm trying to solve the problem:
This is what I'm doing:
data(){
return{
users: [{
id: 1,
fname: 'Joe',
lname: 'Smith',
title: 'Super-Senior',
senior_id: 0,
}, {
id: 2,
fname: 'Bill',
lname: 'Simons',
title: 'Junior-1',
senior_id: 0,
}];
}
},
methods: {
juniors(senior) {
return this.users.filter((user) =>
user.senior_id == senior.id
);
}
}
Then the component tree:
<ul>
<li v-for="chief in juniors(snr_chief)">
<div class="child mx-1">{{chief.lname}} {{chief.fname}}<br /> <small>{{chief.title}}</small>
</div>
<ul>
<li v-for="second in juniors(chief)">
<div class="child mx-1">{{second.lname}} {{second.fname}}<br /> <small>{{second.title}}</small>
</div>
<ul>
<li v-for="third in juniors(second)">
<div class="child mx-1">{{third.lname}} {{third.fname}}<br /> <small>{{third.title}}</small>
</div>
</li>
</ul>
</li>
</ul>
</li>
</ul>
This works perfectly, but of course goes as far as 3 levels down.
I actually don't know how many levels deep the user may go.
So the idea is to have a recursive component but I don't know how to implement it. Something like:
<ul>
<li v-for="chief in juniors(snr_chief)">
<div class="child mx-1">{{chief.lname}} {{chief.fname}}<br /> <small>{{chief.title}}</small>
</div>
<Repeater :juniors="snr_chief" :self="chief" />
</li>
</ul>

function listToTree(data, options) {
options = options || {};
var ID_KEY = options.idKey || 'Id';
var PARENT_KEY = options.parentKey || 'ParentId';
var CHILDREN_KEY = options.childrenKey || 'Items';
var item, id, parentId;
var map = {};
for(var i = 0; i < data.length; i++ ) { // make cache
if(data[i][ID_KEY]){
map[data[i][ID_KEY]] = data[i];
data[i][CHILDREN_KEY] = [];
}
}
for (var i = 0; i < data.length; i++) {
if(data[i][PARENT_KEY]) { // is a child
if(map[data[i][PARENT_KEY]]) // for dirty data
{
map[data[i][PARENT_KEY]][CHILDREN_KEY].push(data[i]); // add child to parent
data.splice( i, 1 ); // remove from root
i--; // iterator correction
} else {
data[i][PARENT_KEY] = 0; // clean dirty data
}
}
};
return data;
}
Vue.component('menu-tree', {
props: ['item'],
template: '<ul class="c-tree"><li>{{item.MenuName}}<menu-tree v-for="y in item.Items" v-bind:item="y"></menu-tree></li></ul>'
})
var app = new Vue({
el:"#app",
data:{
items:[{
Id: 1,
MenuName: "Menu 1",
ParentId: null
},
{
Id: 2,
MenuName: "Menu 2",
ParentId: null
},
{
Id: 3,
MenuName: "Menu 3",
ParentId: null
},
{
Id: 4,
MenuName: "Sub Menu 1 - 1",
ParentId: 1
},
{
Id: 5,
MenuName: "Sub Menu 1 - 2",
ParentId: 1
},
{
Id: 6,
MenuName: "Sub Menu 1 - 1 - 1",
ParentId: 4
},
{
Id: 7,
MenuName: "Sub Menu 1 - 1 - 1 - 1",
ParentId: 6
}],
heirarchyItems: []
},
created: function(){
this.heirarchyItems = listToTree(this.items);
}
});
.c-tree{
list-style: none;
}
.c-tree > li {
margin-left: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<menu-tree v-for="hItem in heirarchyItems" v-bind:item="hItem"></menu-tree>
</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>

Display nested files in children of element ui tree using vue

I am using element ui tree for my vue application. I am implementing 'File browser' type system for my application. In here, files are nested into children.While clicking on child node those nested files or docs will be displaying right side in different container. I am not able to iterate through children and display those files.
**Here is the mocked data :**
data:[{
id: 1,
name: ‘Project A’,
type: ‘folder’,
children: [{
id: 4,
name: 'Project A-1’,
type: ‘folder’,
files: [
{
id: 9,
pid: 4,
name: ‘file 3-A’,
type:’file’,
description: ‘wifi’,
country: ‘USA'
},
{
id: 10,
pid: 4,
name: ‘file 3-B’,
type:’file’,
description: ‘VPN’,
country: ‘USA'
}
]
}
]
},
{
id: 2,
name: 'Services’,
type: 'folder',
children:[],
files: [
{
id: 5,
name: ‘Services-1-A’,
type:’file’,
pid: 2,
description: ‘VPN’,
country: ‘AUS'
},
{
id: 6,
name: ‘Services-1-B’,
type:’file’,
pid: 2,
description: ‘WIFI’,
country: ‘AUS'
}
]
},
{
id: 3,
name: 'Servers',
type: 'folder’,
children:[],
files: [
{
id: 7,
name: ‘Servers-1-A’,
type: ‘file’,
pid: 3,
description: ‘VPN’,
country: ‘CAD'
},
{
id: 8,
name: ‘Servers-1-B',
type: ‘file’,
pid: 3,
description: ‘WIFI’,
country: ‘CAD'
}
]
}]
Here is my UI code
<el-row>
<el-col :span="8" style="background: #f2f2f2">
<div class="folder-content">
<el-tree
node-key="id"
:data="data"
accordion
#node-click="nodeclicked"
ref="tree"
style="background: #f2f2f2"
highlight-current
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span class="icon-folder">
<i class="el-icon-folder" aria-hidden="true"></i>
<span class="icon-folder_text" #click="showFiles(data.id)">{{ data.name }}</span>
</span>
</span>
</el-tree>
</div>
</el-col>
<el-col :span="16"><div class="entry-content">
<ul>
<li aria-expanded="false" v-for="(file,index) in files" :key="index">
<div class="folder__list"><input type="checkbox" :id= "file" :value="file" v-model="checkedFiles" #click="check">
<i class="el-icon-document" aria-hidden="true"></i>
<span class="folder__name">{{file}}</span></div>
</li>
</ul>
</div></el-col>
</el-row>
Show files method:
showFiles(id) {
let f = this.data.filter(dataObject => {
if (dataObject.children && dataObject.children.id === id) {
return false
} else if (!dataObject.children && dataObject.id === id) {
return false
}
return true
})[0]
this.files = f.files
}
}
I am trying to do like this:
I noticed a bug in your filter function. Check line 3 :
showFiles(id) {
let f = this.data.filter(dataObject => {
//isn't this suppose to return true?
if (dataObject.children && dataObject.children.id === id) {
return false
} else if (!dataObject.children && dataObject.id === id) {
return false
}
return true
})[0]
this.files = f.files
}
Why using filter() method to search for single element? It will scan through all the elements. You could just find() instead to improve performance and better readable code.
Try this:
showFiles(id) {
let f = this.data.find(dataObject => dataObject.id == id);
//ensure node was returned
if(f ){
this.files = f.files
}
}
However, You could try and do this in your component instead.
Add another property to the component's data object. Use the new property to hold the selected node.
data(){
//your mock data
tree:[],
//children files being displayed
files:[]
},
methods:{
showFiles(branch){
this.files = branch.files;
}
}
Then pass the whole object to the method
<span class="icon-folder_text" #click="showFiles(data)">{{ data.name }}</span>

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
}
})

orderBy in inner loop on nested ng-repeat

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>

jQuery org chart how to sort the items?

I'am using this plugin: http://th3silverlining.com/2011/12/01/jquery-org-chart-a-plugin-for-visualising-data-in-a-tree-like-structure/
the question is how can I sort the <ul> items in the way I need it? are there some options or maybe some solutions out of the box?
Try this,
Demo
HTML
<div class="topbar">
<div class="topbar-inner">
<div class="container">
<a class="brand" href="#">jQuery Organisation Chart</a>
<ul class="nav">
<li>Github</li>
<li>Twitter</li>
<li>Blog</li>
</ul>
<div class="pull-right">
<div class="alert-message info" id="show-list">Show underlying list.</div>
<pre class="prettyprint lang-html" id="list-html" style="display:none"></pre>
</div>
</div>
</div>
</div>
<ul id="org" style="display:none">
<li>
Food
<ul>
<li id="beer">Beer</li>
<li>Vegetables
Click me
<ul>
<li>Pumpkin</li>
<li>
Aubergine
<p>A link and paragraph is all we need.</p>
</li>
</ul>
</li>
<li class="fruit">Fruit
<ul>
<li>Apple
<ul>
<li>Granny Smith</li>
</ul>
</li>
<li>Berries
<ul>
<li>Blueberry</li>
<li><img src="images/raspberry.jpg" alt="Raspberry"/></li>
<li>Cucumber</li>
</ul>
</li>
</ul>
</li>
<li>Bread</li>
<li class="collapsed">Chocolate
<ul>
<li>Topdeck</li>
<li>Reese's Cups</li>
</ul>
</li>
</ul>
</li>
</ul>
<div id="chart" class="orgChart"></div>
Jquery:
jQuery(document).ready(function() {
$("#org").jOrgChart({
chartElement : '#chart',
dragAndDrop : true
});
$("#show-list").click(function(e){
e.preventDefault();
$('#list-html').toggle('fast', function(){
if($(this).is(':visible')){
$('#show-list').text('Hide underlying list.');
$(".topbar").fadeTo('fast',0.9);
}else{
$('#show-list').text('Show underlying list.');
$(".topbar").fadeTo('fast',1);
}
});
});
$('#list-html').text($('#org').html());
$("#org").bind("DOMSubtreeModified", function() {
$('#list-html').text('');
$('#list-html').text($('#org').html());
prettyPrint();
});
});
////////////You can use this plugin also for json data
////////////Example
$(document).ready(function () {
var ds = [{ id: "2", parentid: "1", text: "India", children: [{ id: "5", parentid: "2", text: "MP", children: [{ id: "7", parentid: "5", text: "Indore", children: [{ id: "8", parentid: "7", text: "Tillore", children: [] }] }] }, { id: "6", parentid: "2", text: "UP", children: [] }] }, { id: "3", parentid: "1", text: "Rusia", children: [] }, { id: "4", parentid: "1", text: "China", children: [] }];
$("#mystring").CustomOrgChart({ dataSource: ds, hasTemplate: true, template: "<div style='color:red;' data-cardid='{0}'><span class='cardadd'>Add</span> <span class='cardedit'>edit</span> <span class='cardremove'>delete</span>{1}</div>",templatefields: ["id","text"] });
$("#custome").jOrgChart({
chartElement: '#string',
dragAndDrop: true
});
});
////////////Plugin
(function ($) {
jQuery.fn.CustomOrgChart = function (options) {
var defaults = {
dataSource: [],
dispalyText: "text",
children: "children",
hasTemplate: false,
template: "{0}",
templatefields: ["text"]
};
var settings = $.extend(true, {}, defaults, options);
if (settings.dataSource) {
var string = "<ul id='custome' style='display:none'>" + GetNodes(settings.dataSource) + "</ul>";
console.log(string);
(this).append(string);
return this;
}
function GetNodes(dataSource) {
var Node = "";
var dataSource = dataSource.slice(0);
var dataSourceArray = $.isArray(dataSource[0]) ? dataSource : [dataSource];
for (var i = 0; i < dataSourceArray.length; i++) {
for (var j = 0; j < dataSourceArray[i].length; j++) {
var text = dataSourceArray[i][j][settings.dispalyText];
var children = dataSourceArray[i][j][settings.children];
Node += "<li>";
var template = settings.template;
var templatefields = settings.templatefields;
if (settings.hasTemplate) {
for (var k = 0; k < templatefields.length; k++) {
template = template.replace("{" + k + "}", dataSourceArray[i][j][templatefields[k]]);
}
Node += template;
}
else {
for (var k = 0; k < templatefields.length; k++) {
template = template.replace("{" + k + "}", dataSourceArray[i][j][templatefields[k]]);
}
Node += template;
}
if (children.length > 0) {
Node += "<ul>" + GetNodes(children) + "</ul>";
}
Node += "</li>";
}
}
return Node;
}
};
})(jQuery);

Categories

Resources