I'm creating two modals on a single page and I'm having problem on how can I make it work. I just copied the example here and I converted it into a function like so:
function isDialogOpen() {
return {
modal: false,
open() { this.modal = true;document.body.classList.add("modal-open"); },
close() { this.modal = false;document.body.classList.remove("modal-open"); },
isOpen() { return this.modal === true },
}
}
These two modals is wrapped inside <main> element.
<main class="assignedquestion" x-data="isDialogOpen()" #keydown.escape="close">
I've tried to do it something like this:
<main class="assignedquestion" x-data="...isDialogOpen(), ...reassignDialog()" #keydown.escape="close">
And create another function with different variables and functions.
function reassignDialog() {
return {
reassignmodal: false,
openReassign() { this.reassignmodal = true;document.body.classList.add("modal-open"); },
closeReassign() { this.reassignmodal= false;document.body.classList.remove("modal-open"); },
isOpenReassign() { return this.reassignmodal=== true },
}
}
Unfortunately it doesn't work. Only the first modal is showing up.
These modal will be triggered by 2 different buttons.
Below is the markup of my modals:
First Modal
<!-- overlay -->
<div
class="overflow-auto"
style="background-color: rgba(0, 0, 0, 0.75);display:none"
x-show="isOpen()"
:class="{ 'user-history-modal': isOpen() }"
>
<!-- dialog -->
<div
class="bg-white shadow-2xl"
x-show="isOpen()"
#click.away="close"
>
<div class="modal-header">
<h3>Reassign Ticket</h3>
<button type="button" #click="close"><svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 1L1 13M1 1l12 12" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
</div>
<div class="modal-content">
</div>
</div><!-- /dialog -->
</div><!-- /overlay -->
Second Modal
<!-- overlay reassignDialog -->
<div
class="overflow-auto"
style="background-color: rgba(0, 0, 0, 0.75);display:none"
x-show="isOpenReassign()"
:class="{ 'reassign-history-modal': isOpenReassign() }"
>
<!-- dialog -->
<div
class="bg-white shadow-2xl"
x-show="isOpenReassign()"
#click.away="closeReassign"
>
<div class="modal-header">
<h3>Reassign Ticket</h3>
<button type="button" #click="closeReassign"><svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 1L1 13M1 1l12 12" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
</div>
<div class="modal-content">
</div>
</div><!-- /dialog -->
</div><!-- /overlay -->
The solution here is to make each modal a separate component and to dispatch custom events to them to open/close them. That way, they'll be separate and you'll be able to close them independently. If only one of the modals can be open at a time, you should add
#toggle-reassign-modal.window="close()" to the dialog modal (1st modal) and #toggle-modal.window="close()" to the reassign modal (2nd modal) so that they always close when the other modal is opened.
<div
x-data="isDialogOpen()"
#toggle-modal.window="modal = !modal"
x-init="$watch('modal', (val) => { if (val) document.body.classList.add('modal-open') } )"
>
<!-- contents of modal as per the question -->
</div>
<div
x-data="reassignDialog()"
#toggle-reassign-modal.window="reassignmodal = !reassignmodal"
x-init="$watch('reassignmodal', (val) => { if (val) document.body.classList.add('modal-open') } )"
>
<!-- contents of reassign modal as per the question -->
</div>
To trigger the modals to open you can use the following:
<!-- can remove x-data if these are inside of an Alpine component -->
<button x-data #click="$dispatch('toggle-modal')">Toggle Modal</button>
<button x-data #click="$dispatch('toggle-reassign-modal')">Toggle Reassign Modal</button>
You can see it working as a Codepen.
Modals (among other components) are now part of official AlpineJS documentation. Use them for perfect out of the box modals!
Related
I have a simple question but it's driving me mad to work out.
I'm using AlpineJS to show and hide content.
Normally with alpine, the button and content will be in the same element, but here I have it outside of it. This works.
But I cannot add classes to both the button and content div when the content is open or closed.
Can anyone guide me please.
<!-- button -->
<div class="mt-4" x-data="{id: 1}">
<button
#click="$dispatch('open-dropdown',{id})"
type="button"
class="bg-white hover:bg-gray-50"
:class="{ 'bg-green-400': open, 'bg-gray-200': !(open) }">
I'm the button
</button>
</div>
<!-- popup 1 -->
<div x-data="{ open: false }"
x-show="open"
#open-dropdown.window="if ($event.detail.id == 1) open = true"
#click.away="open = false">
<div
class="bg-white hover:bg-gray-50"
:class="{ 'bg-green-400': open, 'bg-gray-200': !(open) }">
I'm the content
</div>
</div>
For this you can move the open state inside the global Alpine.js' $store object that is accessible for all component on the page.
<!-- button -->
<div class="mt-4" x-data="{id: 1}">
<button #click="$dispatch('open-dropdown',{id})"
type="button"
class="bg-white hover:bg-gray-50"
:class="{ 'bg-green-400': $store.open, 'bg-gray-200': !($store.open) }">
I'm the button
</button>
</div>
<!-- popup 1 -->
<div x-data
x-show="$store.open"
#open-dropdown.window="if ($event.detail.id == 1) $store.open = true"
#click.away="$store.open = false">
<div class="bg-white hover:bg-gray-50"
:class="{ 'bg-green-400': $store.open, 'bg-gray-200': !($store.open) }">
I'm the content
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('open', false)
})
</script>
Since our open state in the store is a simple value, we can just replace every open with $store.open. Note that the popup 1 component still requires an empty x-data attribute.
I'm using Vue.js 2.1.10 and Bootstrap 3.3.7 to show a modal that opens another modal dialog. Each modal dialog is contained in a distinct component. Inside the 1st component, there is a reference to the 2nd component (select-travler).
According to the Bootsrap documentation, I have to set the focus by listening to the event shown.bs.modal. This works great to set the focus on an input control contained in the 1st modal. Problem: this way doesn't work when the modal is above another modal.
The 1st modal component looks like this:
<template>
<div ref="tripEdit" class="modal fade" role="dialog">
<!-- Inbeded component -->
<select-travler ref="selectTravler"></select-travler>
<!-- /Inbeded component -->
<div class="modal-lg modal-dialog">
<div class="modal-content">
<div class="modal-body container form-horizontal">
<div class="form-group">
<label for="travler_name" class="control-label">
Travler's name
</label>
<input id="travler_name" ref="travler_name"
v-model="travler_name"/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
travler_name: null,
}
},
methods: {
show (operationType) {
$(this.$refs.tripEdit).modal('show');
let that = this;
$(this.$refs.tripEdit).on('shown.bs.modal', function () {
$(that.$refs.travler_name).focus();
});
if (operationType === 'newTravel') {
this.$refs.selectTravler.show();
}
},
},
}
</script>
The 2nd component contains a similar layout with the following show method:
show () {
$(this.$refs.selectTravler).modal('show');
let that = this;
$(this.$refs.selectTravler).on('shown.bs.modal', function () {
$(that.$refs.people_names).focus();
});
},
When the 2nd modal opens, the focus is still on the 1st modal behind the 2nd modal dialog (I can see the caret blinking in travler_name). How can I set the focus on people_names when the 2nd modal is shown?
I think there are really several issues at play here. First, as I mentioned in the comment above, you are not properly adding and removing the shown.bs.modal event handlers.
Second, because your second modal is nested inside the first modal, the shown.bs.modal event will bubble up to the parent modal and it's handler will fire. Initially I thought stopPropagation would be a good way to handle this, but in the end, I simply de-nested the submodal component in the template.
Here is an example of this behavior actually working.
console.clear()
Vue.component("sub-modal", {
template: "#submodal",
methods: {
show() {
$(this.$el).modal("show")
},
onShown(event) {
console.log("submodal onshown")
this.$refs.input.focus()
}
},
mounted() {
$(this.$el).on("shown.bs.modal", this.onShown)
},
beforeDestroy() {
$(this.$el).off("shown.bs.modal", this.onShown)
}
})
Vue.component("modal", {
template: "#modal",
methods: {
show() {
$(this.$refs.modal).modal("show")
},
showSubModal() {
this.$refs.submodal.show()
},
onShown(event) {
console.log("parent")
this.$refs.input.focus()
}
},
mounted() {
$(this.$refs.modal).on("shown.bs.modal", this.onShown)
},
beforeDestroy() {
$(this.$refs.modal).off("shown.bs.modal", this.onShown)
}
})
new Vue({
el: "#app",
})
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<div id="app">
<modal ref="modal"></modal>
<button #click="$refs.modal.show()" class="btn">Show Modal</button>
</div>
<template id="submodal">
<div class="modal fade" tabindex="-1" role="dialog">
<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">Modal title</h4>
</div>
<div class="modal-body">
<input ref="input" type="text" class="form-control">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</template>
<template id="modal">
<div>
<div ref="modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" 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">Modal title</h4>
</div>
<div class="modal-body">
Stuff
<input ref="input" type="text" class="form-control">
</div>
<div class="modal-footer">
<button #click="showSubModal" type="button" class="btn btn-primary">Show Sub Modal</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<sub-modal ref="submodal"></sub-modal>
</div>
</template>
Also, for future readers, I got some useful information about how to construct the template for the modal component used above from this answer. Specifically, unless you manually specify a z-index for the modal, the modal that appears last in HTML will have a higher z-index. The implication being the submodal component needs to come second in the template.
I ran into a similar issue. A b-modal forces focus to stay in the modal. You can disable it by adding a no-enforce-focus attribute.
no-enforce-focus Boolean false Disables the enforce focus routine
which maintains focus inside the modal
https://bootstrap-vue.org/docs/components/modal
This means that the element you're trying to focus is not properly referenced.
Trying to console.log(element); the line before focussing people_names. To see if you're getting the right element.
show () {
$(this.$refs.selectTravler).modal('show');
let element = this.$refs.people_names;
$(this.$refs.selectTravler).on('shown.bs.modal', function () {
$(element).focus();
});
},
Have you considered v-show to open and close your modals ?
I have implemented bootstrap tour on my project. I have tried to load it on a modal window but nothing is displayed. Below is my code :
<script type="text/javascript">
$(document).ready(function () {
// Instance the tour
var tour = new Tour({
steps: [
{
element: "#facility_name_div",
title: "Select Facility",
content: "Change the Displayable Facility. "
}
]});
// Initialize the tour
tour.init();
// Start the tour
tour.start();
});
</script>
And my modal window is below:
<!-- Modal -->
<div class="modal fade" id="myModal" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<form class="query_other_order_no_form" id="query_other_order_no_form">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Select Facility : </h4>
</div>
<div class="modal-body">
<div class="box-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<div id="facility_name_div"></div>
<label>Please Select a Facility : </label>
<select class="form-control select2 c_bpartner_name" id="c_bpartner_name" required="" style="width: 100%;">
<option selected="selected" value="">Please Select One : </option>
</select>
</div>
<!-- /.form-group -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
</div>
<!-- /.box-body -->
</div>
<div class="modal-footer">
<!--<button type="button" id="bpartner_query_btn" class="btn btn-default bpartner_query_btn pull-left">Query</button> <button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>-->
</div>
</form>
</div>
</div>
</div>
Please can you advise on how I can set up the bootstrap tour appear on the modal window?
I suspect you may be running into an issue of your modal not being visible when the step is firing. However I would need more context to make sure. One way you can check is to use one of the really helpful bits of Bootstrap Tour which is the debug mode. This can be added to your tour object like this:
var tour = new Tour({
debug: true,
steps: [
{
element: "#facility_name_div",
title: "Select Facility",
content: "Change the Displayable Facility. "
}
]});
By adding this line, your tour will print out what it's doing in the browser console. The tour will print out that it's skipping a step because the element isn't visible which is super helpful.
I recently ran into this visibility issue and this is a workaround (admittedly not the greatest) that I added to the onNext function of the previous step in my tour. I am totally open to a better workaround for this:
onNext: function(tour){
tour.end();
setTimeout(function(){
tour.restart();
tour.goTo(4); //substitute your step index here
}, 500); //change this to as little as your load time allows
}
Another thing to be mindful of is your z-index of both your modals and the tour which utilizes Bootstrap's popover functions. If the modal is visible and your debug mode shows that the step is currently shown, you may have to adjust the z-indexes of one or the other to make sure that the modal isn't hiding the popover.
A simple solution is to init and start your tour only once the modal is shown...
With your code this would give :
<script type="text/javascript">
$(document).ready(function () {
// Instance the tour
var tour = new Tour({
steps: [
{
element: "#facility_name_div",
title: "Select Facility",
content: "Change the Displayable Facility. "
}
]});
$('#myModal').on('shown.bs.modal', function (e) {
// Initialize the tour
tour.init();
// Start the tour
tour.start();
});
});
</script>
In the bootstrap 3 and Bootstrap Tour 0.11 there is a CSS conflict with fade class (put the opacity on 0 "zero") . Try this CSS:
.tour-tour.fade.in{
opacity: 1;
}
You need to set Popever's index. The tour will be visible when it does.
Example:
.popover[class*=tour-]{
z-index:100503 !important;
}
I have the simple code:
<button class="pswp__button" id="CustomButton" style="background:none;" title="Custom" onclick="CustomClicked()">
<img src="~/CustomImage.jpg"">
</button>
This works as expected on desktop browsers. When I switch to mobile though, I cannot get the event to fire when I tap the button. I've tried attaching every event handler I can think of: tap, click, touch, touchstart, etc, and I can still not get any of the events to fire on mobile.
If I invoke the click event through the console, it works on mobile as well, but I can't seem to get the click event to propagate to that control. I've tried setting its zindex to ridiculous values, and still no luck. Anyone have a clue?
After searching for some solutions online, they all seem to have teh same problem. This one, for instance, doesn't fire on mobile either:
var openPhotoSwipe = function() {
var pswpElement = document.querySelectorAll('.pswp')[0];
// build items array
var items = [
{
src: 'https://farm2.staticflickr.com/1043/5186867718_06b2e9e551_b.jpg',
w: 964,
h: 1024
},
{
src: 'https://farm7.staticflickr.com/6175/6176698785_7dee72237e_b.jpg',
w: 1024,
h: 683
}
];
// define options (if needed)
var options = {
// history & focus options are disabled on CodePen
history: false,
focus: false,
showAnimationDuration: 0,
hideAnimationDuration: 0
};
var gallery = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, items, options);
gallery.init();
};
openPhotoSwipe();
$(document).on('click', '.pswp__button.pswp__button--close' , function(){
alert('d'); // did not fire?
});
document.getElementById('btn').onclick = openPhotoSwipe;
<button id="btn">Open PhotoSwipe</button>
<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
<!-- Background of PhotoSwipe.
It's a separate element, as animating opacity is faster than rgba(). -->
<div class="pswp__bg"></div>
<!-- Slides wrapper with overflow:hidden. -->
<div class="pswp__scroll-wrap">
<!-- Container that holds slides. PhotoSwipe keeps only 3 slides in DOM to save memory. -->
<div class="pswp__container">
<!-- don't modify these 3 pswp__item elements, data is added later on -->
<div class="pswp__item"></div>
<div class="pswp__item"></div>
<div class="pswp__item"></div>
</div>
<!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
<div class="pswp__ui pswp__ui--hidden">
<div class="pswp__top-bar">
<!-- Controls are self-explanatory. Order can be changed. -->
<div class="pswp__counter"></div>
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
<button class="pswp__button pswp__button--share" title="Share"></button>
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
<!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
<!-- element will get class pswp__preloader--active when preloader is running -->
<div class="pswp__preloader">
<div class="pswp__preloader__icn">
<div class="pswp__preloader__cut">
<div class="pswp__preloader__donut"></div>
</div>
</div>
</div>
</div>
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
<div class="pswp__share-tooltip"></div>
</div>
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
</button>
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
</button>
<div class="pswp__caption">
<div class="pswp__caption__center"></div>
</div>
</div>
</div>
</div>
In order to achieve this functionality, you must edit hte photoswipe-ui-default.js file.
Your custom button must have the class pswp__button--myCustomButtonName as well as pswp__button.
<button class="pswp__button pswp__button--myCustomButtonName" id="CustomButton" style="background:none;" title="Custom" onclick="CustomClicked()">
<img src="~/CustomImage.jpg"">
Inside the photoswipe-ui-default.js file, you'll see a list of controls:
var _uiElements = [
{
name: 'caption',
option: 'captionEl',
onInit: function(el) {
_captionContainer = el;
}
},
{
name: 'myCustomButton',
option: 'customOptionName',
onTap:MyCustomButtonFunction
},
You must then add that button name to the options:
var options = {
history: false,
escKey: false,
closeOnScroll: false,
pinchToClose: false,
closeOnVerticalDrag: false,
customOptionName: true
};
Use :before to apply additional styles to the buttons, or modify the default UI JS file, so it registers click on image too.
By default, it registers tap only on the target element (in your case it's image), and does not propagate up in the DOM tree.
Figured out a way that doesn't involve altering the library. It's just a bit unfortunate the way the click handling is written, since you cannot use preventDefault or stopPropagation to cancel the handler.
// Disable hiding controls when tapping action buttons
pswp.framework.bind(pswp.scrollWrap, 'pswpTap', (e) => {
let el = e.target || e.srcElement;
while ((el = el.parentElement) && !el.classList.contains('action'));
if (el) {
pswp.options.tapToToggleControls = false;
setTimeout(() => {
pswp.options.tapToToggleControls = true;
}, 50);
}
});
I have a list of links that each open their own modal. Each of these modals contents display an html file. Here is a sample;
<div id="how-rtm-works" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h1 id="myModalLabel">How Right to Manage works</h1>
</div>
<div class="modal-body" id="utility_body">
<p>One fine body…this is getting replaced with content that comes from passed-in href</p>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<li><a data-target="#how-rtm-works" href="modal-content/how-RTM-works.html" role="button" data-toggle="modal">How Right to Manage works</a></li>
Because there is more than one modal, each has its own ID which is referenced in the data-target.
Can anyone advise me on how I would target these modals from another page, or in my case all pages as these will be linked in the website footer nav.
First page you'll use:
Open Modal
At the second page you'll insert your modal and the follow script:
<script type='text/javascript'>
(function() {
'use strict';
function remoteModal(idModal){
var vm = this;
vm.modal = $(idModal);
if( vm.modal.length == 0 ) {
return false;
}
if( window.location.hash == idModal ){
openModal();
}
var services = {
open: openModal,
close: closeModal
};
return services;
///////////////
// method to open modal
function openModal(){
vm.modal.modal('show');
}
// method to close modal
function closeModal(){
vm.modal.modal('hide');
}
}
Window.prototype.remoteModal = remoteModal;
})();
$(function(){
window.remoteModal('#myModalLabel');
});
</script>
Well your modals had IDs, I think you can trigger them by adding #how-rtm-works to the end of the link
for example if you have a modal called
id="My_Modal"
you can make a link
Link me to the modal
If that is your question I guess this can help you out !