NgBootstrap Accordion component - ng-bootstrap

How to have multiple panels > multiple accordions / multiple panels > single accordion closed with a button click?
tried using "closeOthers" mentioned in the api but that does not work

In your html file
<ngb-accordion #acc="ngbAccordion" (panelChange)="beforeChange($event)">
<ngb-panel title="Title 1" id="id1">
<ng-template ngbPanelContent>
Panel 1
</ng-template>
</ngb-panel>
<ngb-panel title="Title 2" id="id2">
<ng-template ngbPanelContent>
Panel 2
</ng-template>
</ngb-panel>
<ngb-panel title="Title 3" id="id3">
<ng-template ngbPanelContent>
Panel 3
</ng-template>
</ngb-panel>
</ngb-accordion>
<button (click)="closeAll(acc)">Close all</button>
In your ts file
panels = [
{ id: 'id1', state: true },
{ id: 'id2', state: true },
{ id: 'id3', state: true }
];
public beforeChange($event: NgbPanelChangeEvent) {
for (let panel of this.panels)
if (panel.id === $event.panelId)
panel.state = !panel.state;
}
closeAll(acc) {
for (let panel of this.panels)
if (!panel.state)
acc.toggle(panel.id);
}
You mantain an array of objects with the panels id and state. When you interact with one of the panels you use beforeChange() to change the state of the panel.
When you click on the button Close all you toggle() only the panels with the open state.

Related

Masking sensitive data in Angular FE with content projection

I'm building a reusable component that will take sensitive data and include a toggle to show/hide that data. Hidden data is replaced with a PrimeNG skeleton. The problem I'm trying to solve is that if the <ng-content> is empty, I'd rather just show a - than the skeleton to prevent having to toggle it, just to find no data.
<ng-container *ngIf="dataIsMasked; then maskData else showData"></ng-container>
<ng-template #maskData>
<span class="skeleton-container">
<p-skeleton></p-skeleton>
</span>
<span class="no-data-placeholder"> - </span>
</ng-template>
<ng-template #showData>
<span class="data-mask-container">
<ng-content></ng-content>
</span>
</ng-template>
..code to toggle button & component variable...
I'm attempting to use some CSS selectors to determine if the content is empty or not, but on instances where the .data-container is empty, the styles aren't applied to .skeleton-container:
.data-container:empty + .skeleton-container {
display: none;
}

How to change the appearance of a button when it is clicked to show an arrow, using css

I want to change the appearance of a button when it its clicked to show an arrow coming out of it. I would like to do it using css. I am building a react application using typescript.
When the next button is clicked the arrow should be removed from the first one and go to the next one.
I have included a picture of the desired outcome.
Here is an example of 2 of the buttons:
<div className="button-container">
<Button
className="text-white font-nunito text active"
onClick={() => onFieldAdd('textField')}
>
<TextFieldsIcon />
<p> Text Box</p>
</Button>
<Button
className="text-white font-nunito text mx-2 pr-15"
onClick={() => onFieldAdd('imageField')}
disabled={!!formId}
>
<AddPhotoAlternateIcon />
<p> Image</p>
</Button>
</div>
This could be the solution you're looking for:
import { useState } from "react";
const YourComponentName = () => {
const [selectedBtn, setSelectedBtn] = useState(null);
const hanldeClick = (e) => setSelectedBtn(e.target.id);
return (
<div>
<button id="btn1" onClick={hanldeClick} className={selectedBtn === "btn1" ? "hasArrow" : ""}>
Button 1
</button>
<button id="btn2" onClick={hanldeClick} className={selectedBtn === "btn2" ? "hasArrow" : ""}>
Button 2
</button>
// SAME THING FOR THE REST OF THE BUTTONS
</div>
)
}
export default YourComponentName;
Just customize it to suit your use case.
Here, I am initializing a state called selectedBtn to null by default, and listening for onClick events on all of the buttons to change that state to the clicked button id. Once it changes, component will rerenders and the CSS class hasArrow will be added to the appropriate button element by checking if selectedBtn state value is equal to the button id, with the help of the ternary conditional operator ?:.

How to create a recursive form with Angular 8?

I need to create a dynamic form with multiple nested items. I've found this example
but i'm not sure it's doing deep recursive since once i've tried to add more levels of nested items - the ui brakes down.
Here is the default json structure with my attempts :
{
key: "common",
title: "main fields",
group: [
{
key: "createdAt",
title: "Create Date",
type: "date"
},
// group:[{
// key: "foo",
// title: "Foo",
// type: "select",
// },
// {
// key: "goo",
// title: "Goo",
// type: "input",
// },
// ]
]
},
So as you can see under "common" - i've added 2 more levels of groups - the first group works fine - but the nested group with key "foo" and "goo" it's working.
I'm pretty sure the problem is in the template / markup
<form [formGroup]="filterForm" class="filter-form">
<ng-template #recursiveList let-filterFields let-fromGroup="fromGroup">
<ng-container *ngFor="let item of filterFields">
<ng-container *ngIf="item.group; else default;">
// in this area i'm not sure it's iterate over deeper nesting...
<p>{{item.key}} </p>
<div [formGroupName]="item.key">
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit:
item.group, fromGroup: {name: item.key}, isChild:true }"></ng-container>
</div>
</ng-container>
<ng-template #default>
<div class="form-group" [formGroupName]="fromGroup.name">
<input [type]="item.type" [formControlName]="item.key"
[placeholder]="item.title" [name]="item.key" />
</div>
</ng-template>
</ng-container>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: filterFields
}">.
From my understanding, there are two issues in the example you provided:
The data structure.
The template.
Data Structure
These are the interfaces I understand from your example:
interface Base {
key: string;
title: string;
}
interface Field extends Base {
type: 'select' | 'input' | 'date' | ...
}
interface Group extends Base {
group: Array<Field | Group>
}
So the JSON example you provided should look something like this:
{
"key": "common",
"title": "main fields",
"group": [
{
"key": "createdAt",
"title": "Create Date",
"type": "date"
},
{
"key": "test",
"title": "Test"
"group": [
{
"key": "foo",
"title": "Foo",
"type": "select"
},
{
"key": "goo",
"title": "Goo",
"type": "input"
}
]
}
]
}
Template
Let's look at a very simplified version of the form:
<form [formGroup]="filterForm">
<ng-container formGroupName="common">
<ng-container *ngTemplateOutlet="control;
context:{ controlName: 'foo', group: 'test' }">
</ng-container>
</ng-container>
<ng-template #control let-group="group" let-controlName="controlName">
<div class="col-md-3">
<div class="form-group" [formGroupName]="group">
<input type="input" [formControlName]="controlName" />
</div>
</div>
</ng-template>
</form>
The code won't work, why? Think about the ng-template as a function. If you want it to know about the formGroupName="common" it needs to be declared within that scope. What matters is the declaration context and not the invocation context, just like regular functions.
This is the working version of the above example:
<form [formGroup]="filterForm">
<ng-container formGroupName="common">
<ng-container *ngTemplateOutlet="control;
context:{ controlName: 'foo', group: 'test' }">
</ng-container>
<ng-template #control let-group="group" let-controlName="controlName">
<div class="col-md-3">
<div class="form-group" [formGroupName]="group">
<input type="input" [formControlName]="controlName" />
</div>
</div>
</ng-template>
</ng-container>
</form>
Things get trickier when you have nested and you need to use recursion.
That's why I think that the approach of using the formGroupName and formControlName directives in this scenario makes things more complicated than they are.
I suggest passing the form control directly into the input by providing the right path to it.
Here is a working example of the idea based on your original example.

Highlight the sidebar item according the progress

I have a sidebar to display the job progress. Now I have three phases.
At the beginning the project has not started yet, so I want all sidebar items are greyed out. I want the current phase is based on the previous phases completion.
On the right side there are three buttons. Here is the logic.
Working on phase 1. When I work on the phase 1, the Phase 1 sidebar item is active and the color is green. Phase 2 and Phase 3 items are still inactive.
Working on phase 2. When I click Phase 1 completed button then we go to phase 2, the Phase 2 is colored as green and Phase 1 is still active but just no color.
Working on phase 3. When I click Phase 2 completed button then we go to phase 3, the Phase 3 is colored as green and Phase 1 and Phase 2 are active but just no color.
When we go to the next phase, the previous phase items have their borders visible.
My ts code:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-sidenav',
templateUrl: './sidenav.component.html',
styleUrls: ['./sidenav.component.css']
})
export class SidenavComponent implements OnInit {
sidenavWidth = 10;
ngStyle: string;
active1: boolean;
active2: boolean;
active3: boolean;
constructor() {
}
ngOnInit() {
this.active1 = false;
this.active2 = false;
this.active3 = false;
}
submit1() {
this.active1 = true;
this.active2 = false;
this.active3 = false;
console.log('1');
}
submit2() {
this.active1 = true;
this.active2 = true;
this.active3 = false;
console.log('2');
}
submit3() {
this.active1 = true;
this.active2 = true;
this.active3 = true;
console.log('3');
}
}
My html:
<mat-sidenav-container fullscreen>
<mat-sidenav #sidenav mode="side" class="example-sidenav" [ngStyle]="{ 'width.em': sidenavWidth }" opened="true">
<div class="logomain">Project progress</div>
<mat-nav-list>
<mat-list-item routerLink="1" [routerLinkActive]="[active1]" >
<div fxFlex="10"></div>
<div *ngIf="sidenavWidth > 6" class="sidenav-item">
<h5 class="lead">Phase 1</h5>
</div>
</mat-list-item>
<mat-list-item routerLink="2" [routerLinkActive]="[active2]">
<div fxFlex="10"></div>
<div *ngIf="sidenavWidth > 6" class="sidenav-item">
<h5 class="lead">Phase 2</h5>
</div>
</mat-list-item>
<mat-list-item routerLink="3" [routerLinkActive]="[active3]">
<div fxFlex="10"></div>
<div *ngIf="sidenavWidth > 6" class="sidenav-item">
<h5 class="lead">Phase 3</h5>
</div>
</mat-list-item>
</mat-nav-list>
</mat-sidenav>
<!-- <div class="example-sidenav-content">
<router-outlet></router-outlet>
</div> -->
<br><br><br>
<section>
<br>
<div class="example-button-row">
<button mat-raised-button color="primary" (click) ="submit1()">Phase 1 completed</button>
</div>
</section>
<mat-divider></mat-divider>
<br>
<section>
<br>
<div class="example-button-row">
<button mat-raised-button color="primary" (click) = "submit2()">Phase 2 completed</button>
</div>
</section>
<br>
.<section>
<br>
<div class="example-button-row">
<button mat-raised-button color="primary" (click) = "submit3()">Phase 3 completed</button>
</div>
</section>
<br>
</mat-sidenav-container>
Here is the stackblize example.
My questions. When I click the buttons the side bar item are not greyed out by the condition. Also I am not sure how to apply the green color to the working phase.
Finally I have many items instead of 3 items in the example. I don't want manually to set boolean values in the click events.
Update:
The correct editable stackblitz link https://stackblitz.com/edit/angular-material-with-angular-sidenav-etrylt
It looks like you just need a simple structure to represent your Phases with a property to indicate if it is completed or not.
interface Phase {
id: number;
name: string;
isComplete: boolean;
}
Then, when you click a button, toggle the isComplete property.
You can define an array of Phase objects and use *ngFor to repeat them in your template. This will prevent your template from growing when you add more phases.
sidebar.component.ts:
public phases = [
{ id: 1, name: "Phase 1", isComplete: false },
{ id: 2, name: "Phase 2", isComplete: false },
{ id: 3, name: "Phase 3", isComplete: false }
];
sidebar.component.html:
<mat-nav-list>
<mat-list-item *ngFor="let phase of phases">
<h5>{{ phase.name }}</h5>
</mat-list-item>
</mat-nav-list>
<section *ngFor="let phase of phases">
<button
(click)="togglePhaseComplete(phase)"
[disabled]="phase.isComplete"
>
{{ phase.name }} completed!
</button>
</div>
</section>
Then, you can use this isComplete property in your template to disable buttons for phases that have already been completed:
[disabled]="phase.isComplete"
You can also make a little helper function to determine if a phase is Active or not in order to display your green background color:
public isActive(phase: Phase) {
// the first incomplete phase found is active
const activePhase = this.phases.find(p => !p.isComplete);
return phase.id === activePhase?.id;
}
Then just add [class.green]="isActive(phase)" to your mat-list-item (assuming you defined a class named "green" in your css).
Here's a working StackBlitz
Now, disabling the nav items for phases that haven't yet been active is a little more work because the mat-list-item doesn't have a disabled property like the button does, so something like this will NOT work:
<mat-list-item *ngFor="let phase of phases"
[disabled]="!isActive(phase) && !phase.isComplete"
>
But, as mentioned in this answer, you can create a css class to disable mouse events and display the item as disabled, so the code would look like:
<mat-list-item *ngFor="let phase of phases"
[class.disabled]="!isActive(phase) && !phase.isComplete"
>
<button
mat-raised-button
color="primary"
[ngClass]="
phase.active1 === true
? 'active'
: completedPhase.indexOf('active1') > -1
? 'pre-active'
: ''
"
>
Phase 1 completed
</button>
<button
mat-raised-button
color="primary"
[ngClass]="
phase.active2 === true ? 'active' : completedPhase.indexOf('active2') > -1
? 'pre-active'
: ''
"
>
Phase 2 completed
</button>

Need Assistance with Vuejs/Veutify (proboblem with input field and deleting items in list)

Im busy learning Veujs and I have two Issues.
I'm creating a simple todo app with CRUD functionality and I am passing data through an Input field and it doesn't want to set to full width if I set any rules for it in CSS
Secondly is I have a delete button that shows when you check the checkbox as complete, but I don't know what I am doing wrong I followed the vuejs documentation googled but the button doesn't want to remove the item from my list
Any help would be appreciated.
<template>
<v-card class="wrapper mx-auto">
<v-list-item>
<v-list-item-content>
<v-list-item-title c class="title">Your Todo's</v-list-item-title>
</v-list-item-content>
<v-spacer></v-spacer>
<v-text-field
prepend-inner-icon="mdi-magnify"
label="Search"
single-line
clearable
clear-icon="mdi-close-circle-outline"
></v-text-field>
</v-list-item>
<v-divider></v-divider>
<v-container id="todoApp">
<v-form name="todo-form" method="post" action v-on:submit.prevent="addTask">
<v-text-field
v-model="addTodoInput"
v-bind:class="{error: hasError}"
label="What are you working on?"
solo
#keydown.enter="create"
>
<v-fade-transition v-slot:append></v-fade-transition>
</v-text-field>
</v-form>
<v-divider class="mb-4"></v-divider>
<v-card class="todo-lists" v-if="lists.length">
<v-list-item v-for="list in lists" :key="list.id">
<v-checkbox v-model="list.isComplete" :color="list.isComplete ? 'success' : 'primary'"></v-checkbox>
<v-list-item-action>
<input class="input-width" type="text" v-model="list.text" />
</v-list-item-action>
<v-spacer></v-spacer>
<v-scroll-x-transition>
<div v-if="list.isComplete">
<v-btn class="ma-2" v-on:click="removeTask(id)" tile large color="error" icon>
<v-icon>mdi-trash-can-outline</v-icon>
</v-btn>
</div>
</v-scroll-x-transition>
</v-list-item>
</v-card>
</v-container>
</v-card>
</template>
<script>
export default {
data: () => ({
addTodoInput: null,
lists: [
{ id: 1, isComplete: true, text: "go home" },
{ id: 2, isComplete: true, text: "go home" }
],
hasError: false // <-- to handle errors
}),
methods: {
addTask: function() {
if (!this.addTodoInput) {
// <--- If no value then we are setting error to `true`
this.hasError = true;
return;
}
this.hasError = false; // <--- If textbox is filled then setting error to `false`
this.lists.push({
id: this.lists.length + 1,
text: this.addTodoInput,
isComplete: false
});
this.addTodoInput = ""; //clear the input after successful submission
},
updateTask: function(e, list) {
e.preventDefault();
list.title = e.target.innerText;
e.target.blur();
},
create() {
console.log("create");
},
removeTodo: function(lists) {
this.todos.splice(list, 1);
}
}
};
</script>
<style scoped>
.wrapper {
height: 100%;
}
input.input-width {
width: 100%;
}
</style>
Your code is right, But you need to remember you variable names, LOL.
Your delete button is calling removeTask but in your methods it is renamed as removeTodo. In the same method you are trying to splice from data todos. But in your data, its renamed as lists. Also, you are passing lists as an argument but it is then used as list
<v-btn class="ma-2" v-on:click="removeTask(id)" tile large color="error" icon>
<v-icon>mdi-trash-can-outline</v-icon>
</v-btn>
removeTodo: function(lists) {
this.todos.splice(list, 1);
}
To get everything work you just need to correct the method to remove todo as below
removeTask: function(list) {
this.lists.splice(list, 1);
}
WIDTH
You need to set width for v-card, so just update your CSS class as follows.
.wrapper {
height: 100%;
width: 100%;
}
for styling and making your input full width use <v-row></v-row> and <v-col></v-col> combination which is important part of Vuetify structure. for example:
<v-row align="center" justify="center">
<v-col cols="12" sm="12">
<your-input-goes-here/>
</v-col>
<v-row>
use tag instead of div in showing delete section, using div will affect your styling.
<template v-if="list.isComplete"> ... </template>
you called removeTask(id) function which doesn't exist !
either change it to removeTodo(list) or write new function !
<v-scroll-x-transition>
<template v-if="list.isComplete">
<v-btn class="ma-2" v-on:click="removeTodo(list)" tile large color="error" icon>
<v-icon>mdi-trash-can-outline</v-icon>
</v-btn>
</template >
</v-scroll-x-transition>

Resources