How to add a class to an element when another element gets a class in angular? - css

I have a scrollspy directive that adds an ".active" class to a nav item. When the first nav item has the ".active" class I want my header bar to contain a certain class too. Attached is a simplified example, but how can I add ".active" to item 1 by only looking at the classes in item 2. jsfiddle
<div ng-app>
<div ng-controller='ctrl'>
<div id="item1" ng-class="if item2 has class=active then add active class here">Item 1</div>
<div id="item2" ng-class="myVar">Item 2</div>
</div>
//I can't use a scope object I can only look at item 2's classes
<button type="button" ng-click="myVar='active'">Add Class</button>
<button type="button" ng-click="myVar=''">Remove Class</button>

Click here for live demo.
You'll need a directive to interact with the element. I would have the directive watch the element's classes and have it call a function from your controller when the classes change. Then, your controller function can apply the logic specific to your need, which is to set a flag letting another element know how to respond.
angular.module('myApp', [])
.controller('MyCtrl', function($scope) {
$scope.foo = function(classes) {
if (~classes.indexOf('active')) {
$scope.otherItemIsActive = true;
}
};
})
.directive('onClassChange', function() {
return {
scope: {
onClassChange: '='
},
link: function($scope, $element) {
$scope.$watch(function() {
return $element[0].className;
}, function(className) {
$scope.onClassChange(className.split(' '));
});
}
};
})
;

Related

How to call a function inside a child component in `Vue3` created through a `v-for` loop?

I am currently building a form builder with vue3 composition API. The user can add in different types of inputs like text, radio buttons etc into the form before saving the form. The saved form will then render with the appropriate HTML inputs. The user can edit the name of the question, eg Company Name <HTML textInput.
Currently, when the user adds an input type eg,text, the type is saved into an ordered array. I run a v-for through the ordered array and creating a custom component formComponent, passing in the type.
My formComponent renders out a basic text input for the user to edit the name of the question, and a place holder string for where the text input will be displayed. My issue is in trying to save the question text from the parent.
<div v-if="type=='text'">
<input type="text" placeholder="Key in title"/>
<span>Input field here</span>
</div>
I have an exportForm button in the parent file that when pressed should ideally return an ordered array of toString representations of all child components. I have tried playing with $emit but I have issue triggering the $emit on all child components from the parent; if I understand, $emit was designed for a parent component to listen to child events.
I have also tried using $refs in the forLoop. However, when I log the $refs they give me the div elements.
<div v-for="item in formItems" ref="formComponents">
<FormComponent :type="item" />
</div>
The ideal solution would be to define a method toString() inside each of the child components and have a forLoop running through the array of components to call toString() and append it to a string but I am unable to do that.
Any suggestions will be greatly appreciated!
At first:
You don't really need to access the child components, to get their values. You can bind them dynamically on your data. I would prefer this way, since it is more Vue conform way to work with reactive data.
But I have also implemented the other way you wanted to achieve, with accessing the child component's methods getValue().
I would not suggest to use toString() since it can be confused with internal JS toString() function.
In short:
the wrapping <div> is not necessary
the refs should be applied to the <FormComponents> (see Refs inside v-for)
this.$refs.formComponents returns the Array of your components
FormComponent is used here as <form-components> (see DOM Template Parsing Caveats)
The values are two-way bound with Component v-model
Here is the working playground with the both ways of achieving your goal.
Pay attention how the values are automatically changing in the FormItems data array.
const { createApp } = Vue;
const FormComponent = {
props: ['type', 'modelValue'],
emits: ['update:modelValue'],
template: '#form-component',
data() {
return { value: this.modelValue }
},
methods: {
getValue() {
return this.value;
}
}
}
const App = {
components: { FormComponent },
data() {
return {
formItems: [
{ type: 'text', value: null },
{ type: 'checkbox', value: false }
]
}
},
methods: {
getAllValues() {
let components = this.$refs.formComponents;
let values = [];
for(var i = 0; i < components.length; i++) {
values.push(components[i].getValue())
}
console.log(`values: ${values}`);
}
}
}
const app = createApp(App)
app.mount('#app')
#app { line-height: 2; }
[v-cloak] { display: none; }
label { font-weight: bold; }
th, td { padding: 0px 8px 0px 8px; }
<div id="app">
<label>FormItems:</label><br/>
<table border=1>
<thead><tr><th>#</th><th>Item Type:</th><th>Item Value</th></tr></thead>
<tbody><tr v-for="(item, index) in formItems" :key="index">
<td>{{index}}</td><td>{{item.type}}</td><td>{{item.value}}</td>
</tr></tbody>
</table>
<hr/>
<label>FormComponents:</label>
<form-component
v-for="(item, index) in formItems"
:type="item.type" v-model="item.value" :key="index" ref="formComponents">
</form-component>
<button type="button" #click="getAllValues">Get all values</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="form-component">
<div>
<label>type:</label> {{type}},
<label>value:</label> <input :type='type' v-model="value" #input="$emit('update:modelValue', this.type=='checkbox' ? $event.target.checked : $event.target.value)" />
</div>
</script>

Is there a way to make a css property reactive with vuejs #scroll?

I'm making a vuejs component in my project, and i need to create a zoom with scroll, in a div (like googlemaps).
<div #scroll="scroll">
<Plotly :data="info" :layout="layout" :display-mode-bar="false"></Plotly>
</div>
<style>
div {
transform: scale(property1);
}
<\style>
<script>
export default {
methods: {
scroll(event) {
},
},
created() {
window.addEventListener('scroll', this.scroll);
},
destroyed() {
window.removeEventListener('scroll', this.scroll);
}
}
</script>
How can i make a method that make the "property1" reactive? Or there is another way to zoom with scroll only the div?
you can bind a dynamic style object to your div which includes the transform property with a dynamic value (see docs for deeper explanation):
<div #scroll="scroll" :style="{ transform : `scale(${property1})`}">
<Plotly :data="info" :layout="layout" :display-mode-bar="false"></Plotly>
</div>
new Vue({
...
data() {
return {
property1: defaultValue
}
},
methods : {
scroll(e){
// e is the event emitted from the scroll listener
this.property1 = someValue
}
}
})
You can also add some modifiers in the template as shown in the documentation to reduce the method code (here we could be preventing the page from scrolling whenever the user will be scrolling while hovering this specific element):
<div #scroll.self.stop="scroll" :style="{ transform : `scale(${property1})`}">
<Plotly :data="info" :layout="layout" :display-mode-bar="false"></Plotly>
</div>

Remove or add class in Angular

I have a list and the plugin (dragula) I used, adds certain CSS class on certain action. I am using Angular 5. I want to find out the presence of certain class (myClass) and remove that and replace with (yourClass). In jQuery we can do that like this
$( "p" ).removeClass( "myClass" ).addClass( "yourClass" );
How can I achieve this in Angular5. Here the main issue is that myClass is added automatically to the selected li by the plugin. So using a function I cant set the class.
When I tried with renderer2, it is removing the CSS class and adding another class. But it is adding only to the first li. My code is:
let myTag ;
myTag = this.el.nativeElement.querySelector("li");
this.renderer.addClass(myTag, 'gu-mirrorss')
this.renderer.removeClass(myTag, 'dragbox');
<div class="right-height" id ='dest' [dragula]='"second-bag"' [dragulaModel]="questions">
{{ questions.length == 0 ? ' Drag and drop questions here ' : ' '}}
<li #vc data-toggle="tooltip" data-placement="bottom" title= {{question.questionSentence}} class="well dragbox" *ngFor="let question of questions; let i = index" [attr.data-id]="question.questionId" [attr.data-index]="i" (click)="addThisQuestionToArray(question,i, $event)" [class.active]="isQuestionSelected(question)" #myId > {{question.questionId}} {{question.questionSentence}}</li>
</div>
Import ElementRef from angular core and define in constructor then try below code:
Below line of code will give you first occurrence of <p> tag from Component. querySelector gives you first item and querySelectorAll gives you all items from DOM.
import { Component, ElementRef } from "#angular/core";
constructor(private el: ElementRef) {
}
let myTag = this.el.nativeElement.querySelector("p"); // you can select html element by getelementsByClassName also, please use as per your requirement.
Add Class:
if(!myTag.classList.contains('myClass'))
{
myTag.classList.add('myClass');
}
Remove Class:
myTag.classList.remove('myClass');
You can use id in your html
<button #namebutton class="btn btn-primary">login</button>
add viewchild in your.ts
#ViewChild('namebutton', { read: ElementRef, static:false }) namebutton: ElementRef;
create a function that will trigger the event
actionButton() {
this.namebutton.nativeElement.classList.add('class-to-add')
setTimeout(() => {
this.namebutton.nativeElement.classList.remove('class-to-remove')
}, 1000);
}
and call the function.
If your native element is undefined check this answer
Angular: nativeElement is undefined on ViewChild
Here are multiple ways to pass classes
<some-element [ngClass]="'first second'">...</some-element>
<some-element [ngClass]="['first', 'second']">...</some-element>
<some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
<some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
<some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
From Official Docs
If you want to change all li.myClass, you can do like this:
Note the #questions in the container div.
Component.ts
#ViewChild('questions') questions: any;
questions.nativeElement.querySelectorAll('.myClass').forEach(
question => {
question.classList.remove('myClass');
question.classList.add('newClass');
}
)
Component.html
<div #questions class="right-height" id ='dest' [dragula]='"second-bag"' [dragulaModel]="questions">
{{ questions.length == 0 ? ' Drag and drop questions here ' : ' '}}
<li
#vc
data-toggle="tooltip"
data-placement="bottom"
title= {{question.questionSentence}}
class="well dragbox"
*ngFor="let question of questions; let i = index"
[attr.data-id]="question.questionId"
[attr.data-index]="i"
(click)="addThisQuestionToArray(question,i, $event)"
[class.active]="isQuestionSelected(question)"
#myId>
{{question.questionId}} {{question.questionSentence}}
</li>
</div>
The Best and easiest way is : -
If student is null then dissabled else not. Use this in button attribute. If you are using bootstrap theme
[ngClass]="{disabled: (students === null) ? true : false}"
If 'yourFlag' is true, 'classB' is enabled.
This worked in angular10.
class="classA {{yourFlag ? 'classB' : ''}}"

Data binding of states between paper elements

Is it possible to bind a state (attribute) of a paper-checkbox [checked|unchecked] dynamically to an attribute like [readonly|disabled] inside a paper-input element? This is my implementation so far:
<template repeat="{{item in lphasen}}">
<div center horizontal layout>
<paper-checkbox unchecked on-change="{{checkStateChanged}}" id="{{item.index}}"></paper-checkbox>
<div style="margin-left: 24px;" flex>
<h4>{{item.name}}</h4>
</div>
<div class="container"><paper-input disabled floatingLabel id="{{item.index}}" label="LABEL2" value="{{item.percent}}" style="width: 120px;"></paper-input></div>
</div>
</template>
The behavior should be as follow:
When the user uncheck a paper-checkbox, then the paper-input element in the same row should be disabled and/or readonly and vice versa. Is it possible to directly bind multiple elements with double-mustache or do I have to iterate the DOM somehow to manually set the attribute on the paper-input element? If YES, could someone explain how?
Another way to bind the checked state of the paper-checkbox.
<polymer-element name="check-input">
<template>
<style>
#checkbox {
margin-left: 1em;
}
</style>
<div center horizontal layout>
<div><paper-input floatingLabel label="{{xlabel}}" value="{{xvalue}}" disabled="{{!xenable}}" type="number" min="15" max="200"></paper-input></div>
<div><paper-checkbox id="checkbox" label="Enable" checked="{{xenable}}"></paper-checkbox></div>
</div>
</template>
<script>
Polymer('check-input', {
publish:{xenable:true, xvalue:'',xlabel:''}
});
</script>
</polymer-element>
<div>
<check-input xenable="true" xvalue="100" xlabel="Weight.1"></check-input>
<check-input xenable="false" xvalue="185" xlabel="Weight.2"></check-input>
</div>
jsbin demo http://jsbin.com/boxow/
My preferred approach would be to refactor the code to create a Polymer element responsible for one item. That way, all of the item specific behaviour is encapsulated in one place.
Once that is done, there are a couple ways of doing this.
The easiest would be to simply create an on-tap event for the check box that toggles the value of a property and sets the disabled attribute accordingly.
<paper-checkbox unchecked on-tap="{{checkChanged}}"></paper-checkbox>
//Other markup for item name display
<paper-input disabled floatingLabel id="contextRelevantName" style="width:120 px;"></paper-input>
One of the benefits of putting this into it's own polymer element is that you don't have to worry about unique id's anymore. The control id's are obfuscated by the shadowDOM.
For the scripting, you would do something like this:
publish: {
disabled: {
value: true,
reflect: false
}
}
checkChanged: function() {
this.$.disabled= !this.$.disabled;
this.$.contextRelevantName.disabled = this.$.disabled;
}
I haven't tested this, so there might be some tweaks to syntax and what have you, but this should get you most of the way there.
Edit
Based on the example code provided in your comment below, I've modified your code to get it working. The key is to make 1 element that contains an either row, not multiple elements that contain only parts of the whole. so, the code below has been stripped down a little bit to only include the check box and the input it is supposed to disable. You can easily add more to the element for other parts of your item displayed.
<polymer-element name="aw-leistungsphase" layout vertical attributes="label checked defVal contractedVal">
<template>
<div center horizontal layout>
<div>
<paper-checkbox checked on-tap="{{checkChanged}}" id="checkbox" label="{{label}}"></paper-checkbox>
</div>
<div class="container"><paper-input floatingLabel id="contractedInput" label="Enter Value" value="" style="width: 120px;"></paper-input></div>
</div>
</template>
<script>
Polymer('aw-leistungsphase', {
publish: {
/**
* The label for this input. It normally appears as grey text inside
* the text input and disappears once the user enters text.
*
* #attribute label
* #type string
* #default ''
*/
label: '',
defVal : 0,
contractedVal : 0
},
ready: function() {
// Binding the project to the data-fields
this.prj = au.app.prj;
// i18n mappings
this.i18ndefLPHLabel = au.xlate.xlate("hb.defLPHLabel");
this.i18ncontractedLPHLabel = au.xlate.xlate("hb.contractedLPHLabel");
},
observe : {
'contractedVal' : 'changedLPH'
},
changedLPH: function(oldVal, newVal) {
if (oldVal !== newVal) {
//this.prj.hb.honlbl = newVal;
console.log("GeƤnderter Wert: " + newVal);
}
},
checkChanged: function(e, detail, sender) {
console.log(sender.label + " " + sender.checked);
if (!this.$.checkbox.checked) {
this.$.contractedInput.disabled = 'disabled';
}
else {
this.$.contractedInput.disabled = '';
}
console.log("Input field disabled: " + this.$.contractedInput.disabled);
}
});
</script>
</polymer-element>

change the entire page background when I click on a button

so here is my question:
lets say I have a page with 3 buttons. each contains a unique pattern as as background. I want to change the entire page background image once I click/ hover on one of the buttons.
what I need is something similar to http://subtlepatterns.com/
I dont need a stop preview option, as long as the background image change again when I select a different button.
how can I do that?
also, if its not possible, this will also work for me:
change the color of a DIV (instead of the entire page background) whenever I click/ hover on one of the buttons.
have 3 different body class in ur CSS sheet:
body.class1 {
background: ...;
}
body.class2 {
background: ...;
}
body.class3 {
background: ...;
}
use jQuery to dynamic change body class
$("#btn1").click(function() {
$('body').removeClass();
$('body').addClass('class1');
});
$("#btn2").click(function() {
$('body').removeClass();
$('body').addClass('class2');
});
$("#btn3").click(function() {
$('body').removeClass();
$('body').addClass('class3');
});
then finally put a id in each button to jQuery find this in DOM:
<a id="btn1">bg1</a>
<a id="btn2">bg2</a>
<a id="btn3">bg3</a>
Using just javascript you could do something like this
function changeBg(color) {
var color = '#' + color;
document.body.style.background = color;
}
http://jsfiddle.net/62SXu/
You can also change this to pass it the path to whatever your image is
does have to be done with CSS? it seems alot easier method to do with jQuery. something like this would work:
<style>
.button1 {background:url(url to PIC);}
</style>
$(document).ready(function (){
$(".onClick").click(function (){
var ID = $(this).attr("id");
$(body).removeClass();
$(body).addClass(ID);
})
})
<div class = "onClick" id="button1"> ... </div>
<div class = "onClick" id="button2"> ... </div>
<div class = "onClick" id="button3"> ... </div>

Resources