As is shown in the image, there are three objects in the current array. Those three objects are the "parents" and each parent has its own array of "children". This is the tricky part; a parent element can also be a child element of another parent element and can have its own child elements. This part is dynamic, meaning that users will create more "parent" elements and more "children" elements.
I want to display this JSON from the image in the front-end using Aurelia. How would I do that? Any ideas are very much welcome.
Aleksandar.
What you intend to do - render a tree structure on the DOM - using Aurelia, can be achieved with a bunch of options.
Being a recursive structure, it is convenient to define the structure that way; for example (note that the example assumes Typescript and bootstrap):
export interface TreeNode {
name: string;
children?: TreeNode[];
}
For the render, a solution is to define a Custom component holding the "Tree" and a custom component to render the nodes of the tree (in a recursive way).
So, you could do:
<require from="./tree"></require>
...
<tree source.bind="treeData"></tree>
The treeData could be:
treeData: TreeNodeModel[] = [
{ name: "node01", children: [{ name: "child011" }, { name: "child012" }, { name: "child013" }] },
{ name: "node02", children: [{ name: "child021" }, { name: "child022", children: [{ name: "child0221" }, { name: "child0222" }, { name: "child0223" }] }] },
{ name: "node03", children: [{ name: "child031" }] },
{ name: "node04", children: [{ name: "child041" }] },
{ name: "node05", children: [{ name: "child051" }] },
{ name: "node06", children: [{ name: "child061" }] },
{ name: "node07", children: [{ name: "child071" }] },
The implementation of the Tree custom componente would be:
import { bindable } from "aurelia-framework";
import { TreeNodeModel } from "./model";
export class Tree {
#bindable source: TreeNodeModel[];
}
The view for the Tree custom component:
<template>
<require from="./node"></require>
<div class="container">
<div repeat.for="node of source">
<node node.bind="node"></node>
</div>
</div>
</template>
The node custom component:
import { bindable } from "aurelia-framework";
import { TreeNodeModel } from "./model";
export class Node {
#bindable node: TreeNodeModel;
#bindable indent: number = 0;
}
The corresponding view:
<template>
<div style="margin-left: ${indent * 8}px;">
<div>${node.name}</div>
<div repeat.for="child of node.children">
<node node.bind="child" indent.bind="indent + 1"></node>
</div>
</div>
</template>
A working example with al the code is available at:
https://codesandbox.io/s/aurelia-recursive-tree-nodes-6fi9e?file=/src/node.html:0-226
Best wishes.
Related
I am trying to move items from one list to another using vue draggable, but the lists are inside of a component and I can't get it to work. The items are able to move inside a list but not from a list to another.
I have got a Board component containing all the lists and a List component containing the draggable items.
This is the board component:
<template>
<div class="board">
<BoardMenu :users="this.users" :name="this.name"> </BoardMenu>
<div class="boardContent">
<Backlog></Backlog>
<div class="lists">
<List
class="list"
v-for="list in lists"
:key="list.id"
:id="list.id"
:items="list.items"
></List>
</div>
</div>
</div>
</template>
<script>
import BoardMenu from "./BoardMenu";
import Backlog from "./Backlog";
import List from "./List";
export default {
name: "UserIcon",
props: {
id: Number
},
components: {
BoardMenu,
Backlog,
List
},
data() {
return {
name: "BOARDNAME",
users: [{ name: "Bram Coenders" }, { name: "Jasper van der Zwaan" }],
lists: [
{
id: 1,
items: [
{
type: "story",
id: 1,
listId: 1
},
{
id: 2,
listId: 1
},
]
},
{ id: 2, items: [] }
],
backlog: { id: 2 }
};
},
};
</script>
And this is the List component:
<template>
<div class="list">
<div class="list-header">
<h2 id="list-name">{{ name }}</h2>
<p id="list-description">{{ description }}</p>
</div>
<draggable
v-model="items"
:list="this.id"
class="list-list"
>
<div :id="item.id" class="list-item" v-for="item in items" :key="item.id">
<div v-if="item.type == 'story'">
<Story class="story" :id="item.id"></Story>
</div>
<div v-else>
<Task class="task" :id="item.id"></Task>
</div>
</div>
</draggable>
</div>
</template>
<script>
import draggable from "vuedraggable";
import Story from "./Story.vue";
import Task from "./Task.vue";
export default {
name: "List",
components: {
Story,
Task,
draggable
},
props: {
items: []
},
data() {
return {
name: "To do",
description: "this sprint."
};
},
methods :{
newItem: function(){
console.log("test")
}
}
};
</script>
Add :options='{group: "items"}' to your draggable component or you can just try to add the attribute group="items" (if you're using Vue 2.2+)
The Navigator tool comes with the ability to display custom tables, defined through the frontend-config.js file. The quickstart example contains such a file that defines a custom list of contracts.
Is it also possible to display a custom list of templates?
To add a custom list of templates, add a custom view with source.type=="templates".
Here is an example of a custom view that would list all tempaltes that have "Iou:Iou" in their template ID:
iouTemplates: {
type: "table-view",
title: "Iou Templates",
source: {
type: "templates",
filter: [
{
field: "id",
value: "Iou:Iou",
}
],
search: "",
sort: [
{
field: "id",
direction: "ASCENDING"
}
]
},
columns: [
{
key: "id",
title: "Template ID",
createCell: ({rowData}) => ({
type: "text",
value: rowData.id
}),
sortable: true,
width: 80,
weight: 0,
alignment: "left"
}
]
}
I am currently in the process of trying to build out a hierarchy view of users. My end goal is to generate the view using this hierarchy view
or something similar.
The difficulty lies in how the JSON objects used to generate the hierarchy are given. This is a sample response (this response can be much bigger), where pid is the parent id and depth is the distance from the first parent.
response = [
{uid: "abc", pid: null, depth: 1, parent: true},
{uid: "def", pid: "abc", depth: 2, parent: false},
{uid: "ghi", pid: "abc", depth: 2, parent: true},
{uid: "jkl", pid: "ghi", depth: 3, parent: false},
{uid: "mno", pid: "ghi", depth: 3, parent: false},
]
To explain the above response better, here is the visual hierarchy view of it:
image
A lot of the answers and solutions I've seen so far utilize JSON with children nested in each one. Is it possible to generate the view using the json model above?
Any help or insight would be much appreciated! Thanks!
First, you need to convert your self-reference table to a hierarchical table (tree). I suggest you use a custom pipe to do this since you will be able to reuse this pipe in other places.
You can use Reactgular's code, my code from the StackOverflow thread, or write your own code. I create my converter pipe with Reactgular's code:
converter.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'converter'
})
export class ConverterPipe implements PipeTransform {
transform(array: any[], id: string = 'uid', parentId: string = 'pid'): any[] {
const map = array.reduce(
(acc, node) => ((node.items = []), (acc[node[id]] = node), acc),
{}
);
return Object.values(map)
.map(
node => (node[parentId] && map[node[parentId]].items.push(node), node)
)
.filter(node => node[parentId] === null);
}
}
Don't forget to add it to the declaration section of your module:
app.module.ts
import { ConverterPipe } from './converter.pipe';
#NgModule({
declarations: [
ConverterPipe
]
})
export class AppModule { }
Now, you can create your component template and use the approach from the Hierarchy View CodePen. Since you need different markup for branches and leaves, it's handy to use NgTemplateOutlet's and NgIf structural directives. This's a good idea to move the level markup in a template and reuse it when you need to render a tree in Angular. The same idea is illustrated in my answer. Based on the provided CodePen code, your Angular markup may look as the following:
app.component.html
<div class="hv-wrapper">
<ng-template #Item let-item>
<ng-container *ngIf="!item.items.length; else Component">
<p>{{ item.uid }}</p>
</ng-container>
<ng-template #Component>
<div class="hv-item">
<div class="hv-item-parent">
<p>{{ item.uid }}</p>
</div>
<div class="hv-item-children">
<div class="hv-item-child" *ngFor="let child of item.items">
<ng-container
*ngTemplateOutlet="Item; context: { $implicit: child }"
></ng-container>
</div>
</div>
</div>
</ng-template>
</ng-template>
<ng-container *ngFor="let child of response | converter"
><ng-container
*ngTemplateOutlet="Item; context: { $implicit: child }"
></ng-container
></ng-container>
</div>
Here, response is your original array:
app.component.ts
export class AppComponent {
response = [
{ uid: 'abc', pid: null, depth: 1, parent: true },
{ uid: 'def', pid: 'abc', depth: 2, parent: true },
{ uid: 'ghi', pid: 'abc', depth: 2, parent: false },
{ uid: 'jkl', pid: 'ghi', depth: 3, parent: false },
{ uid: 'mno', pid: 'ghi', depth: 3, parent: false }
];
}
Don't forget to use the CodePen SASS styles in your project.
After this, you will a graph like:
This is a StackBlitz project that demonstrates this approach in action.
You can use a reducer to convert the flat array into UID map of nodes, and once you have the map you can populate the children easily. You can then just pick out the root node and use that to render the HTML.
const map = [
{uid: "abc", pid: null, depth: 1, parent: true},
{uid: "def", pid: "abc", depth: 2, parent: true},
{uid: "ghi", pid: "abc", depth: 2, parent: false},
{uid: "jkl", pid: "ghi", depth: 3, parent: false},
{uid: "mno", pid: "ghi", depth: 3, parent: false},
].reduce((acc, node) => (node.children = [], acc[node.uid] = node, acc), {});
const [root] = Object.values(map)
.map(node => (node.pid && map[node.pid].children.push(node), node))
.filter(node => node.pid === null);
console.log(root);
You can render the tree by using the same component recursively, and have the template render the children.
#Component({
selector: 'app-node',
template: `
<span>Node</span>
<app-node [node]="child" *ngFor="let child of node.children"></app-node>
`
})
export class NodeComponent {
#Input()
public node: any;
}
It's not hard to modify the above to match the HTML/CSS you linked to in your question.
I am completly new to HTML/Angular.
I was following the basic tutorial from the angular website (https://angular2-tree.readme.io/docs#basic-usage).
The problem I now have is that I do not understand how I call the tree in my HTML file with the statement from the website:
<tree-root [nodes]="nodes" [options]="options"></tree-root>
To me it is not clear how the output class is important/defines the nodes that are in the tree and what the JSON "code" below the statement means. Any help is greatly appreciated!
EDIT: my component.ts class
#Component({
selector: 'app-component',
template: '<tree-root [nodes]="nodes" [options]="options"></tree-root>',
templateUrl: './app.component.html'
})
export class App2Component {
nodes = [
{
id: 1,
name: 'root1',
children: [
{ id: 2, name: 'child1' },
{ id: 3, name: 'child2' }
]
},
{
id: 4,
name: 'root2',
children: [
{ id: 5, name: 'child2.1' },
{
id: 6,
name: 'child2.2',
children: [
{ id: 7, name: 'subsub' }
]
}
]
}
];
options = {};
}
and my component.html class
<div class="container">
<div class="jumbotron">
<h1>Bootstrap Tutorial</h1>
<h2>Project 2 Demo</h2>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
Status
</div>
<div class="panel-body">
<h3>{{title}}</h3>
<tree-root [nodes]="[
{
id: 1,
name: 'root1',
children: [
{
id: 2,
name: 'child1'
}, {
id: 3,
name: 'child2'
}
]
}
]" [options]="options"></tree-root>
</div>
</div>
</div>
Angular Architecture:-
1) <body> tag contains - root/parent component as <app-root></app-root> ie
parent module which contains a .ts,.html,.css,.spec files.
2) All other modules are inserted/loads inside app.component.html template
either by passing selector of components or loading component with
<router-outlet></router-outlet> and data is passed to child components
through selector of child component(tree-root) and child component
receive that data from parent component through Input();
app.component.ts
#Component({
selector: 'app',
template: '<tree-root [nodes1]="nodes" [options1]="options"></tree-root>'
});
export class App {
nodes = [
{
id: 1,
name: 'root1',
children: [
{ id: 2, name: 'child1' },
{ id: 3, name: 'child2' }
]
},
{
id: 4,
name: 'root2',
children: [
{ id: 5, name: 'child2.1' },
{
id: 6,
name: 'child2.2',
children: [
{ id: 7, name: 'subsub' }
]
}
]
}
];
options = {};
}
tree-root.component.ts
#Component({
selector: 'tree-root', <--- selector
template: ''
});
export treeclassComponent implements OnInit{
#Input() nodes1: any <---- Recieve input from app.component.ts(parent) here
#Input() options1: any <----Recieve input here
constructor(){}
ngOnInit() {
console.log(nodes);
console.log(options);
}
I also recommend the Angular tutorial and the docs.
The JSON array describes the tree nodes and their children (see here).
The tree-component receives two input parameters (see #Input), which are provided by the node and options property.
The nodes array delivers the information about the tree's node and children of the tree.
The tree-component will iterate over the given input array and creates the tree as you see it.
Update:
To create a tree you need to create an array of objects in your *.component.ts, like following:
this.nodes = [
{
id: 1,
name: 'root1',
children: [
{
id: 2,
name: 'child1'
}, {
id: 3,
name: 'child2'
}
]
}
];
The sample above is from Angular2 Tree Nodes.
Update 2:
As I see you can simply use your previously posted html, because you declared the nodes array in your component:
<tree-root [nodes]="nodes" [options]="options"></tree-root>
Also you need to remove the template from your #Component definition, it should look like this:
#Component({
selector: 'app-component',
templateUrl: './app.component.html'
})
....
I have several components that all do the same thing:
display a form (the form varies though, so the HTML differs for each)
capture the form
validate and send the form using a REST API
There are certain things I'm looking to share among the components. For example, the forms all have a list of RadioButtons with the following values:
#Input() radioList: Object[] = [
{ label: 'Unsatisfactory', value: 1 },
{ label: 'Needs Improvement', value: 2 },
{ label: 'Meets Expectations', value: 3 },
{ label: 'Exceeds Expectations', value: 4 },
{ label: 'Outstanding', value: 5 }
];
Is there any way to share stuff like this so if I want to edit that RadioList, I don't have to do it four times?
Extend class?
//property and property-level annotations (#Input) will be picked up by ancestors
export abstract class RadioListAwareComponent{
#Input() radioList: Object[] = [
{ label: 'Unsatisfactory', value: 1 },
{ label: 'Needs Improvement', value: 2 },
{ label: 'Meets Expectations', value: 3 },
{ label: 'Exceeds Expectations', value: 4 },
{ label: 'Outstanding', value: 5 }
];
}
#Component({
template: `<div>Comp1</div>`
})
export class RadioListImplComponentONE extends RadioListAwareComponent{}
#Component({
template: `<div>Comp2</div>`
})
export class RadioListImplComponentTWO extends RadioListAwareComponent{}
You can make your own factory and inject it into your controller https://docs.angularjs.org/guide/providers