How to add and remove CSS class names in Angular 2? - angular2-template

I get the data in the JSON format and must and I must to handle which item the user clicked on. I'm write this code and it correctly work.
My Example with DOM-usage:
#Component({
selector: 'my-app',
template: `
<div *ngFor="let person of personsList; let i = index">
<span class="toggle-icon" (click)="toggleStatus(person.id)" id="{{person.id}}">{{person.name}}</span>
</div>
`,
styles: ['.active { color: red; }']
})
export class App {
toggleIsActive: boolean = false;
personsList: any;
constructor() {
this.personsList = [
{
"id": "1",
"name": "Alex"
},
{
"id": "2",
"name": "John"
}
]
}
toggleStatus(id){
const span = document.getElementById(`${id}`);
if (span.className.indexOf('active') >= 0) {
span.classList.remove('active');
} else {
span.classList.add('active');
}
}
}
How I can add and remove CSS class names without DOM for a similar case?

I'm just add "personIsActive" field, and use Class binding.
#Component({
selector: 'my-app',
template: `
<div *ngFor="let person of personsList; let i = index">
<span class="toggle-icon" [class.active]="person.personIsActive"
(click)="toggleStatus(person.id)">{{person.name}}</span>
</div>
`,
styles: ['.active { color: red; }']
})
export class App {
toggleIsActive: boolean = false;
personsList: any;
constructor() {
this.personsList = [
{
"id": "1",
"name": "Alex",
"personIsActive": false
},
{
"id": "2",
"name": "John",
"personIsActive": false
}
]
}
toggleStatus(id){
for (let i = 0; i < this.personsList.length; i++) {
if (this.personsList[i].id === id) {
this.personsList[i].personIsActive= !this.personsList[i].personIsActive;
}
}
}
}

Related

Check if the value change in loop Vuejs

I'm making chat app by Vuejs and want to handle if messages in loop belong to new user for styling color/background-color of user's message.
<template v-for="msg in allMsgs">
<li :key=msg.id> //I want to add some class to handle if next message belong to new user.
<span class="chatname">{{msg.user.name}}</span>
{{msg.content}}
</li>
</template>
https://prnt.sc/114ynuq
Thank you so much
You can use a computed property to determine the position of each message according to the sequence, and then, use class-binding as follows:
new Vue({
el:"#app",
data: () => ({
allMsgs: [
{ id:1, user: { name:'B' }, content:'contentB' },
{ id:2, user: { name:'A' }, content:'contentA' },
{ id:3, user: { name:'A' }, content:'contentA' },
{ id:4, user: { name:'B' }, content:'contentB' },
{ id:5, user: { name:'B' }, content:'contentB' },
{ id:6, user: { name:'A' }, content:'contentA' },
{ id:7, user: { name:'A' }, content:'contentA' }
]
}),
computed: {
messages: function() {
let pos = 1, prev = null;
return this.allMsgs.map((msg, index) => {
// if msg is not the first, and it belongs to a new user, opposite pos
if(index !== 0 && msg.user.name !== prev.user.name) pos *= -1;
msg.position = pos;
prev = msg;
return msg;
});
}
}
});
.chatname { font-weight:bold; }
.left { text-align:left; }
.right { text-align:right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="(msg, index) in messages">
<li
:key=msg.id
:class="{ 'right': msg.position === 1, 'left': msg.position === -1 }"
>
<span class="chatname">
{{msg.user.name}}
</span>
{{msg.content}}
</li>
</template>
</div>

vue js ag-grid filter component not working

I am quite new to vue-js and ag-grid, I would like to have a custom filter on my ag-grid so tried using component as filter as shown in vue-js ag-grid example: "https://www.ag-grid.com/javascript-grid-filter-component/" but its not working and giving "componentType is not a constructor" error in console.
Below is my code:
Gird:
<template>
<div class="all-devices" style="width: 100%; height: 425px;">
<ag-grid-vue
style="width: 100%; height: 100%;"
class="ag-theme-balham"
:gridOptions="gridOptions"
#grid-ready="onGridReady"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:rowData="rowData"
:frameworkComponents="frameworkComponents"
></ag-grid-vue>
</div>
</template>
<script>
import { AgGridVue } from "ag-grid-vue";
import PartialMatchFilter from "./PartialMatchFilter";
export default {
name: "AllDevices",
components: {},
data() {
return {
gridOptions: null,
columnDefs: null,
defaultColDef: null,
rowData: null,
frameworkComponents: null
};
},
components: {
AgGridVue
},
beforeMount() {
this.gridOptions = {};
this.columnDefs = [
{
headerName: "Row",
field: "row",
width: 450
},
{
headerName: "Filter Component",
field: "name",
width: 430,
filter: "partialMatchFilter"
}
];
this.rowData = [
{
row: "Row 1",
name: "Michael Phelps"
},
{
row: "Row 2",
name: "Natalie Coughlin"
},
{
row: "Row 3",
name: "Aleksey Nemov"
},
{
row: "Row 4",
name: "Alicia Coutts"
},
{
row: "Row 5",
name: "Missy Franklin"
},
{
row: "Row 6",
name: "Ryan Lochte"
},
{
row: "Row 7",
name: "Allison Schmitt"
},
{
row: "Row 8",
name: "Natalie Coughlin"
},
{
row: "Row 9",
name: "Ian Thorpe"
},
{
row: "Row 10",
name: "Bob Mill"
},
{
row: "Row 11",
name: "Willy Walsh"
},
{
row: "Row 12",
name: "Sarah McCoy"
},
{
row: "Row 13",
name: "Jane Jack"
},
{
row: "Row 14",
name: "Tina Wills"
}
];
this.defaultColDef = { filter: true };
this.frameworkComponents = { partialMatchFilter: PartialMatchFilter };
},
methods: {
onGridReady(params) {
params.api.sizeColumnsToFit();
}
}
};
</script>
<style>
</style>
Filter component:
<template>
<div>
<input style="height: 20px" :ref="'input'" v-model="text" />
</div>
</template>
<script>
export default {
name: "PartialMatchFilter",
data() {
return {
text: "",
valueGetter: null
};
},
methods: {
isFilterActive() {
return this.text !== null && this.text !== undefined && this.text !== "";
},
doesFilterPass(params) {
return (
!this.text ||
this.text
.toLowerCase()
.split(" ")
.every(filterWord => {
return (
this.valueGetter(params.node)
.toString()
.toLowerCase()
.indexOf(filterWord) >= 0
);
})
);
},
getModel() {
return { value: this.text };
},
setModel(model) {
if (model) {
this.text = model.value;
}
},
afterGuiAttached() {
this.$refs.input.focus();
},
componentMethod(message) {
alert(`Alert from PartialMatchFilterComponent ${message}`);
}
},
watch: {
text: function(val, oldVal) {
if (val !== oldVal) {
this.params.filterChangedCallback();
}
}
},
created() {
this.valueGetter = this.params.valueGetter;
}
};
</script>
Am I missing something? Please help! - Thanks
I had the same problem.
First, make this change in your columnDefs and get rid of frameworkComponents. Its just cleaner.
filter: "partialMatchFilter" -> filterFramework: PartialMatchFilter
Then the actual fix.
In your filter component add Vue.extend:
<template>
...
</template>
<script>
import Vue from "vue";
export default Vue.extend({
...
});
The example I followed -> https://github.com/ag-grid/ag-grid-vue-example/tree/master/src/rich-grid-example
Ref for Vue.extend -> https://v2.vuejs.org/v2/api/#Vue-extend

Set focus on input element with Vue

I try to code an inline edit element. I like to have the focus on the input, after click. Here my code:
<span v-show="!name.edit" #click="toggleEdit(this, name)">{{name.val}}</span>
<input type="text"
v-model="name.val"
v-show="name.edit"
v-on:blur=" saveEdit(this, name)"
>
</div>
data: function () {
return {
name: {
val: '',
edit: false
},
}
},
methods: {
...mapMutations([
]),
toggleEdit: function(ev, obj){
obj.edit = !obj.edit;
console.log(obj)
if(obj.edit){
Vue.nextTick(function() {
ev.$$.input.focus();
})
}
},
saveEdit: function(ev, obj){
//save your changes
this.toggleEdit(ev, obj);
}
},
But it's still not working.
Try puting $ref in that specific input and vue.nextTick should be this.$nextTick:
like this:
<input type="text"
ref="inputVal"
v-model="name.val"
v-show="name.edit"
v-on:blur=" saveEdit(this, name)"
>
this.$nextTick(function() {
this.$refs.inputVal.focus();
});
https://codesandbox.io/s/keen-sutherland-euc3c
Usually for dynamic elements i would do this:
<template>
<div>
<template v-for="name in names">
<span :key="name.name" v-show="!name.edit" #click="toggleEdit(this, name)">{{name.val}}</span>
<input
:key="name.name"
type="text"
:ref="name.val"
v-model="name.val"
v-show="name.edit"
v-on:blur=" saveEdit(this, name)"
>
</template>
</div>
</template>
<script>
export default {
name: "App",
components: {},
data: function() {
return {
names: [
{
val: "TEST1",
edit: true
},
{
val: "TEST2",
edit: true
},
{
val: "TEST3",
edit: true
}
]
};
},
methods: {
toggleEdit: function(ev, obj) {
obj.edit = !obj.edit;
console.log(obj);
if (obj.edit) {
this.$nextTick(function() {
this.$refs[`${obj.val}`][0].focus();
});
}
},
saveEdit: function(ev, obj) {
//save your changes
this.toggleEdit(ev, obj);
}
}
};
</script>

Filter on nested (recursive) data ( Vue 2 )

Here is an example of my JSON data :
"data":[
{
"id":01,
"name":"test",
"parent_id":null,
"children":[
{
"id":15,
"name":"subChild",
"parent_id":21,
"children":[
{
"id":148,
"name":"subSubChild",
"parent_id":22,
"children":[
....
]
}
]
}
]
},
I would like to filter this level by level. I have made this method :
computed: {
filteredData: function () {
let filterData = this.filter.toLowerCase()
return _.pickBy(this.data, (value, key) => {
return _.startsWith(value.name.toLowerCase(), filterData)
})
},
This work for only the first "level" and I tried several solutions but none worked for children.
So, I would like to be able to filter by several levels.
If you have an idea!
Thank you
A recursive function could come in handy for this particular purpose.
Try the following approach, and for better view, click on Full page link next to the Run code snippet button down below.
new Vue({
el: '#app',
data() {
return {
filter: '',
maintainStructure: false,
data: [{
"id": 01,
"name": "test",
"parent_id": null,
"children": [{
"id": 15,
"name": "subChild",
"parent_id": 21,
"children": [
{
"id": 148,
"name": "subSubChild",
"parent_id": 22,
"children": []
},
{
"id": 150,
"name": "subSubChild3",
"parent_id": 24,
"children": []
}
]
}]
}]
}
},
methods: {
$_find(items, predicate) {
let matches = [];
for (let item of items) {
if (predicate(item)) {
matches.push(item);
}
else if (item.children.length) {
let subMatches = this.$_find(item.children, predicate);
if (subMatches.length) {
if (this.maintainStructure) {
matches.push({
...item,
children: subMatches
});
}
else {
matches.push(subMatches);
}
}
}
}
return matches;
},
filterBy(item) {
return item.name.toLowerCase().startsWith(this.filter.toLowerCase());
}
},
computed: {
filteredData() {
return this.$_find(this.data, this.filterBy);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<label>Filter by <code>item.name</code>:</label>
<input v-model.trim="filter" placeholder="e.g. subsub" />
</div>
<div>
<label>
<input type="checkbox" v-model="maintainStructure" /> Maintain structure
</label>
</div>
<hr />
<pre>{{filteredData}}</pre>
</div>
Note that I'm prefixing the function with $_ to sort of mark it as private function (as recommended in this Style Guide) since we're not going to invoke it anywhere else.

Vue, Disable all other inputs after radio is clicked

I loop over an array and display a list of radio buttons. I am trying to disable all other radio inputs except for the one selected after one is clicked. All I have been able to manage is to disable all of the radios or the one that is clicked.
I was able to accomplish dynamically adding a class in the v-for loop, so I tried disabling the inputs in the same manner, but everything gets disabled:
:disabled="{'disabled':answer.answerID == isChecked}"
I also tried using a method, but didn't have any luck there either:
/* List data */
"answers":[
{
"answerID": "1",
"answerName": "Blueberries"
},
{
"answerID": "2",
"answerName": "Apples"
},
{
"answerID": "3",
"answerName": "Bananas"
},
{
"answerID": "4",
"answerName": "Pineapple"
},
{
"answerID": "5",
"answerName": "Strawberries"
}
]
/* Component code */
<template>
<input
v-for="(answer, index) in answers"
:key="index"
type="radio"
class="mg-input mg-answer"
ref="mgAnswer"
name="mg-answer"
:value="answer.answerName"
v-model="answerVal"
:disabled="disableAnswer(answers, index)"
:class="{'mg-checked':answer.answerID == isChecked}"
#click="
isChecked = answer.answerID
checkAnswer(answers, index, $event)" />
</template>
<script>
export default {
data: function () {
return {
// Class definer for is checked or not
isChecked: undefined,
// Answer Data Properties
answerVal: '',
checkedAnswerID: '',
checkedAnswerElem: '',
}
},
methods: {
checkAnswer: function (arr, i, event) {
let mgAnswer = this.$refs.mgAnswer
this.checkedAnswerID = arr[i].answerID
this.checkedAnswerElem = mgAnswer[i]
if (mgAnswer[i].answerName !== this.answerVal) {
this.answerVal = ''
}
},
disableAnswer: function (arr, i) {
if (arr[i].answerName !== this.answerVal) {
return true
}
}
}
}
</script>
disableAnswer(answers, index) is evaluated only once upon rendering. Initially, answerVal is null, as no answers have been selected yet, so disableAnswer returns true, causing all radio buttons to be disabled immediately.
A quick fix is to add answerVal as a function argument (i.e., disableAnswer(answers, index, answerVal)), so that the function is re-evaluated when answerVal changes. We have to also modify disableAnswer to ignore null values of answerVal, which would occur at initialization:
disableAnswer(arr, i, answerVal) {
if (!answerVal) {
// not yet set
return;
}
if (arr[i].answerName !== answerVal) {
return true
}
}
new Vue({
el: '#app',
data() {
return {
// Class definer for is checked or not
isChecked: undefined,
// Answer Data Properties
answerVal: '',
checkedAnswerID: '',
checkedAnswerElem: '',
"answers":[
{
"answerID": "1",
"answerName": "Blueberries"
},
{
"answerID": "2",
"answerName": "Apples"
},
{
"answerID": "3",
"answerName": "Bananas"
},
{
"answerID": "4",
"answerName": "Pineapple"
},
{
"answerID": "5",
"answerName": "Strawberries"
}
]
};
},
methods: {
checkAnswer: function (arr, i, event) {
let mgAnswer = this.$refs.mgAnswer
this.checkedAnswerID = arr[i].answerID
this.checkedAnswerElem = mgAnswer[i]
if (mgAnswer[i].answerName !== this.answerVal) {
this.answerVal = ''
}
},
disableAnswer: function (arr, i, answerVal) {
if (!answerVal) return;
if (arr[i].answerName !== answerVal) {
return true
}
}
}
})
input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: 50%;
width: 16px;
height: 16px;
border: 2px solid #999;
transition: 0.2s all linear;
outline: none;
margin-right: 5px;
position: relative;
top: 4px;
}
input[disabled] {
border: 1px solid #ccc;
}
.mg-checked {
border: 6px solid black;
}
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<input
v-for="(answer, index) in answers"
:key="index"
type="radio"
class="mg-input mg-answer"
ref="mgAnswer"
name="mg-answer"
:value="answer.answerName"
v-model="answerVal"
:disabled="disableAnswer(answers, index, answerVal)"
:class="{'mg-checked':answer.answerID == isChecked}"
#click="
isChecked = answer.answerID
checkAnswer(answers, index, $event)" />
{{answerVal}}
</div>
You might also find useful a minor refactoring of your code: demo
You can do something like this,
check if input is clicked or if not clicked in that case make disabled set to false.
<input :disabled="disableAnswer(answer)" />
Js
disableAnswer : function(answer){
if(this.answerVal=="" ||this.answerVal==answer.answerName) {
return false;
} else {
return true;
}
}
Added Snippet:
function callMe(){
var vm = new Vue({
el : '#root',
data : {
answerVal:"",
answers:[
{
"answerID": "1",
"answerName": "Blueberries"
},
{
"answerID": "2",
"answerName": "Apples"
},
{
"answerID": "3",
"answerName": "Bananas"
},
{
"answerID": "4",
"answerName": "Pineapple"
},
{
"answerID": "5",
"answerName": "Strawberries"
}
]
},
methods: {
disableAnswer(item){
if(this.answerVal=="" ||this.answerVal==item) {
return false;
} else {
return true;
}
}
}
})
}
callMe();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.11/dist/vue.js"></script>
<div id='root'>
<input
v-for="(answer, index) in answers"
:key="index"
type="radio"
class="mg-input mg-answer"
ref="mgAnswer"
name="mg-answer"
:value="answer.answerName"
v-model="answerVal"
:disabled="disableAnswer(answer.answerName)"
/>
</div>