Angular2 | How to addClass and removeClass specifically (clicked div element to activate the className) - angular2-directives

## Note: ##
Onclick of div I am trying to enable the class name which was clicked.
like eg: $('div[data-index='0.0']').addClass('selected'); in Jquery // addClass only to specified div which has data-index =0.0.
I dont want want it to enable all className on click.
Unique way of enabling specific class name
I want the answer specifically in angular2
## Template: ##
<div class="board">
<div class="matrix selected" data-index="0-0" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(0-0)">
</div>
<div class="matrix selected" data-index="0-1" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(0-1)">
</div>
<div class="matrix selected" data-index="1-0" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(1-0)">
</div>
<div class="matrix selected" data-index="1-1" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(1-1)">
</div>
</div>
## component ##
import { Component, OnInit} from '#angular/core';
#Component({
selector: 'app'
})
export class displayComponent implements OnInit {
isCellSelected :boolean ;
constructor() {
}
ngOnInit() {
}
fireClickEvent(clickedCell) {
const selectedCellIndex = clickedCell;
this.isCellSelected = true; // enabling className for all three
// I need only clicked cell to activate the className Not all.
}
}
Thanks in Advance .!!

#Directive({
selector: '[selectable]'
})
export class MatrixDirective {
#HostBinding('class')
classes = '';
#Input('selectableClass')
toggleClass = 'selected';
#HostListener('click')
fireClickEvent() {
if(this.classes.indexOf(this.toggleClass) > -1) {
this.classes = this.classes.replace(this.toggleClass, '');
} else {
this.classes = `${this.classes} ${this.toggleClass}`;
}
};
}
This directive here will accomplish what you're looking for, a bit of overkill, but will help shift you into the "angular way" of doing things.
To use the above directive, just adjust your elements above with this directive.
<!-- OLD -->
<div class="matrix selected" data-index="0-0" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(0-0)">
</div>
<!-- Adjusted -->
<div class="matrix" data-index="0-0" selectable>
</div>

Related

Why transition does not work in my Vue components?

I am building a Vue app. I have two components that are related to each other. One of them is called loginRegister.vue and the code of that is here:
loginRegister.vue:
<template>
<BaseModal idBtn = "regis" btnOneText="Cancel" :btnTwoText="button2Text['register']" v-show="showModal1" #close="showModal1 = false" #submitForm="registerUser(regLogPara['register'])" :popoverVue = "showToolTip.register">
<!-- the material that must be shown in modal comes here. it is better to use bootstrap "card" classes for compatible design -->
<!-- ################ -->
<!-- transition part -->
<!-- ################ -->
<transition name="fade">
<div v-if="modalAlert==='form'" key="item1">
<h4 class="card-title text-center my-3">Create Your Account</h4>
</div>
<!-- showing success message -->
<div v-else-if="modalAlert==='success'" key="item2">
<p>you succeed</p>
</div>
<!-- showing error message -->
<div v-else key="item3">
<p>there is an error!</p>
</div>
</transition>
</BaseModal>
</template>
<script>
import BaseButton from './BaseButton.vue';
import BaseInput from './BaseInput.vue'
import BaseModal from './BaseModal.vue';
import { onMounted, reactive, ref } from 'vue';
export default {
name: "loginRegister",
components: {
BaseModal,
BaseButton,
BaseInput
},
setup(props) {
const showModal = ref(false);
const showModal1 = ref(false);
const modalAlert = ref("form");
const registerUser = async (checkPara) => {
/* This function is responsible for sending form data to backend and getting the result from backend */
if (checkPara == "reset") {
/* for showing form again if there is a back-end error */
console.log("reset form");
modalAlert.value = "form";
button2Text["login"] = "Submit";
button2Text["register"] = "Submit";
regLogPara["login"] = "login";
regLogPara["register"] = "register";
} else {
/* for submiting form */
modalAlert.value = "error";
button2Text[checkPara] = "Try again";
regLogPara[checkPara] = "reset";
console.log("register function");
}
}
return {
registerUser,
showModal,
showModal1,
validation,
blurInput,
loginValid,
registerValid,
finalCheck,
showToolTip,
store,
modalAlert,
button2Text,
regLogPara
}
}
};
</script>
The other one called BaseModal.vue and the code of that is here:
BaseModal.vue:
<template>
<div class="modal-overlay container-fluid p-0">
<div class="row align-items-center justify-content-center" #click.self="$emit('close')">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<!-- text and other html comes here in the slot -->
<!-- ############## -->
<!-- this is the slot part -->
<!-- ############## -->
<slot></slot>
<div class="card-footer d-flex justify-content-around align-items-center">
<base-button
large
v-if="btnOneText"
kind = "btn-secondary"
:textBtn = "btnOneText"
#click="$emit('close')"
>
</base-button>
<base-button
large
v-if="btnTwoText"
kind = "btn-secondary"
:textBtn = "btnTwoText"
#click="popoverFunc"
#blur="popoverDisable"
:id="idBtn"
>
</base-button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BaseButton from './BaseButton.vue';
import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min.js";
export default {
// $emit('submitForm'),
name: "BaseModal",
props: {
btnOneText: {
type: String
},
btnTwoText: {
type: String
},
popoverVue: {
type: Boolean
},
idBtn: {
type: String
}
},
computed: {
popOverData: function () {
return this.popoverVue;
},
popVar: function() {
return new bootstrap.Popover(document.getElementById(this.idBtn), {
trigger: "manual",
title: "Notice:",
content: "Please fill all fields in the correct way to submit your information!",
customClass: "myPopover",
placement: "top"
})
}
},
data() {
return {
showPopOver: false
}
},
emits: ["close", "submitForm"],
components: {
BaseButton
},
watch: {
popOverData: function (newState, oldState) {
if(newState === true) {
this.showPopOver = true;
this.popoverFinal();
} else {
this.showPopOver = false;
this.popoverFinal();
}
}
},
methods: {
popoverFunc: function() {
this.$emit('submitForm');
if (this.popOverData) {
this.showPopOver = true;
this.popoverFinal();
}
},
popoverDisable: function() {
console.log("popoverDisable");
this.showPopOver = false;
this.popoverFinal();
},
popoverFinal: function() {
console.log(this.showPopOver);
/* this function is responsible for showing and hiding the popover according to "showPopOver" data */
if (this.showPopOver) {
this.popVar.show();
} else {
this.popVar.hide();
}
}
}
};
</script>
<style scoped src="../assets/css/compoStyles/baseModal.css"></style>
There are some codes that are not related to this question and also I tried to simplify the codes and clarify the parts that are related to transition and slot in my components code.
Although the codes may seem long or complicated, the goal that I want to reach is simple. I want to submit a register form in a modal component. Actually the user in my app clicks on register button and then the BaseModal.vue component is shown. In that case the register form (that for simplicity I substitute that with a h4 tag) is the default thing that user could see. After submitting the form according that the process is successful or there is an error, I want to show a message to the user and change the text of button from submit to try again if there is an error. After that when the user clicks try again button the form (h4 tag) must be fade in again. So I tried the v-if/v-else-if/v-else structure of Vue in my loginRegister.vue component. The code that I used in transition part is similar to the code that Vue documentation is used, But the transition does not work correctly. In my local development environment, the h4 tag disappears smoothly and then no message is shown, after clicking try again the h4 tag fade in again. Also in the console I could see this warning:
[Vue warn]: <transition> can only be used on a single element or component. Use <transition-group> for lists.
at <BaseTransition mode=undefined appear=false persisted=false ... >
at <Transition name="fade" >
at <BaseModal idBtn="regis" btnOneText="Cancel" btnTwoText="Try again" ... >
at <LoginRegister>
at <App>
But I don't think that is related to my issue, because I did not use transition on multi elements in my app. So could anyone please help me that what is wrong in my codes?
It seems like you want baseModal.vue to be inside loginRegister.vue and the baseModal to have some kind of transition whenever some state changes? Try changing <transition></transition> into <transition-group></transition-group>

Vue 3 watched property does not update

In the pen i've created, the child component does two things, it watches the props and has a method which emits two values.
The parent handling the emitted values modifies the prop and changes the component. However, the child component never fires its watch event.
See this pen
The logs:
"Emiting values up"
"Form state listener fired"
"Form state has changed"
"move listener fired"
"activeComponent has changed"
"Foo: About to unmount"
"Foo: Unmounted"
I expect between
"Form state listener fired"
"Form state has changed"
a log saying "Foo: formState changed".
How come the watcher is not firing?
I built a couple of test components in my Vue 2 CLI sandbox application, but hopefully it will translate enough to Vue 3 to help you solve your problem. Also, not sure if it is contributing to your problem, but you may want to take a look at this Event Names documentation.
Parent.vue
<template>
<div class="parent">
<h3>Parent.vue</h3>
<hr>
<child :formState="formState" #form-state-event="updateFormState" />
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: {
Child
},
data() {
return {
formState: 'Initial State'
}
},
methods: {
updateFormState(newState) {
console.log('updateFormState');
this.formState = newState;
}
}
}
</script>
Child.vue
<template>
<div class="child">
<h3>Child.vue</h3>
<div class="row">
<div class="col-md-6">
<label>Form State From Parent (Prop):</label>
<span>{{ dataFormState }}</span>
</div>
</div>
<div class="row">
<div class="col-md-6">
<button type="button" class="btn btn-sm btn-secondary" #click="changeAndEmit">Emit new form state</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
formState: {
type: String,
required: true
}
},
data() {
return {
dataFormState: this.formState,
clickCounter: 1
}
},
watch: {
formState(newValue) {
this.dataFormState = newValue;
}
},
methods: {
changeAndEmit() {
console.log('Emitting values up')
this.$emit("form-state-event", "New form state " + this.clickCounter++);
}
},
}
</script>
<style scoped>
label {
font-weight: bold;
margin-right: 0.5rem;
}
</style>

VUE 2 with Bootstrap 4 - Radio button action not working when using data-toggle="buttons"

I have the following code:
<template>
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-outline-dark active">
<input type="radio" name="grp" v-model="channel" value="vh1" checked> VH1
</label>
<label class="btn btn-outline-dark">
<input type="radio" name="grp" v-model="channel" value="mtv"> MTV
</label>
</div>
</template>
<script>
export default {
data() {
return {
channel: 'mtv'
}
},
watch: {
channel: function(newValue) {
console.log('value has been changed to ' + newValue);
}
}
}
</script>
When I click on a radio button, nothing happens. But when I removed the "data-toggle="buttons" attribute for the styling, then it starts working! Can anyone help me find a work around here?
Edit
For those of you who don't know, data-toggle is from bootstrap button plugin, it adds extra styles and functionality to the buttons you are making. Check out the docs here:
https://getbootstrap.com/docs/4.0/components/buttons/#toggle-states
Remove data-toggle and set active class by :class="{ 'active': channel === [value] }" for each input.
Finally got the solution
After long hours of searching, I was able to come up with a way to hack into the buttons.
Step 1
Change the above template to the following (removed v-model from the inputs)
<template>
<div class="btn-group" data-toggle="buttons" v-radio="channel">
<label class="btn btn-outline-dark active">
<input type="radio" name="grp" value="vh1" checked> VH1
</label>
<label class="btn btn-outline-dark">
<input type="radio" name="grp" value="mtv"> MTV
</label>
</div>
</template>
This solution was pretty difficult to find, and the solution was pretty hacky, but it got the job done.
Step 2 (UPDATED 6/18/2018)
Create a directive
In my case, since I was using single file components, I needed to bring it directives in the following way:
radio.js
export default {
inserted: function (el, binding) {
var btns = $(el).find('.btn');
var radioGrpName = $(btns[0]).find('input')[0].name;
$("input[name='" + radioGrpName + "'][value='" + binding.value + "']").closest('.btn').button('toggle');
},
bind: function (el, binding, vnode) {
var btns = $(el).find('.btn');
btns.each(function () {
$(this).on('click', function () {
var v = $(this).find('input').get(0).value;
(function set(obj, str, val) {
str = str.split('.');
while (str.length > 1) {
obj = obj[str.shift()];
}
return obj[str.shift()] = val;
})(vnode.context, binding.expression, v);
})
})
}
}
What this does is it binds a jquery click event to each radio button found in the div containing the v-radio. The second part where I'm doing function set (copied from the answer in reference), it checks to see if there's any dots in the expression, otherwise it sets the value of vnode.context["channel"], which updates the model. The bind hooks up to events before the component is loaded so it can be ready to fire off the internal workings of the radio button.
The inserted function is called after bind, during the physical insertion of the component into the parent node. So when you set an initial state (no event fired), the component will automatically reflect the value set in data.
Step 3
add directives radio to the script
import radio from '../../directives/radio'
<script>
export default {
directives: {
radio: radio
},
data() {
return {
channel: 'mtv'
}
},
//you can use computed property, but you need get and set
watch: {
channel: function(newValue) {
console.log('value has been changed to ' + newValue);
}
}
}
</script>
Using these links as references:
Old custom directive hack using vue 1
Update model from custom directive

template v-if="main", not working with return this.$route.path.indexOf('/') === 0 in computed "main"

works with my other routes like "/dashboard", etc, but is appearing in all routes. I basically want this template to only appear when the url is "/". Have tried it throughout my project and just plum doesn't work. PLease help and thank you!! for any suggestions.
<template v-if="main">
<div id="accordion-nav">
<div class="accordion-panels">
Dashboard <i class="fa fa-caret-right" aria-hidden="true"></i>
</div>
<div class="accordion-panels">
Shifts <i class="fa fa-caret-right" aria-hidden="true"></i>
</div>
<div class="accordion-panels">
Other <i class="fa fa-caret-right" aria-hidden="true"></i>
</div>
</div>
</template>
<script>
export default {
name: 'accordion-nav',
computed: {
main: function () {
return this.$route.path.indexOf('/') === 0
}
}
}
</script>
<style scoped>
</style>
Setup the v-if in the sidebar component itself
'<template>
<div id="sidebar" class="sidebar"
:class="{ isOpen: isOpen }">
<div class="sidebar-opener"
#click="toggle">{{openerText}}</div>
<accordion-nav v-if="main"></accordion-nav>
<accordion></accordion>
</div>
</template>'
<script>
export default {
data () {
return {
}
},
computed:{
main(){
return this.$route.path === '/'
}
}
}
</script>
With this.$route.path you get a string that equals the path of the current route resolved as absolute path in any component of your app.
So you can use this to check whether you are in the root route using:
this.$route.path === '/'
Here is the example fiddle
Use v-if="main" as a method v-if="main()" instead of a computed property
methods: {
main: function () {
return this.$route.path.indexOf('/') === 0
}
Methods do such complex updates much better than computed properties. Computed = lazy

Two way binding not working on bootstrap-select with aurelia

I have managed to create a custom element to use the boostrap-select element. However, I can pass/bind values to it from the main view (parent) but I am unable to get the selection out from the element when I use two-way binding.
My custom element is:
import {inject, customElement, bindable} from 'aurelia-framework';
import * as selectpicker from 'bootstrap-select'
#customElement('select-picker')
export class BootStrapSelectPicker {
#bindable selectableValues = null;
#bindable newValue = null;
#bindable selectedValue = 10;
constructor(){
}
attached(){
$('.selectpicker').selectpicker({
style: 'btn-info',
size: 4
});
$('.selectpicker').on('change', function(){
var selected = $(this).find("option:selected").val();
this.selectedValue = selected;
console.log(this.selectedValue);
});
$('.selectpicker').val(this.selectedValue); <-- the selection here is correct
$('.selectpicker').selectpicker('refresh');
}
}
The corresponding view is:
<template>
<select class="selectpicker">
<option repeat.for="p of selectableValues">${p}</option>
</select>
</template>
My containing view that uses the custom element is:
<template>
<require from="./select-picker"></require>
<ul class="list-group">
<li class="list-group-item" repeat.for="p of messageProperties">
<div if.bind="p.propertyType == 'string'">
<div class="form-group">
<label for="ln">Name: ${p.propertyName}</label>
<input type="text" value.bind="p.propertyValue" class="form-control" id="ln" >
</div>
</div>
<div if.bind="p.propertyType == 'integer'">
<div class="form-group">
<label for="ln">Name: ${p.propertyName}</label>
<input type="text" value.bind="p.selectedValue" class="form-control" id="ln" >
<select-picker selectable-values.bind="p.selectableValues"
selected-value.two-way="p.selectedValue"></select-picker>
</div>
</div>
</li>
</ul>
</template>
I expected p.selectedValue to change once a selection is made with the select control as shown here with the two-way command:
selected-value.two-way="p.selectedValue"
However, p.selectedValue is not changing.
Any ideas why this is not working?
Turns out to be a simple scope issue:
attached(){
$('.selectpicker').selectpicker({
style: 'btn-info',
size: 4
});
$('.selectpicker').on('change', function(){
var selected = $(this).find("option:selected").val();
this.selectedValue = selected; // <-- This here doesn't refer to the VM any more
// if you look at the line above you are wrapping $(this) with jq, this works
// because 'this' is now in the scope of the calling element but
// doesn't refer to the aurelia viewmodel
console.log(this.selectedValue);
});
$('.selectpicker').val(this.selectedValue);
$('.selectpicker').selectpicker('refresh');
}
Simple fix is:
attached(){
var self = this; // <--- Create a ref to the VM
$('.selectpicker').selectpicker({
style: 'btn-info',
size: 4
});
$('.selectpicker').on('change', function(){
var selected = $(this).find("option:selected").val();
// Change this to self
self.selectedValue = selected; // <--- Correct object gets the value now - binding works
console.log(this.selectedValue);
});
$('.selectpicker').val(this.selectedValue);
$('.selectpicker').selectpicker('refresh');
}
I'm not sure how this will actually be handled in ES6/7 - I'm sure I read somewhere about how this will change, but since you are transpiling to ES5 it's definitely something to watch out for
The following code works for me, in case anyone has the same issue:
import {inject, customElement, bindable} from 'aurelia-framework';
import 'bootstrap-select'
#customElement('select-picker')
#inject(Element)
export class BootStrapSelectPicker {
#bindable name: string;
#bindable selectableValues;
#bindable selectedValue;
constructor(private element) {
}
attached() {
var self = this;
var $: any = jQuery;
var $elm = $(self.element).find('select');
if ($elm.length > 0) {
$elm.selectpicker();
$elm.on('change', function () {
self.selectedValue = $(this).find("option:selected").val();
});
this.refreshPicker($elm);
}
}
selectedValueChanged(newValue, oldValue) {
var $: any = jQuery;
var $elm = $(this.element).find('select');
this.refreshPicker($elm);
}
private refreshPicker = ($elm) => {
$elm.val(this.selectedValue);
$elm.selectpicker('refresh');
}
}