I'm building a SPA in Angular4/typescript.
I have several javascripts which manipulate DOM (add/remove CSS classes) which I want to integrate to the application.
What is the best way to do it?
Currently the structure of the application is the following:
-app:
-app.component.ts
-app.module.ts
-menu.component.ts
-menu.view.html
-menu.css
menu.component.ts handles data display of the application.
I want to integrate the following script:
<script>
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter(){
this.classList.add('trigger-enter');
setTimeout(() => this.classList.contains('trigger-enter') &&
this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
const dropdownCords = dropdown.getBoundingClientRect();
const navCoords = nav.getBoundingClientRect();
const coords = {
height: dropdownCords.height,
width: dropdownCords.width,
top: dropdownCords.top - navCoords.top,
left: dropdownCords.left- navCoords.left
};
background.style.setProperty('width', `${coords.width}px`);
background.style.setProperty('height', `${coords.height}px`);
background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`);
}
function handleLeave(){
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
</script>
CSS:
nav {
position: relative;
perspective: 600px;
}
nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
}
.cool > li {
position: relative;
display:flex;
justify-content: center;
}
.cool > li > a {
color: yellow;
text-decoration: none;
font-size: 20px;
background: rgba(0,0,0,0.2);
padding:10px 20px;
display: inline-block;
margin:20px;
border-radius:5px;
}
.dropdown {
opacity: 0;
position: absolute;
overflow: hidden;
padding:20px;
top:-20px;
border-radius:2px;
transition: all 0.5s;
transform: translateY(100px);
will-change: opacity;
display: none;
}
.trigger-enter .dropdown {
display: block;
}
.trigger-enter-active .dropdown {
opacity: 1;
}
.dropdownBackground {
width:100px;
height:100px;
position: absolute;
background: #fff;
border-radius: 4px;
box-shadow: 0 50px 100px rgba(50,50,93,.1), 0 15px 35px rgba(50,50,93,.15), 0 5px 15px rgba(0,0,0,.1);
transition:all 0.3s, opacity 0.1s, transform 0.2s;
transform-origin: 50% 0;
display: flex;
justify-content: center;
opacity:0;
}
.dropdownBackground.open {
opacity: 1;
}
.arrow {
position: absolute;
width:20px;
height:20px;
display: block;
background:white;
transform: translateY(-50%) rotate(45deg);
}
html:
<nav class="top" menuElement>
<div class="dropdownBackground">
<span class="arrow"></span>
</div>
<ul class="cool">
<li>
Some information
<div class="dropdown dropdown1">
Info
</div>
</li>
<li>
More information
<ul class="dropdown">
<li>
some info
</li>
</ul>
</li>
<li>
Other Links
<ul class="dropdown dropdown3">
<li>some links</li>
</ul>
</li>
</ul>
</nav>
There are 3 querySelector. I tried to implement the functionality by defining a Directive:
menu.directive.ts:
import {Directive, HostListener, ElementRef, Renderer2} from '#angular/core';
#Directive({
selector: '[menuElement]',
})
export class MenusDirective {
constructor(
private renderer: Renderer2,
private el: ElementRef
){}
//menuLi = this.el.nativeElement.querySelectorAll('.cool > li');
background = this.el.nativeElement.querySelector('.dropdownBackground');
handleEnter(target){
this.renderer.addClass(target, 'trigger-enter');
setTimeout(() => target.classList.contains('trigger-enter') &&
this.renderer.addClass(target, 'trigger-enter-active'), 150);
this.background.classList.add('open');
const dropdown = target.querySelector('.dropdown');
const dropdownCords = dropdown.getBoundingClientRect();
const filterNavCoords = this.el.nativeElement.getBoundingClientRect();
const coords = {
height: dropdownCords.height,
width: dropdownCords.width,
top: dropdownCords.top - filterNavCoords.top,
left: dropdownCords.left- filterNavCoords.left
};
this.background.style.setProperty('width', `${coords.width}px`);
this.background.style.setProperty('height', `${coords.height}px`);
this.background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`);
}
handleLeave(target){
this.renderer.removeClass(target, 'trigger-enter');
this.renderer.removeClass(target, 'trigger-enter-active');
this.background.classList.remove('open');
}
#HostListener('mouseenter', ['$event']) onMouseEnter(event: Event) {
this.handleEnter(event.target);
}
#HostListener('mouseleave', ['$event']) onMouseLeave(event: Event) {
this.handleLeave(event.target);
}
}
If I define the selector [menuElement] equals to the upper nav, I'll be able to select :
background = this.el.nativeElement.querySelector('.dropdownBackground');
or
triggers = this.el.nativeElement.querySelectorAll('.cool > li');
but event Listener would be bind on the whole nav, so there is no way to know which one of the '.cool > li' is selected.
If I define [menuElement] as '.cool > li', I don't find a way to select '.dropdownBackground' or the upper nav.
const background = document.querySelector('.dropdownBackground');
returns null (I tried also getElementByClassName, etc.)
I can't define them in different Directives as they are manipulated simultaneous.
Also I tried to add the function in HTML:
<li (mouseenter)="handleEnter($event.target)" (mouseleave)="handleLeave($event.target)">
But the functions are not recognized as defined in menu.directive and not in menu.component.
What are the possible solution?
- integrate Javascript as is (loaded after the DOM is loaded)
- select several DOM object, but bind a EventListener to one of them.
Thanks a lot!
Veronika
The way I figured out to work:
add Event listeners in view.html:
<li (mouseenter)="handleEnter($event.target);" (mouseleave)="handleLeave($event.target)">
Define handleEnter and handleLeave functions in menu.component.ts, add MenuDirective as a provider:
menu.component.ts:
import { MenuDirective } from './menu.directive';
#Component({
selector: 'menu',
templateUrl: './menu.view.html',
styleUrls: [ './app.component.css'],
providers: [MenuDirective]
})
export class MenuComponent {
constructor(
private menuDirective: MenuDirective,
){}
handleEnter(target): void {
this.menuDirective.handleEnter(target);
}
handleLeave(target): void {
this.menuDirective.handleLeave(target);
}
}
and menu.directive.ts:
import {Directive, HostListener, ElementRef, Renderer2} from '#angular/core';
#Directive({
selector: '[menuElement]',
})
export class MenuDirective {
constructor(
private renderer: Renderer2,
private el: ElementRef
){}
handleEnter(target){
this.renderer.addClass(target, 'trigger-enter');
setTimeout(() => target.classList.contains('trigger-enter') &&
this.renderer.addClass(target, 'trigger-enter-active'), 150);
this.el.nativeElement.querySelector('.dropdownBackground').classList.add('open');
const dropdown = target.querySelector('.dropdown');
const dropdownCords = dropdown.getBoundingClientRect();
const filterNavCoords = this.el.nativeElement.getBoundingClientRect();
const coords = {
height: dropdownCords.height,
width: dropdownCords.width,
top: dropdownCords.top - filterNavCoords.top,
left: dropdownCords.left- filterNavCoords.left
};
this.el.nativeElement.querySelector('.dropdownBackground').style.setProperty('width', `${coords.width}px`);
this.el.nativeElement.querySelector('.dropdownBackground').style.setProperty('height', `${coords.height}px`);
this.el.nativeElement.querySelector('.dropdownBackground').style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`);
}
handleLeave(target){
this.renderer.removeClass(target, 'trigger-enter');
this.renderer.removeClass(target, 'trigger-enter-active');
this.el.nativeElement.querySelector('.dropdownBackground').classList.remove('open');
}
}
It seems too complex for such simple feature...
Related
I would like to open and close overlay using single button, so when the button is clicked an additional class is added, when closed the class is removed and overlay is closed.
So far I wrote the code that opens overlay and add/remove the class to the button.
Also I've created the method to close the overlay but I'm struggling to create a proper event to actually close it, so I would be happy if anyone can guide me a bit.
I think there should be an 'if' statement within the events() checking if the button have added class, if so, the overlay will be closed using this function element.classList.contains("active");
Also the button is animated, so when class is added 3 bars (hamburger icon) becomes X and this is the main reason I don't want to have separate buttons to open and close, I already achieved that but this is not what I'm looking for.
class OverlayNav {
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayOpen())
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x")
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
injectHTML() {
document.body.insertAdjacentHTML('beforeend', `
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`)
}
}
export default OverlayNav
You can make a function with a if statement handle Opening and closing the overlay
Here is your code edited
class OverlayNav {
constructor() {
this.injectHTML();
this.hamburgerIcon = document.querySelector(".menu-icon");
this.events();
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayHandle());
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x");
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
overlayHandle() {
if (element.classList.contains("active")) {
this.overlayClose();
} else {
this.overlayOpen();
}
}
injectHTML() {
document.body.insertAdjacentHTML(
"beforeend",
`
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`
);
}
}
export default OverlayNav;
You can add a property that keeps track of the state of the nav bar.
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
this.overlayVisible=true;
}
Then add a method that toggles the state and calls the right open/close-method:
toggleOverlay() {
if (this.overlayVisible)
this.overlayOpen();
else
this.overlayClose();
this.overlayVisible=!this.overlayVisible;
}
Finally make the events method call toggleOverlay() instead of overlayOpen().
events() {
this.hamburgerIcon.addEventListener("click", () => this.toggleOverlay())
}
Alternativly, a pure HTML + CSS solution, using only the details element and the [open] CSS attribute selector.
.overlay > p {
padding: 1rem;
margin: 0;
display: flex;
flex-direction: column;
width: 25vw
}
.overlay summary {
padding: 1rem 0.5rem;
cursor: pointer;
max-height: 90vh;
overflow: auto;
font-size: 4em;
list-style: none;
}
.overlay[open] summary {
background: black;
color: white;
padding: 0.5rem;
font-size: 1em;
}
.overlay[open] {
position: fixed;
/* top: calc(50% - 25vw); */
left: calc(50% - 15vw);
outline: 5000px #00000090 solid;
border: 5px red solid;
border-radius: 0.5rem;
font-size: 1em
}
.overlay[open] summary::after {
content: '❌';
float: right;
}
<details class="overlay">
<summary>☰</summary>
<p>
Hello world!
</p>
</details>
Is there any way to handle how CSS is applied to a web component like you can do with attributes using attributeChangedCallback.
I am working on a couple web components that would benefit from being styled with CSS classes, but I need to change multiple styles for it to look correct (e.g. if you set the color of the control, the user would expect the border color of one element and the font color of another to change in the shadow DOM).
Is there any way to get .usingCSS { color: red; } to change the color of the toggle switch in the following simple web component example?
// based on https://www.w3schools.com/howto/howto_css_switch.asp
class W3schoolsToggleSwitch extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({ mode: "open" });
this.span = document.createElement("span");
this.span.innerHTML = `
<style>
/* The switch - the box around the slider */
.switch {
--color: #2196F3;
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: var(--color);
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
</style>
<label class="switch">
<input type="checkbox" checked>
<span class="slider round"></span>
</label>
`;
shadow.appendChild(this.span);
}
static get observedAttributes() {
return ["color"];
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(name, newValue);
if ("color" === name) {
this.shadowRoot
.querySelector(".switch")
.style.setProperty("--color", newValue);
}
}
get color() {
return this.getAttribute("color");
}
set color(value) {
return this.setAttribute("color", value);
}
}
customElements.define("w3schools-toggle-switch", W3schoolsToggleSwitch);
.usingCSS {
color: red;
}
default:
<w3schools-toggle-switch></w3schools-toggle-switch>
<br><br> color attribute used to change the color to green:
<w3schools-toggle-switch color="green"></w3schools-toggle-switch>
<br><br> can you change the color with CSS?:
<w3schools-toggle-switch class="usingCSS"></w3schools-toggle-switch>
From the outside with <link>
You could apply CSS style to a Web Component uning a <link> element in the Shadow DOM.
#shadow-root
<link rel="stylesheet" href="default.css">
attributeChangedCallback( name, old, value ) {
if (name === 'class')
this.shadowRoot.querySelector( 'link' ).href = value + ".css"
}
With style defined inside Shadow DOM :host() pseudo-class function
You can apply different styles based on the context. You can combine multiple classes.
customElements.define( 'custom-element', class extends HTMLElement {
constructor() {
super()
this.attachShadow( { mode: 'open' } )
.innerHTML = `
<style>
:host( .red ) { color: red }
:host( .blue ) { color: blue }
:host( .border ) { border: 1px solid }
</style>
Hello`
}
} )
ce1.onclick = ev => ev.target.classList.add( 'border' )
<custom-element class="red" id="ce1"></custom-element>
<custom-element class="blue border"></custom-element>
On Chrome / Opera: with Constructable stylesheets
Create one (or several) Stylesheet(s) and apply it(them) to the Shadow DOM. You can apply multiple stylesheets to the same Shadow DOM.
var ss = []
ss['red'] = new CSSStyleSheet
ss.red.replaceSync( 'span { color: red }' )
ss['green'] = new CSSStyleSheet
ss.green.replaceSync( 'span { color: green }' )
ss['border'] = new CSSStyleSheet
ss.border.replaceSync( 'span { border: 1px solid }' )
customElements.define( 'custom-element', class extends HTMLElement {
constructor() {
super()
this.attachShadow( { mode: 'open' } )
.innerHTML = `<span>Hello</span>`
}
static get observedAttributes() { return [ 'class' ] }
attributeChangedCallback() {
this.shadowRoot.adoptedStyleSheets = [ ...this.classList ].map( cl => ss[ cl ] )
}
} )
ce1.onclick = ev => ev.target.classList.add( 'border' )
<custom-element class="red" id="ce1"></custom-element>
<custom-element class="green border"></custom-element>
Extending on Supersharps answer.
when you can not use Constructable Stylesheets yet:
You could (brutally) import a whole STYLE definition from the Host document.
onload=this.disabled=true to prevent styling the document DOM
or create a <my-themes></my-themes> Component that hosts (and serves) the STYLE elements
customElements.define( 'custom-element', class extends HTMLElement {
constructor() {
super()
this.root=this.attachShadow( { mode: 'open' } );
this.root.innerHTML = `<style>div{font-size:40px}</style>`
+`<style id="theme"></style><div>Click Me</div>`;
let themes = window.themes;//duplicate IDs create a global NodeList
let themeNr = 0;
this.root.addEventListener('click', ev =>
this.theme = themes[ themeNr<themes.length ? themeNr++ : themeNr=0 ].innerHTML);
}
set theme(css){
this.root.getElementById('theme').innerHTML = css;
}
} )
<style id="themes" onload="this.disabled=true">
div{
background:yellow;
}
</style>
<style id="themes" onload="this.disabled=true">
div{
background:hotpink;
font-size:30px;
}
</style>
<style id="themes" onload="this.disabled=true">
div{
background:red;
color:white;
}
div::after{
content:" theme2"
}
</style>
<custom-element></custom-element>
<custom-element></custom-element>
<custom-element></custom-element>
<div>Main Document</div>
CodePen of the nav
On the first interaction with the mobile nav, it will open and close as expected but anything after that and there's a bug. It will begin to open and close instantly or the links will appear is weird orders.
What I need is for the mobile nav to first open from right to left, have each of the links to cascade into the view, starting from About all the way to Blog, and then I would like it to reverse when leaving the view.
Right now I don't have the logic implemented for the reverse but I need to work out this bug before I get to that.
SNIPPET
const bars = document.querySelector('.fa-bars');
bars.addEventListener('click', () => {
const navItemsContainer = document.querySelector('.navbar__links-container');
const navItems = document.querySelectorAll('.navbar__links-container__item');
const sleep = ms => {
return new Promise(resolve => {
setTimeout(() => {
return resolve();
}, ms);
});
};
const startNavAnimation = async () => {
let count = 0;
for (let item of navItems) {
if (item.classList.contains('navbar__links-container__item--show')) {
setTimeout(() => {
item.style.transitionDelay = `${ count }s`
item.classList.remove('navbar__links-container__item--show');
count += .15;
}, count);
}
else {
item.style.transitionDelay = `${ count }s`
item.classList.add('navbar__links-container__item--show');
count += .15;
}
}
};
if (navItemsContainer.classList.contains('navbar__links-container--open')) {
navItems[ navItems.length - 1 ].addEventListener('transitionend', () => {
navItemsContainer.classList.remove('navbar__links-container--open');
});
}
else {
navItemsContainer.classList.add('navbar__links-container--open');
}
startNavAnimation();
});
body {
margin: 0;
}
.navbar {
background: #f2f2f2;
display: flex;
flex-wrap: wrap;
}
.navbar__mobile-container {
display: flex;
justify-content: space-around;
width: 100%;
}
.fa-bars {
cursor: pointer;
}
.navbar__links-container {
background: inherit;
display: flex;
flex-direction: column;
align-items: flex-end;
list-style: none;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
top: 20px;
left: 100%;
transition: left .25s, width .25s;
width: 0%;
}
.navbar__links-container__item {
left: 52px;
margin-bottom: 1rem;
position: relative;
transition: left .25s;
width: auto;
}
.navbar__links-container--open {
left: 0;
width: 100%;
}
.navbar__links-container__item--show {
left: -63px;
}
<nav class="navbar">
<div class="navbar__mobile-container">
<div class="navbar__logo-container">
<a class="navbar__logo-container__logo">BB</a>
</div>
<div class="navbar__hamburger-container">
<i class="fas fa-bars">MENU</i>
</div>
</div>
<ul class="navbar__links-container">
<li class="navbar__links-container__item">
<a class="navbar__links-container__item__link">About</a>
</li>
<li class="navbar__links-container__item">
<a class="navbar__links-container__item__link">Portfolio</a>
</li>
<li class="navbar__links-container__item">
<a class="navbar__links-container__item__link">Blog</a>
</li>
</ul>
</nav>
NOTES
I think the problem is the first if statement in the bars event-handler. Something about the way it's waiting for the transitionend event but the startNavAnimation hasn't been called.
There are two issues.
One is that you are adding a new event listener inside of the click event listener. I moved that outside.
The second issue is that the --open class is going to be there while the menu is opening or closing so you need another way to test open or closed status. To make the Codepen clear to understand, I just used an isOpen flag.
https://codepen.io/Jason_B/pen/jzGwQX?editors=0010
I like using classes for this, and your code shows that you do too, so you might want to have a class for open status and a class for visibility.
Currently I have a number of clickable boxes that when I hover over them, they change colour. When I click and hold on a specific box, it changes colour.(by using :active in css.
Is there anyway I can make the colour of the border change permanently until a different box is clicked? E.G the same as the :active property except I don't have to keep the mouse held in?
My Code:
flight-viewer.html
<h3>Flights </h3>
<div>
<ul class= "grid grid-pad">
<a *ngFor="let flight of flights" class="col-1-4">
<li class ="module flight" (click)="selectFlight(flight)">
<h4>{{flight.number}}</h4>
</li>
</a>
</ul>
</div>
<div class="box" *ngIf="flightClicked">
<!--<flight-selected [flight]="selectedFlight"></flight-selected>-->
You have selected flight: {{selectedFlight.number}}<br>
From: {{selectedFlight.origin}}<br>
Leaving at: {{selectedFlight.departure || date }}<br>
Going to: {{selectedFlight.destination}}<br>
Arriving at: {{selectedFlight.arrival || date}}
</div>
flight-viewer.css:
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
ul {
width: 1600px;
overflow-x: scroll;
background: #ccc;
white-space: nowrap;
vertical-align: middle;
}
div
{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
}
li {
display: inline-block;
/* if you need ie7 support */
*display: inline;
zoom: 1
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
cursor: pointer;
color: #607d8b;
}
.module:active {
border: 5px solid #73AD21;
}
.box {
text-align: center;
margin-bottom: 0;
margin: auto;
width: 600px;
position:absolute;
top: 180px;
right: 0;
height: 100px;
border: 5px solid #73AD21;
text-align: center;
display: inline-block;
}
flight-viewer-component.ts:
import { Component } from '#angular/core';
import { FlightService } from './flight.service';
import { Flight } from './flight.model'
#Component({
selector: 'flight-viewer',
templateUrl: 'app/flight-viewer.html',
styleUrls: ['app/flight-viewer.css']
})
export class FlightViewerComponent {
name = 'FlightViewerComponent';
errorMessage = "";
stateValid = true;
flights: Flight[];
selectedFlight: Flight;
flightToUpdate: Flight;
flightClicked = false;
constructor(private service: FlightService) {
this.selectedFlight = null;
this.flightToUpdate = null;
this.fetchFlights();
}
flightSelected(selected: Flight) {
console.log("Setting selected flight to: ", selected.number);
this.selectedFlight = selected;
}
flightUpdating(selected: Flight) {
console.log("Setting updateable flight to: ", selected.number);
this.flightToUpdate = selected;
}
updateFlight(flight: Flight) {
let errorMsg = `Could not update flight ${flight.number}`;
this.service
.updateFlight(flight)
.subscribe(() => this.fetchFlights(),
() => this.raiseError(errorMsg));
}
selectFlight(selected: Flight) {
console.log("Just click on this flight ", selected.number, " for display");
this.flightClicked = true;
this.selectedFlight = selected;
}
private fetchFlights() {
this.selectedFlight = null;
this.flightToUpdate = null;
this.service
.fetchFlights()
.subscribe(flights => this.flights = flights,
() => this.raiseError("No flights found!"));
}
private raiseError(text: string): void {
this.stateValid = false;
this.errorMessage = text;
}
}
Thanks!
I'm quite sure that this has already been answered.
Make your DIVs focusable, by adding tabIndex:
<div tabindex="1">
Section 1
</div>
<div tabindex="2">
Section 2
</div>
<div tabindex="3">
Section 3
</div>
Then you can simple use :focus pseudo-class
div:focus {
background-color:red;
}
Demo: http://jsfiddle.net/mwbbcyja/
You can use the [ngClass] directive provided by angular to solve your problem.
<a *ngFor="let flight of flights" class="col-1-4">
<li [ngClass]="{'permanent-border': flight.id === selectedFlight?.id}" class ="module flight" (click)="selectFlight(flight)">
<h4>{{flight.number}}</h4>
</li>
</a>
This will add the css class permantent-border to the <li> element, if the id of the flight matches the id with the selectedFlight (Assuming you have an id proberty specified, or just use another proberty which is unique for the flight)
I'm creating my component library in vue, and I defined my component checkbox, the code is like this:
<template>
<div class="checkboxcont" :class="{'checkboxcont-selected': isSelected}" #click="clickevent">
<span class="j-checkbox">
<input type="checkbox" />
</span>
<slot></slot>
</div>
</template>
<script>
export default {
data() {
return {
isSelected: false
}
},
methods: {
clickevent(event) {
if(this.isSelected) {
this.isSelected = false;
} else {
this.isSelected = true;
}
}
},
}
</script>
Now, I hope that when I click the checkbox to set the data "isSelected" false, I can give the component class "checkboxcont-selected-last", and when I click other checkbox component, the classname "checkboxcont-selected-last" can be removed, how can I listen my click event to finish it? I try to use native JavaScript code to add the classname of the dom, but it seemed to have nothing when I binded the classname of my component with Vue.js:
clickevent(event) {
if(this.isSelected) {
this.isSelected = false;
this.$el.classList.add("checkboxcont-selected-last");
} else {
this.isSelected = true;
}
}
What should I do to solve this problem, please?
Here is my style code using less:
<style lang="less" scoped rel="stylesheet/less">
#import '../../mixin/mixin.less';
.checkboxcont {
display: inline-block;
&:hover {
cursor: pointer;
.j-checkbox {
border-color: #jbluelight;
}
}
}
.j-checkbox {
position: relative;
top: 0;
left: 0;
width: 12px;
height: 12px;
display: inline-block;
border: 1px solid #border;
border-radius: 3px;
line-height: 12px;
vertical-align: -3px;
margin: 0 5px;
z-index: 20;
transition: all .2s linear;
input {
opacity: 0;
position: absolute;
left: 0;
top: 0;
visibility: hidden;
/*display: none;*/
}
}
.checkboxcont-selected {
.j-checkbox {
background: #jbluelight;
border-color: #jbluelight;
&:after {
content: '';
width: 4px;
height: 7px;
border: 2px solid white;
border-top: none;
border-left: none;
position: absolute;
left: 3px;
top: 0;
z-index: 30;
transform: rotate(45deg) scale(1);
}
}
}
</style>
<style lang="less" rel="stylesheet/less">
#import '../../mixin/mixin.less';
.checkboxcont-selected-last .j-checkbox {
border-color: #jbluelight;
}
</style>
My initial thought is that I add the class by using this.$el after I clicked the component, it can be accessed because I dispatched the click event, and I just can't access the other component:
if(this.isSelected) {
this.isSelected = false;
this.$el.classList.add("checkboxcont-selected-last")
} else {
this.isSelected = true;
}
And I remove the class by using native HTML DOM operation when I dispatch the click event because I can not access the other component, so the complete definition of clickevent is that:
clickevent(event) {
let selectedLast = document.querySelector(".checkboxcont-selected-last");
if(selectedLast) {
selectedLast.classList.remove("checkboxcont-selected-last")
}
if(this.isSelected) {
this.isSelected = false;
this.$el.classList.add("checkboxcont-selected-last")
} else {
this.isSelected = true;
}
}
It looks good, but I can not add classname of my component when I use v-bind to bind my component's classname, is it wrong? And Is it unable to use native HTML DOM operation when I bind my component's classname with Vue?
A better way to dynamically add or remove class can be using v-bind:class. There are different ways you can add a dynamic class based on a vue data variable.
I see you are already using it:
<div class="checkboxcont" :class="{'checkboxcont-selected': isSelected}" #click="clickevent">
So here this div will have only one class : checkboxcont if isSelected is false, and two classes : checkboxcont and checkboxcont-selected if isSelected is true.
Edited:
Given that you want to add a class to DOM on another component, I can think of two ways:
Using Web API: You can do following if you know the id of the element you want to add class using Element.className:
var d = document.getElementById("yourElem") d.className += " otherclass"
Vuex way: You can have a centralised state provided by vue or use vuex to manage state, these state variables can be changed across components, and can be used to add/remove class dynamically.
You can have a look at my this answer to understand more about vuex.