Highlight the sidebar item according the progress - css

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>

Related

VUE3.JS - Modal in a loop

I have a GET request in my home.vue component.
This query allows me to get an array of objects.
To display all the objects, I do a v-for loop and everything works fine.
<div class="commentaires" v-for="(com, index) of coms" :key="index">
My concern is that I want to display an image by clicking on it (coms[index].imageUrl), in a modal (popup).
The modal is displayed fine but not with the correct image, i.e. the modal displays the last image obtained in the loop, which is not correct.
Here is the full code of my home.vue component
<template>
<div class="container">
<div class="commentaires" v-for="(com, index) of coms" :key="index">
<modale :imageUrl="com.imageUrl_$this.index" :revele="revele" :toggleModale="toggleModale"></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="toggleModale">
</div>
</div>
</template>
<script>
//import axios from "axios";
import axios from "axios";
import Modale from "./Modale";
export default {
name: 'HoMe',
data() {
return {
coms: [],
revele: false
}
},
components: {
modale: Modale
},
methods: {
toggleModale: function () {
this.revele = !this.revele;
},
</script>
Here is my modale.vue component
<template>
<div class="bloc-modale" v-if="revele">
<div class="overlay" #click="toggleModale"></div>
<div class="modale card">
<div v-on:click="toggleModale" class="btn-modale btn btn-danger">X</div>
<img :src=""" alt="image du commentaire" id="modal">
</div>
</div>
</template>
<script>
export default {
name: "Modale",
props: ["revele", "toggleModale", "imageUrl"],
};
</script>
I've been working on it for 1 week but I can't, so thank you very much for your help...
in your v-for loop you're binding the same revele and toggleModale to every modal. When there is only one revele then any time it's true, all modals will be displayed. It's therefore likely you're actually opening all modals and simply seeing the last one in the stack. You should modify coms so that each item has it's own revele, e.g.:
coms = [
{
imageUrl: 'asdf',
revele: false
},
{
imageUrl: 'zxcv',
revele: false
},
{
imageUrl: 'ghjk',
revele: false
}
];
then inside your v-for:
<modale
:image-url="com.imageUrl"
:revele="com.revele"
#toggle-modale="com.revele = false"
></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="com.revele = true">
passing the same function as a prop to each modal to control the value of revele is also a bad idea. Anytime a prop value needs to be modified in a child component, the child should emit an event telling the parent to modify the value. Notice in my code snippet above I replaced the prop with an event handler that turns the revele value specific to that modal to false. Inside each modal you should fire that event:
modale.vue
<div class="btn-modale btn btn-danger" #click="$emit('toggle-modale')">
X
</div>
This way you don't need any function at all to control the display of the modals.

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 disable ngx slick slider when few items?

I'm working with ngx-slick-slider (https://www.npmjs.com/package/ngx-slick-carousel) and it doesn't seem to have a config to disable the slider when there are few items. When there are less items than the needed to display the carousel they displayed centered in the screen. How can I achieve this?
Template:
<div class="scrolling-wrapper" *ngIf="hasUsage">
<ngx-slick-carousel class="carousel"
#slickModal="slick-carousel"
[config]="slideConfig"
(init)="slickInit($event)"
(breakpoint)="breakpoint($event)"
(afterChange)="afterChange($event)"
(beforeChange)="beforeChange($event)">
<div ngxSlickItem *ngFor="let card of data" class="slide">
<div class="card-container">
<card-component
[thecard]="card"
></card-component>
</div>
</div>
</ngx-slick-carousel>
</div>
Component.ts:
slideConfig = {
"slidesToShow": 7,
"slidesToScroll": 4,
"infinite": false
};
slickInit(e:any) {
console.log('slick initialized');
}
breakpoint(e:any) {
console.log('breakpoint');
}
afterChange(e:any) {
console.log('afterChange');
}
beforeChange(e:any) {
console.log('beforeChange');
}
I see two ways to go about this:
Disable the buttons in the slick config { 'arrows': false }
and then create custom elements in your template that hook up to slickNext() and slickPrev(). You can now disable those buttons using something like *ngIf="slides.length <= mySlideLimit
You could also create custom templates for your forward/previous buttons in the config using the nextArrow and prevArrow config options.
I persionallly prefer option 1.
template:
<div class="scrolling-wrapper" *ngIf="hasUsage">
<ngx-slick-carousel class="carousel" ...>
</ngx-slick-carousel>
<button *ngIf="data.length >= 3" (click)="slickModal.slickNext">Next</button>
<button *ngIf="data.length >= 3" (click)="slickModal.slickPrev">Prev</button>
</div>
Component.ts
slideConfig = {
"slidesToShow": 7,
"slidesToScroll": 4,
"infinite": false,
"arrows": false,
};
...
You can find more info on all that suff here: http://kenwheeler.github.io/slick/

How to add class on click event in Aurelia?

I'm new to aurelia. I'm looking to find the best method for adding classes on click events.
I simply want to click approve or request information, and then add a class to the corresponding "contact card". This class would change the background color.
I know it's probably simple, but I thought I'd look here for the best method.
Here's an image to what I've got:
Apologies for the wait, work has been a bit busy.
This is my first time posting on S.O., so I apologize for any expectations I'm not meeting.
<div class="col-sm-4">
<button class="btn btn-success col-sm-12" click.delegate="goodBoi()">
approve contact
</button>
</div>
<div class="col-sm-4">
<button class="btn btn col-sm-12" click.delegate="requestContact()">
request information
</button>
</div>
</div>
the element to be changed is named "list-group-item", containing the
contact's details(code shown above).
<template>
<div class="contact-list">
<ul class="list-group">
<li repeat.for="contact of contacts" class="list-group-item ${contact.id === $parent.selectedId ? 'active' : ''}">
<a route-href="route: contacts; params.bind: {id:contact.id}" click.delegate="$parent.select(contact)">
<h4>${contact.firstName} ${contact.lastName}</h4>
<p>${contact.company}</p>
<p>${contact.email}</p>
<h6>${contact.approval}</h6>
</a>
<a route-href="route: contacts; params.bind: {id:contact.id}">
<p>${contact.phoneNumber}</p>
</a>
</li>
</ul>
</div>
goodBoi() {
let result = confirm("Are you sure you want to confirm this contact?");
if (result === true) {
var standingShell = document.getElementsByClassName("list-group-item");
//im hoping here I would add a class to the new variable//
this.contact.approval = 'approved';
this.save();
}
}
//confirms contact, changing color of approved contact//
//same thing here, just plan to give it a different color//
requestContact() {
let contactRequestText = "request sent to contact";
this.routeConfig.navModel.setTitle(this.contact.approval = contactRequestText);
this.ea.publish(new ContactUpdated(this.contact));
}
There are many ways to set a CSS-class using Aurelia. Following I prepared an example gist:
Template:
<template>
<h1>${message}</h1>
<div class="form-group ${clicked ? 'red' : 'blue'}" style="width: 100px; height: 100px;">
</div>
<div class="form-group">
<button click.delegate="save()">
Click me
</button>
</div>
</template>
And the code class:
#autoinject
export class App {
#bindable clicked = false;
save(){
this.clicked = true;
}
}
https://gist.run/?id=425993b04a977466fa685758389aa2b4
But there are other, cleaner ways:
using ref in a custom element.
custom attributes.
Include jQuery for using e.g. $('#myelement').addClass()

Updating parent of recursive component in Vue

I've made a menu showing systems and their subsystems (can in theory be indefinitely) using a recursive component. A user can both add and delete systems, and the menu should therefore update accordingly.
The menu is constructed using a "tree"-object. This tree is therefore updated when a new system is added, or one deleted. But, I now have a problem; even though the new child component is added when the tree is rerendered, the classes of it's parent-component doesn't update. It is necessary to update this because it defines the menu-element to having children/subsystems, and therefore showing them.
Therefore, when adding a new subsystem, this is presented to the user:
<div class="">
<a href="#/Admin/364" class="item">
<i class=""></i>Testname
<div class=""></div>
</a>
</div>
Instead of this:
<div class="menu transition visible" style="display: block !important;">
<a href="#/Admin/364" class="item">
<i class=""></i>Testname
<div class=""></div>
</a>
</div>
It works fine adding a subsystem to a system which already have subsystems (since the menu-class is already present), but not when a subsystem is added to one without subsystems. In that case, the menu ends up looking like this:
The "opposite" problem also occurs on deletion, since the parent still has the menu-class:
Here's the code for the recursive component:
<template>
<router-link :to="{ name: 'Admin', params: { systemId: id } }" class="item" >
<i :class="{ dropdown: hasChildren, icon: hasChildren }"></i>{{name}}
<div :class="{ menu: hasChildren }">
<systems-menu-sub-menu v-for="child in children" :children="child.children" :name="child.name" :id="child.id" :key="child.id"/>
</div>
</router-link>
</template>
<script type = "text/javascript" >
export default {
props: ['name', 'children', 'id'],
name: 'SystemsMenuSubMenu',
data () {
return {
hasChildren: (this.children.length > 0)
}
}
}
</script>
I'm guessing this has to do with Vue trying to be efficient, and therefore not rerendering everything. Is there therefore any way to force a rerender, or is there any other workaround?
EDIT: JSFiddle https://jsfiddle.net/f6s5qzba/

Resources