I have a php page person.php that includes 2 vueJs components: person-details.vue and phones.vue. Each of these components include the same third component notify-delete. This notify-delete component includes a bootstrap modal dialog to confirm a deletion action of the parent component (person or phone).
I use props to set a message in the modal confirmation dialog and $refs to show it.
Problem:
When this props is set the msg props from the component person and the dialog is shown, the message is correctly set. However when I set from the phones component, the dialog shows the message set by person. as if person is constantly overriding the value of the msg props.
here is a sample of the code:
person.php:
<person-details details="<?= json_encode($person) ?>"></person-details>
<phones details="<?= json_encode($phones) ?>"></phones>
Person-details.vue:
<template>
<notify-delete ref="modalDeletePerson" :entity="'person'" :msg="deleteMsg" #confirmed="deleteMe"></notify-delete>
<button type="button" #click="confirmDelete">Delete this person</button>
</template>
<script>
export default {
data () {
return {
deleteMsg: ''
}
},
methods: {
confirmDelete() {
this.deleteMsg = 'Are you sure you want to delete this person?'
this.$refs.modalDeletePerson.show()
},
deleteMe() {
// delete person
}
}
}
</script>
</template>
Phones.vue:
<template>
<notify-delete ref="modalDeletePhone" :entity="'phone'" :msg="deleteMsg" #confirmed="deleteMe($event)"></notify-delete>
<button type="button" #click="confirmDelete">Delete this phone</button>
</template>
<script>
export default {
data () {
return {
deleteMsg: ''
}
},
methods: {
confirmDelete() {
this.deleteMsg = 'Are you sure you want to delete this phone?'
this.$refs.modalDeletePhone.show()
},
deleteMe() {
// delete phone
}
}
}
</script>
Notify-delete.vue:
<template>
<div id="modalDelete" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
{{msg}}
</div>
<div class="modal-footer">
<button type="submit" data-dismiss="modal" #click="confirm">Delete</button>
<button type="button" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['entity', 'msg'],
methods: {
show() {
$('#modalDelete').modal('show')
},
confirm() {
this.$emit('confirmed')
}
}
}
</script>
Any idea how I can have two distinguish instances of the same component?
The problem is here
show() {
$('#modalDelete').modal('show')
}
You are rendering two modals with the same id, then using jQuery to show them. Specifically, $('#modalDelete') will contain two elements. I expect the modal method just picks the first one and shows it.
Try
<div ref="modal" class="modal fade" tabindex="-1" role="dialog">
and
show() {
$(this.$refs.modal).modal('show')
}
This should give each instance of the Notify-delete.vue component it's own reference.
Related
I have a list of projects which I get from an api. They get forwarded one by one to this component:
<template>
<button id="project-button" #contextmenu="handler($event)" class="btn btn-outline-secondary mt-1 ms-1" data-bs-toggle="collapse" :data-bs-target="`#${project.Name + project.ID}`">{{project.Name}}</button>
<div :id="`${project.Name + project.ID}`" class="collapse">
<button #click="setProjectId" id="add-object-button" class="btn btn-outline-secondary mt-1 ms-1" data-bs-toggle="modal" data-bs-target="#createObject">
<b>Add Data Object</b>
<i id="plus-icon" class="fas fa-plus"></i>
</button>
<DataObjects :dataObjects="dataobjects"/>
</div>
<CreateObjectModal #add-object="addObject" :projectId="projectId"/>
</template>
<script>
import DataObjects from './DataObjects'
import CreateObjectModal from './CreateObjectModal'
export default {
name: 'Project',
data() {
return {
dataobjects: [],
projectId: 0,
}
},
props: {
project: Object,
},
components: {
DataObjects,
CreateObjectModal
},
methods: {
setProjectId() {
console.log(this.project.ID)
this.projectId = this.project.ID
console.log(this.projectId)
},
async addObject(objectName) {
console.log(objectName)
console.log("After adding object: " + this.projectId)
console.log("Event arrived")
},
Inside the data function I have an object which has the attribute projectId. When I click on the Add Data Object button a modal opens where the user can type in the name of the data object. The modal opens through the data-bs-toggle and data-bs-target attributes. Now in my click event for this button I can get the individual id of a project depending on which button I click. But the problem here is that I will need the specific id of a project inside my addObject function. The addObject function gets called when I click 'Ok' in my modal after the user types in a name. The logs show clearly that if I click on a button the different id gets set correctly but when I click 'Ok' from the modal and the AddObject function gets called then the id remains 1 which is the first id of the projects
I dont know if I am complicating things but I tried to send the id to my modal component which didnt work. The idea was to send it back to the addObject function via $emit. I am kinda stuck with this problem and hope someone can help me. Here is the Modal Component:
<template>
<div>
<div class="modal fade" id="createObject" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">New Data Object</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="form-floating mb-3">
<input v-model="objectName" class="form-control" id="input">
<label for="input">Object Name:</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<Button data-bs-dismiss="modal" #click="createObject" :text="'Save'"/>
<p>{{projectId}}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Button from './Button'
export default {
name: 'CreateObjectModal',
data() {
return {
objectName: '',
}
},
props:{
projectId: Number
},
methods: {
createObject() {
this.$emit('add-object', this.objectName)
}
},
components: {
Button
},
emits: ['add-object']
}
</script>
I have an app where I'm using Bootstrap 5. In this app, I have an Offcanvas component. I need to do some work when this component closes. In an attempt to do this, I wanted to hook into the hide.bs.offcanvas event. My Vue item looks like this:
<template>
<div>
...
<div class="offcanvas offcanvas-bottom overlay-offcanvas" tabindex="-1" id="myOffCanvas" ref="myOffCanvas" aria-labelledby="overlayOffCanvasLabel" data-bs-backdrop="false">
<div class="offcanvas-header bg-secondary text-white">
<h6 class="offcanvas-title" id="overlayOffCanvasLabel">Hello</h6>
<button data-bs-dismiss="offcanvas">x</button>
</div>
<div class="offcanvas-body">
Hello, world
</div>
</div>
</div>
</template>
<script>
import { Offcanvas } from 'bootstrap';
export default {
data() {
myOffCanvas: null
},
methods: {
onOffcanvasHidden() {
alert('Good bye!');
}
},
mounted() {
this.myOffCanvas = new Offcanvas(this.$refs.myOffCanvas);
}
}
</script>
How do I call onOffcanvasHidden when the offcanvas elements is closed? In other words, how do I wire-up an event handler to the hide.bs.offcanvas event from Vue.js?
I am trying to create a cancel function that cancels a specific viewing based on its id. I have a viewings model that I retrieve from firebase as an observable, I then input it into a child div. The problem being that when I click cancel, it always cancels the first viewing on the page, not the specific one that I click.
The strange thing is, that I created a new component on the same level as my modal component, Input the data in the exact same way, and that works perfectly. It only seems to be with the modal that It does not work.
<div class="col-md-3 buttons" [hidden]='!hidden' [#sliderDiv]="out">
<button class="button button-primary button-sm button-green text-center" (click)="updateViewingStatus()">Update Time</button>
<button data-toggle="modal" data-target="#cancelModal" class="button button-primary button-sm button-previous text-center">Cancel Viewing</button>
</div >
<viewings-cancel-modal [viewings]="viewings"></viewings-cancel-modal>
<testing-cancel [viewings]="viewings"></testing-cancel>
</div>
this is where I input viewings property into the child components. Both identical.
this is my test logic (using just a button) that works perfectly:
export class TestingCancelComponent implements OnInit {
#Input() viewings: TenantViewingModel;
private viewingsId;
constructor(private _viewings: ViewingsService) { }
ngOnInit() {
this.viewingsId = this.viewings.viewings_id;
console.log(this.viewings.viewings_id)
}
updateViewingStatus() {
this._viewings.updateViewingStatus(this.viewingsId);
}
}
And this is my modal logic (that doesnt work) :
export class PropertyViewingsCancelModalComponent implements OnInit {
#Input() viewings: TenantViewingModel;
private viewingsId;
constructor(private _viewings: ViewingsService) { }
ngOnInit() {
this.viewingsId = this.viewings.viewings_id;
// console.log(this.viewings.viewings_id)
}
updateViewingStatus() {
this._viewings.updateViewingStatus(this.viewingsId);
}
}
Which again are identical. The html for the working button is as follows:
<button (click)="updateViewingStatus()"></button>
and the non-working modal:
<div class="modal" id="cancelModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="text-center modal_title"> Are you sure you want to </div>
<h1 class="text-center" id="exampleModalLongTitle">Cancel Viewing? {{viewingsId}}</h1>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<div class="container">
<img src="/assets/img/site/cancel.svg" alt="" style="height: auto; width: 100%; padding: 25px">
<div class="row">
<div class="col-md-6">
<button data-dismiss="modal" class="button button-primary button-xs button-previous text-center" (click)="updateViewingStatus()">confirm</button>
</div>
<div class="col-md-6 text-right">
<button data-dismiss="modal" (click)="logViewings()" class="button button-primary button-xs button-blue text-center">back</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
So just as a re-cap:
The button test works perfectly, removing the specific viewing that I want to remove or cancel. But with the modal, it only removes the first viewing on the page, even though the logic is identical, and I pass the data through to each child component in the exact same way.
Here some working example let's try this,
Html File,
<!--Pass your id as argument to your method.-->
<button (click)="onCancel(id)" class="button button-primary button-sm button-previous text-center">Cancel Viewing</button>
<!--This button has been hiden element is used to call your method -->
<button type="button" class="hideElement" data-toggle="modal" data-target="#cancelModal" #cancelModal></button>
<viewings-cancel-modal [viewings]="viewings"></viewings-cancel-modal>
Typescript File,
import { ElementRef, ViewChild } from '#angular/core';
#ViewChild('cancelModal') private cancelModal: ElementRef;
public onCancel(id:number) {
//set some global variable has been id value
localstorage.setItem("Id",id); //here set value as local storage.
this.viewings.viewings_id=id;
this.cancelModal.nativeElement.click(); //then here iam doing call that modal
}
Modal typescript File,
export class PropertyViewingsCancelModalComponent implements OnInit {
#Input() viewings: TenantViewingModel;
private viewingsId;
constructor(private _viewings: ViewingsService) { }
ngOnInit() {
//this.viewingsId = this.viewings.viewings_id;
this.viewingsId = localstorage.getItem("Id"); //here iam getting id value in localstorage
console.log("View Id",this.viewingsId) //here you getting your id you can proceed your process in modal button click time.
}
updateViewingStatus() {
this._viewings.updateViewingStatus(this.viewingsId);
}
}
It is a simple logic you have to implement your convenient. I hope it's helpful to solve your problem.
Thanks,
Muthukumar
I have 2 components:
Vue.component('repo-button', {
props:["check_in_id", "repo_id"],
template: '#repo-button',
methods: {
fetchRepo: function() {
url = window.location.href.split("#")[0] + "/check_ins/" + this.check_in_id + "/repositionings/" + this.repo_id + ".json"
cl(url)
cl(this)
var that;
that = this;
$.ajax({
url: url,
success: function(data) {
cl(data)
that.showRepo();
}
})
},
showRepo: function() {
// what do I put here to display the modal
}
},
data: function() {
var that = this;
return {
}
}
});
Vue.component('repo-modal', {
template: "#repo-modal",
data: function() {
return {
status: 'none'
}
}
});
var repositionings = new Vue({
el: "#repo-vue"
});
...and my view consists of a button and a modal. I'd like the button to call fetchRepo on the repo-button component and display the modal (change its status property from none to block.
<script type="text/x-template" id="repo-button">
<div class='socialCircle-item success'>
<i class='fa fa-comment'
#click="fetchRepo"
:data-check_in='check_in_id'
:data-repo='repo_id'>
</i>
</div>
</script>
<script type="text/x-template" id="repo-modal">
<div v-bind:style="{ display: status }" class="modal" id="vue-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-client_id="<%= #client.id %>">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-danger btn-simple" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</script>
<div id="repo-vue">
<div is="repo-modal"></div>
<div is="repo-button" repo_id="<%= ci.repositioning.id %>" check_in_id="<%= ci.id %>"></div>
</div>
Props down, events up
In Vue.js, the parent-child component relationship can be summarized
as props down, events up. The parent passes data down to the child via
props, and the child sends messages to the parent via events.
In particular, if the state of a component needs to be controlled externally (by a parent or sibling), that state should be passed in as a prop from the parent. Events indicate to the parent that the state should be changed.
Your modal's state is controlled by events in itself and in a sibling component. So the state lives in the parent, and is passed to the modal as a prop. Clicking the modal Close button emits a (custom) hidemodal event; clicking the sibling component's comment icon emits a showmodal event. The parent handles those events by setting its showRepoModal data item accordingly.
Vue.component('repo-button', {
template: '#repo-button',
methods: {
showRepo: function() {
this.$emit('showmodal');
}
}
});
Vue.component('repo-modal', {
template: "#repo-modal",
props: ["show"],
computed: {
status() {
return this.show ? 'block' : 'none'
}
},
methods: {
hideRepo() {
this.$emit('hidemodal');
}
}
});
var repositionings = new Vue({
el: "#repo-vue",
data: {
showRepoModal: false
}
});
.socialCircle-item i {
cursor: pointer;
}
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<template id="repo-button">
<div class='socialCircle-item success'>
<i class='fa fa-comment'
#click="showRepo"
>
</i>
</div>
</template>
<template id="repo-modal">
<div v-bind:style="{ display: status }" class="modal" id="vue-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" >
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" #click="hideRepo" class="btn btn-danger btn-simple" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</template>
<div id="repo-vue">
<div is="repo-modal" :show="showRepoModal" #hidemodal="showRepoModal = false"></div>
<div is="repo-button" #showmodal="showRepoModal = true"></div>
</div>
What I'm doing feels wonky to me, and I would love to know if there is a better way to do this: I'm using
<compose view-model.bind="modalModel.viewModel"></compose>
in my app.html. When I need to display a modal, whatever viewmodel is active will fire off an event-aggregator publish event with a modal model which contains three things. Modal title, an array of buttons (strings), and a module path to a view/viewmodel. In app.js, I'm subscribing to the modal event and when one comes through, I set the app's
this.modalModel = modalModel;
which immediately binds and loads the view and viewmodel, then I display the modal (bootstrap modal). This works, but there are a few problems which tells me that there must be a better way to do it:
canDeactivate() and deactivate() are never called on the modal's viewmodel that is loaded, when the app.js's modalModel is reset.
I don't think I can pass any parameters into the activate() method in the modal viewmodel.
It just feels wonky.
Would there happen to be a better way to dynamically load in a view and viewmodel, bound and everything, in such a way that it can be properly deactivated upon closing the modal?
Relevant Code:
<template>
<require from="./app-head"></require>
<require from="./main-nav"></require>
<!-- actual site content removed for this example -->
<div class="modal" id="main-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-ajax"></div>
<div class="modal-header">
<button type="button" class="close" click.delegate="modalButtonClick('Cancel')">×</button>
<h4 class="modal-title">${modalModel.title}</h4>
</div>
<div class="modal-body">
<!-- EMPHASIS HERE: -->
<compose view-model.bind="modalModel.viewModel"></compose>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-default" repeat.for="button of modalModel.buttons"
click.delegate="modalButtonClick(button)">
${button}
</button>
</div>
</div>
</div>
</div>
</template>
export class App {
//...
attached() {
//...
this.listen('show-modal', this.displayModal.bind(this));
this.listen('hide-modal', this.closeModal.bind(this));
this.listen('show-modal-loader', this.showModalLoader.bind(this));
this.listen('hide-modal-loader', this.hideModalLoader.bind(this));
}
displayModal(modalModel) {
this.modalModel = modalModel;
$('#main-modal').modal();
}
closeModal() {
$('#main-modal').modal('hide');
this.modalModel = null;
}
showModalLoader() {
document.querySelector('#main-modal .modal-ajax').style.display = 'block';
}
hideModalLoader() {
document.querySelector('#main-modal .modal-ajax').style.display = 'none';
}
listen(event, fn) {
this.eventAggregatorSubscriptions.push(this.ea.subscribe(event, fn));
}
}
Use the aurelia-dialog plugin.
To install: jspm install aurelia-dialog
To activate: in main.js configure(), call aurelia.use.plugin('aurelia-dialog');
Usage: https://github.com/aurelia/dialog#using-the-plugin