Adding directives to simplify ngx-bootstrap modal - ngx-bootstrap

This is a suggestion to simplify the basic usages of ngx-bootstrap modal.
The idea is to use 2 directives:
bsDismissModal
replaces bootstrap data-dismiss="modal" attribute
works with "OK" button too, delaying the modal closing to pre-handle button click event
[bsToggleModal]="modalTemplate"
replaces both bootstrap data-toggle="modal" and data-target="#exampleModal" attributes
takes the ng-template reference as input value
Usage example:
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" [bsToggleModal]="exampleModal">
Launch demo modal
</button>
<!-- Modal -->
<ng-template #exampleModal>
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" bsDismissModal>×</button>
<h5 class="modal-title">Modal title</h5>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" bsDismissModal>Cancel</button>
<button type="button" class="btn btn-primary" (bsDismissModal)="save($event)">Save</button>
</div>
</div>
</div>
</div>
</ng-template>

A simple approach would be to use the BsModalService:
DismissModalDirective
import { Directive, EventEmitter, HostListener,
Inject, Output } from '#angular/core';
import { DOCUMENT } from '#angular/common';
import { BsModalService } from 'ngx-bootstrap/modal';
#Directive({
selector: '[dapDismissModal]'
})
export class DismissModalDirective {
// tslint:disable-next-line:no-output-rename
#Output('dapDismissModal') modalClosed = new EventEmitter<MouseEvent>();
private get modalsCount() {
return this.modalService.getModalsCount();
}
constructor(
private readonly modalService: BsModalService,
#Inject(DOCUMENT) private readonly document: Document
) {}
#HostListener('click', ['$event'])
hideModal(click: MouseEvent) {
this.modalClosed.emit(click);
if (click.defaultPrevented) {
return;
}
this.modalService.hide(this.modalsCount);
// Fix BsModalService
if (this.modalsCount === 0) {
this.document.body.classList.remove('modal-open');
}
}
}
(dismissModal)="handleClick($event)" replaces (click)="handleClick($event)", $event being the click MouseEvent.
Modal closing can be cancelled when preventing the click: event.preventDefault();.
ToggleModalDirective
import { Directive, HostListener, Input, TemplateRef } from '#angular/core';
import { BsModalService, ModalOptions } from 'ngx-bootstrap/modal';
#Directive({
selector: '[bsToggleModal]'
})
export class ToggleModalDirective {
// tslint:disable-next-line:no-input-rename
#Input('bsToggleModal') content: TemplateRef<any>;
#Input() bsModalConfig: ModalOptions;
constructor(private readonly modalService: BsModalService) {}
#HostListener('click')
showModal() {
this.modalService.show(this.content, this.bsModalConfig);
}
}

Related

boostrap vue modal not hide when click ok button with validation

I want to add validation to a modal window, I need a behavior in which when the OK button (form submission) is clicked, validation would take place, and if the result is negative, the window should not close
my modal
<b-modal
size="lg"
id="modalToRepair"
title="Add Problem"
title-class="font-18"
centered
body-class="p-4"
no-close-on-backdrop
no-close-on-esc
#ok="onClickModalRepair"
>
<div class="row">
<div class="col-lg-12">
<div class="form-group row">
<label class="col-4 col-form-label">
Repair Problem
<span class="text-danger">*</span>
</label>
<div class="col-8">
<input
v-model="theProblem"
type="text"
class="form-control"
placeholder="Input problem"
name="theProblem"
:class="{
'is-invalid': typesubmit && $v.theProblem.$error
}"
/>
<div
v-if="typesubmit && $v.theProblem.$error"
class="invalid-feedback"
>
<span v-if="!$v.theProblem.required">Requred field.</span>
</div>
</div>
</div>
</div>
</div>
</b-modal>
and my methods
Vue.js
methods: {
onClickModalRepair() {
this.typesubmit = true;
this.$v.$touch();
if (this.$v.$invalid) {
this.$bvModal.show("modalToRepair"); // not work - modal hide
//code for not hide this modal
return;
}
}
},
validations: {
theProblem: {
required
}
}
is it possible?
The method used in the #ok event, is passed an event, which you can call .preventDefault() on, if you want to prevent the modal from closing.
onClickModalRepair(bvModalEvt) {
this.typesubmit = true;
this.$v.$touch();
if (this.$v.$invalid) {
bvModalEvt.preventDefault();
return;
}
}
You can see an example of this on the docs.

Click Event on Dynamically Generated Button Don't get fired in Vue

I am adding a button dynamically and attaching the click event but it doesn't seem to fire.
I see something similar on link below but its not exactly what I am looking for.
Vue: Bind click event to dynamically inserted content
let importListComponent = new Vue({
el: '#import-list-component',
data: {
files: [],
},
methods: {
// more methods here from 1 to 5
//6. dynamically create Card and Commit Button
showData: function (responseData) {
let self = this;
responseData.forEach((bmaSourceLog) => {
$('#accordionOne').append(`<div class="main-card mb-1 card">
<div class="card-header" id=heading${bmaSourceLog.bmaSourceLogId}>
${bmaSourceLog.fileName}
<div class="btn-actions-pane-right actions-icon-btn">
<input type="button" class="btn btn-outline-primary mr-2" value="Commit" v-on:click="commit(${bmaSourceLog.bmaSourceLogId})" />
<a data-toggle="collapse" data-target="#collapse${ bmaSourceLog.bmaSourceLogId}" aria-expanded="false" aria-controls="collapse${bmaSourceLog.bmaSourceLogId}" class="btn-icon btn-icon-only btn btn-link">
</a>
</div>
</div>
<div id="collapse${ bmaSourceLog.bmaSourceLogId}" class="collapse show" aria-labelledby="heading${bmaSourceLog.bmaSourceLogId}" data-parent="#accordionOne">
<div class="card-body">
<div id="grid${ bmaSourceLog.bmaSourceLogId}" style="margin-bottom:30px"></div>
</div>
</div>
</div>`);
});
},
//7. Commit Staging data
commit: function (responseData) {
snackbar("Data Saved Successfully...", "bg-success");
},
}});
I am adding button Commit as shown in code and want commit: function (responseData) to fire.
I was able to achieve this by pure Vue way. So my requirement was dynamically add content with a button and call a function from the button. I have achieved it like so.
Component Code
const users = [
{
id: 1,
name: 'James',
},
{
id: 2,
name: 'Fatima',
},
{
id: 3,
name: 'Xin',
}]
Vue.component('user-component', {
template: `
<div class="main-card mb-1 card">
<div class="card-header">
Component Header
<div class="btn-actions-pane-right actions-icon-btn">
<input type="button" class="btn btn-outline-primary mr-2" value="Click Me" v-on:click="testme(user.id)" />
</div>
</div>
<div class="card-body">
{{user.name}}
</div>
<div class="card-footer">
{{user.id}}
</div>
</div>
`
,props: {
user: Object
}
,
methods: {
testme: function (id) {
console.log(id);
}
}});
let tc = new Vue({
el: '#test-component',
data: {
users
},});
HTML
<div id="test-component">
<user-component v-for="user in users" v-bind:key="user.id" :user="user" />
</div>

how to change ngx bootstrap backdrop modal when two modals are open

ngx-bootstrap for angular with bootstrap 4 version you see the below code when we open one popup the backdrop is working fine when we open another popup(modal) from the first modal the backdrop opacity is not reflecting on the first popup. The opacity is not changing how to change the opacity(backdrop) of first modal when second modal is open.
import { Component, TemplateRef } from '#angular/core';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
#Component({
selector: 'demo-modal-service-nested',
templateUrl: './service-nested.html'
})
export class DemoModalServiceNestedComponent {
modalRef: BsModalRef;
modalRef2: BsModalRef;
constructor(private modalService: BsModalService) {}
openModal(template: TemplateRef<any>) {
this.modalRef = this.modalService.show(template, { class: 'modal-lg' });
}
openModal2(template: TemplateRef<any>) {
this.modalRef2 = this.modalService.show(template, { class: 'second' });
}
closeFirstModal() {
this.modalRef.hide();
this.modalRef = null;
}
}
<button type="button" class="btn btn-primary" (click)="openModal(template)">Open first modal</button>
<ng-template #template>
<div class="modal-header">
<h4 class="modal-title pull-left">First modal</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
This is a first modal
<button type="button" class="btn btn-primary" (click)="openModal2(templateNested)">Open second modal</button>
</div>
</ng-template>
<ng-template #templateNested>
<div class="modal-header">
<h4 class="modal-title pull-left">Second modal</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef2.hide()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
This is nested modal.<br>
<button *ngIf="modalRef" type="button" class="btn btn-danger" (click)="closeFirstModal()">Close first modal</button>
</div>
</ng-template>
I need to show 2 overlapping modals, the second is smaller than the first and I just can't hide the first. My solution was to apply a background-color to the second one:
openModal2(template: TemplateRef<any>) {
this.modalRef2 = this.modalService.show(template, { class: 'second' });
document.getElementsByClassName('second')[0].parentElement.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
}
document.getElementsByClassName('second')[0].parentElement.style.backgroundColor
= 'rgba(0, 0, 0, 0.4)';
I have found a CSS workround for nested modal backdrop issue.
.modal {
background: rgba(0, 0, 0, .3);
}

Validation not getting triggered when value is changed

In Aurelia project, I have created a bootstrap modal that will allow users to enter email addresses. At first when the pop-up is triggered, it applies the validation fine. See below image. This is how it looks like when the pop-up is opened for the first time.
Once you enter the validate email address and click on add btn, I am resetting the value of this.setEmail to "" an empty string. So that way users can type new email address to add. But the validation rule that shows the message Email is required is no longer getting triggered. See below example:
See the Plunker link here. Once the page is loaded. Click on the + icon next to email input. It will open a bootstrap modal.
Below is the code and can be seen at above link as well:
email.ts
import { customElement, useView, bindable, bindingMode, inject, observable } from 'aurelia-framework';
import { ValidationRules, ValidationControllerFactory, Validator } from 'aurelia-validation';
#inject(ValidationControllerFactory)
#customElement('email')
#useView('./email.html')
export class Email {
#bindable public modalName: string;
#bindable public modalValue: string;
#bindable public emailAddress: string;
public emailAddresses = [];
#observable public setEmail: string;
public errorMessage: string;
emailController = null;
constructor(factory) {
this.setEmail = '';
this.emailController = factory.createForCurrentScope();
ValidationRules.ensure('setEmail')
.displayName('Email')
.required()
.email()
.on(this);
}
public bind() {
this.emailController.validate();
}
private joinEmails() {
this.emailAddress = this.emailAddresses.join(";");
}
private isUniqueEmail = (email: string) => {
return (this.emailAddresses.indexOf(email) === -1)
}
public addEmail() {
if (this.setEmail) {
if(!this.isUniqueEmail(this.setEmail))
{
this.errorMessage = "You must provide unique email address.";
return;
}
this.emailAddresses.push(this.setEmail);
this.joinEmails();
this.setEmail = '';
}
else
{
this.errorMessage = "You must provide an email address."
}
}
public setEmailChanged(newValue, oldValue) {
console.log({oldValue: oldValue, newValue: newValue});
}
public removeEmail(index) {
this.emailAddresses.splice(index, 1);
this.joinEmails();
console.log(this);
}
}
email.html
<template>
<!-- Modal -->
<div class="modal fade" id="${modalName}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Add Email Address</h4>
</div>
<div class="modal-body">
<div class="input-group">
<input type="text" id="setEmail" name="setEmail" class="form-control" value.bind="setEmail & validateOnChangeOrBlur" />
<span class="input-group-btn">
<button class="btn btn-primary"
disabled.bind="emailController.errors.length > 0"
click.delegate="addEmail()">Add
</button>
</span>
</div>
<input type="text" value.bind="emailAddress" hidden />
<span class="text-danger" repeat.for="error of emailController.errors">${error.message}</span>
<span class="text-danger" if.bind="errorMessage">${errorMessage}</span>
<div>
<ul class="list-group" if.bind="emailAddresses.length > 0" style="margin-top: 10px;">
<li class="list-group-item" repeat.for="e of emailAddresses">
${e} <span class="glyphicon glyphicon-remove text-danger pull-right" style="cursor: pointer;" click.delegate="removeEmail($index)"></span>
</li>
</ul>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</template>
In your addEmail() function after the line this.setEmail = ''; call the validate again with this.emailController.validate();
The validate() method returns a Promise so you may want to handle any rejections as you would normally see this section of the validation docs Validation Controller specifically the sub section 'validate & reset'.
I'm guessing you expected this to happen automatically because of the 2-way binding and the validateOnChangeOrBlur binding behavior the reason it didn't is that the JavaScript setting the value doesn't trigger DOM events so you need to manually call or fire a synthetic event.

Show child component when promise data is exists and also render the data in child omponent

I am trying to implement search component for my application, parent component have the search text box and button. When the user provide some value i want to send the data to api and show the result in child component. I am bit confused where to call the api and also how to populate the data in child component. Also, initially my child component should not render in the parent component, when the search get some result then it can render. Please help me how to implement a search functionality in vue js 2.
Parent Component
<template>
<div><h3> Search </h3></div>
<div class="row">
<form role="search">
<div class="form-group col-lg-6 col-md-6">
<input type="text" v-model="searchKey" class="form-control">
</div>
<div class="col-lg-6 col-md-6">
<button type="button" id="btn2" class="btn btn-danger btn-md" v-on:click="getInputValue">Search</button>
</div>
</form>
</div>
<result :searchdataShow='searchData'></result>
</template>
<script>
import resultView from './result'
export default {
components: {
'result': resultView
},
data () {
return {
searchKey: null,
searchData: null
}
},
methods: {
getInputValue: function(e) {
console.log(this.searchKey)
if(this.searchKey && this.searchKey != null) {
this.$http.get('url').then((response) => {
console.log(response.data)
this.searchData = response.data
})
}
}
}
</script>
Search Result component(child component)
<template>
<div>
<div class="row"><h3> Search Results</h3></div>
</div>
</template>
<script>
export default {
props: ['searchdataShow']
}
</script>
Create a boolean variable that keeps track of your ajax request, i usually call it loading, or fetchedData, depending on the context. Before the ajax call, set it to true, after the call, set it to false.
Once you have this variable working, you can then conditionally render the result component with v-if. I like to show a loading icon with the corresponding v-else.
Also your template doesn't seem to have a root element, which is required.
<template>
<div><h3> Search </h3></div>
<div class="row">
<form role="search">
<div class="form-group col-lg-6 col-md-6">
<input type="text" v-model="searchKey" class="form-control">
</div>
<div class="col-lg-6 col-md-6">
<button type="button" id="btn2" class="btn btn-danger btn-md" v-on:click="getInputValue">Search</button>
</div>
</form>
</div>
<result v-if="!loading" :searchdataShow='searchData'></result>
<div v-else>loading!!</div>
</template>
<script>
import resultView from './result'
export default {
components: {
'result': resultView
},
data () {
return {
loading: false,
searchKey: null,
searchData: null
}
},
methods: {
getInputValue: function(e) {
console.log(this.searchKey)
this.loading = true;
if(this.searchKey && this.searchKey != null) {
this.$http.get('url').then((response) => {
console.log(response.data)
this.loading = false;
this.searchData = response.data
})
}
}
}
</script>