knockout modal window opens only once - javascript

I have bootstrap modal window which opens on button click. Everything works fine except one thing : when i click on outmodal space - modal window become closed but i can open it again on button click. If i close modal on button close - it works well.
Update: when i close modal using 'Esc' keyboard - behaviour is the same as for outmodal click.
HTML
<button data-bind="click: function () { $root.showLogModal(); }" title="Show Logs" class="btn btn-large btn-info">Show Logs</button>
<div data-bind="bootstrapLogModal: logModal" tabindex="-1" role="dialog"></div>
Custom Template
<script id="showLogModal" type="text/html">
<div class="modal-header">
<button type="button" class="close" data-bind="click: close" aria-hidden="true">×</button>
<h3>Logs</h3>
</div>
<div class="modal-body">
<div class="alert alert-info">
Some Content
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn" data-bind="click: close">Close</button>
</div>
JS
function ViewModel() {
var self = this;
self.logModal = {
show: ko.observable(false),
onClose: function () {
},
onAction: function () {
}
};
self.showLogModal = function () {
self.logModal.show(true);
};
}
$(function () {
var viewModel = new ViewModel();
ko.bindingHandlers.bootstrapLogModal = {
init: function (element, valueAccessor, allBindingsAccessor, data, bindingContext) {
var props = valueAccessor(),
vm = bindingContext.createChildContext(data);
ko.utils.extend(vm, props);
vm.close = function () {
vm.show(false);
vm.onClose();
};
vm.action = function () {
vm.onAction();
};
ko.utils.toggleDomNodeCssClass(element, "modal hide fade large", true);
ko.renderTemplate("showLogModal", vm, null, element);
var showHide = ko.computed(function () {
$(element).modal(vm.show() ? 'show' : 'hide');
});
return {
controlsDescendantBindings: true
};
}
};
ko.applyBindings(viewModel);
});
http://jsfiddle.net/tLT6d/ here is Live example.

It's expected behavior, if you want to prevent the modal closing when outside area is click, provide backdrop attribute as 'static'.
Includes a modal-backdrop element. Alternatively, specify static for a backdrop which doesn't close the modal on click.
update
I don't know why you rewrite the bootstrap modal into knockout, but to make you code works without closing the modal when outside area is clicked.
just add this line of code in your binding handler
ko.renderTemplate("showLogModal", vm, null, element);
// adds new code
$(element).modal({ backdrop: 'static', show: false });
var showHide = ko.computed(function () {
$(element).modal(vm.show() ? 'show' : 'hide');
});
update 2
To fix your initial code to perform proper closing, when outside area is clicked, attach hidden event handler and call the related close method.
// adds new code
$(element).on("hidden.bs.modal", function(){
vm.close();
});
var showHide = ko.computed(function () {
$(element).modal(vm.show() ? 'show' : 'hide');
});

Related

jQuery multiple click handling

I have the code below working to handle clicking a link from a list, showing a screen, and then hiding it when the .back button is pressed.
Is there a cleaner way to handle the click function for any x number of list items?
$(".container").on("click", ".dog-btn", function() {
$(".screen1").css("display","flex")
});
$(".container").on("click", "#back", function() {
$(".screen1").css("display","none")
});
$(".container").on("click", ".cat-btn", function() {
$(".screen2").css("display","flex")
});
$(".container").on("click", "#back", function() {
$(".screen2").css("display","none")
});
$(".container").on("click", ".bird-btn", function() {
$(".screen3").css("display","flex")
});
$(".container").on("click", "#back", function() {
$(".screen3").css("display","none")
});
You can use html5 data attributes to pass screen classes.
HTML CODE
<div class="container">
<button class="flex" data-screen="screen1"> DOG</button>
Back
</div>
JavaScript Code
//Flex Handler
$(".container").on("click", ".flex", function() {
$("."+$(this).data("screen")).css("display","flex")
});
//Back Button Handler
$(".container").on("click", ".back", function() {
$("."+$(this).data("screen")).css("display","none")
});
Hope it helps.
You can have benefits of data-* attributes of HTML5 as its answered here.
But additionally you can put your custom styles in JSON format in data attributes too. So that you don't need to specify it on every control. That would just go with one click handler as below
$('button').on('click',function(){
var target=$(this).data('target'),
css = $(this).data('css');
$(target).css(css);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button data-css='{"background":"yellow"}'
data-target="#screen1">
click 1
</button>
<button data-css='{"background":"red"}'
data-target="#screen2">
click 2
</button>
<div id="screen1">
screen1
</div>
<div id="screen2">
screen2
</div>
A mockup without jQuery, using event delegation and ES20xx
document.querySelector(".container").addEventListener("click", handleEvents);
document.querySelector("#back").click();
function handleEvents(evt) {
const origin = evt.target;
const screenMap = {
"dog-btn": "screen1",
"cat-btn": "screen2",
"bird-btn": "screen3",
};
if (origin.id && origin.id === "back") {
const screens = `.${Object.entries(screenMap).map(([key, value]) => value).join(",.")}`;
return Array.from(document.querySelectorAll(screens))
.forEach(screen => screen.style.display = "none");
}
if (Array.from(origin.classList).filter(v => /\-btn$/i.test(v)).length) {
return document.querySelector(`.${screenMap[origin.classList]}`)
.style.display = "flex";
}
}
<div class="container">
<div class="screen1">screen 1 DOG</div>
<div class="screen2">screen 2 CAT</div>
<div class="screen3">screen 3 BIRD</div>
<button id="back">#back</button>
<button class="dog-btn">.dog-btn</button>
<button class="cat-btn">.cat-btn</button>
<button class="bird-btn">.bird-btn</button>
</div>

angular-bootstrap-datetimepicker close calendar when clicked outside

I'm totally new to angular and I try to use angular-bootstrap-datetimepicker in my project. My html code is:
<span class="input-group-btn" ng-class="{open: openedDP}">
<button type="button" class="btn btn-default btn-sm" ng-click="open()">
<i class="glyphicon glyphicon-calendar"></i>
</button>
<ul class="dropdown-menu" role="menu">
<datetimepicker ng-model="abc"
on-set-time="close(new, old)">
</datetimepicker>
</ul>
</span>
<input id="abc" ng-model="abc" class="form-control" date-time-input="DD-MM-YYYY HH:mm:SS" />
I wanted to close a calendar when user clicks anywhere outside it. I almost copy-pasted the code from ui.bootstrap. Original one is inside directive and looks like this:
var documentClickBind = function(event) {
if (scope.isOpen && event.target !== element[0]) {
scope.$apply(function() {
scope.isOpen = false;
});
}
};
scope.$watch('isOpen', function(value) {
if (value) {
scope.$broadcast('datepicker.focus');
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
$document.bind('click', documentClickBind);
} else {
$document.unbind('click', documentClickBind);
}
});
My version (inside controller):
var documentClickBind = function (event) {
if ($scope.openedDP) {
$scope.$apply(function () {
$scope.openedDP = false;
});
}
};
$scope.$watch('openedDP', function (value) {
if (value) {
$timeout(function() {
$document.bind('click', documentClickBind);
}, 0, false);
} else {
$document.unbind('click', documentClickBind);
}
});
I removed "element" variable because I don't have it in my controller and it seems to work, but I don't know why. Maybe it works just by chance? Why clicking inside calendar is different than clicking anywhere else? In addition I'd like to avoid creating multiple functions like this when I have multiple datepickers on a page.
The behavior you want is built-in to the bootstrap dropdown, if you have bootstrap already in your project you might consider making use of its dropdown.
If not, you could create a custom directive on the page that broadcasts a 'page-clicked' event when the user clicks on another part of the page, then each controller can listen for that event on their scope and react accordingly.

ng-show in this sample not working

What I want is to show a DIV when I click a button and hide it after 2s.
<div ng-show="alertMsg.show">{{alertMsg.text}}</div>
After triggering the click event, the DIV shows correctly, but cannot hide. It seems that the value of "alertMsg.show" has been changed to FALSE correctly after 2s, but the DIV still there in the view.
Click Event:
$scope.$apply(function(){
$scope.alertMsg.show = true;
});
$timeout(function () {
$scope.alertMsg.show = false;
}, 2000);
I want to know how to hide the DIV via $timeout
hide show with time limit
live code is here http://jsfiddle.net/dofrk5kj/4/
HTML
<div ng-controller="homeController">
<button ng-click="showAlert()"> Show alert </button>
<div ng-show="alertMsg.show">{{alertMsg.text}}</div>
</div>
controller.js
controller('homeController', function ($scope, $timeout) {
$scope.alertMsg = {
show: false,
text:"Alert message is ::show"
};
$scope.showAlert =function(){
$scope.alertMsg.show = true;
$timeout(function(){
$scope.alertMsg.show = false;
}, 2000);
}
});
but you can use separate flag to hide/show alert
<alert ng-repeat="alert in alerts" type="{{alert.type}}"
close="closeAlert($index)">{{alert.msg}} </alert>

Only open closest modal to button

I have a page which has a random number of items each of which has a button and a modal - as I do not know how many items there will be I am using classes for the button and modal.
When I click any button it loads all the modals whereas I only want it to load the next modal - I have tried to use closest and next but cannot get them to fire.
<div class="social-button-container">
<button type="button" class="social-button">Button</button>
</div>
<div class="socialModal modal fade">
<h2>123</h2>
</div>
<div class="social-button-container">
<button type="button" class="social-button">Button2</button>
</div>
<div class="socialModal modal fade">
<h2>234</h2>
</div>
$(document).ready(function () {
$('.social-button').click(function () {
$('.socialModal').modal();
return false;
});
});
The above code works in that the modals fire but they all do - I tried using:
$(document).ready(function () {
$('.social-button').click(function () {
$('.socialModal').next().modal();
return false;
});
});
but this does nothing at all (none fire)
Also tried:
$(document).ready(function () {
$('.social-button').click(function () {
$(this).next('.socialModal').modal();
return false;
});
});
with the same result - how can I get only the following modal to open on clicking the relevant button?
http://jsfiddle.net/0v0pg7fx/
Your selectors were a bit off, and you probably want to in initialize the modals first.
$('.socialModal').modal({show:false});
$('.social-button').click(function () {
$(this).closest('.social-button-container').next('.socialModal').modal('show');
});
Demo
$(document).ready(function () {
$('.button').click(function () {
$('.Modal').closest().modal();
return false;
});
});

Bootstrap modal - hide one then show another

I've been using jQueryUI for a long time now but have recently made the switch to Bootstrap for aesthetic reasons. I am now struggling with what I would expect to be a simple issue and wondered if it's something that others more familiar with Bootstrap can help me with.
I have a generic function for creating dialogs on-the-fly and there are occasions where I show a dialog with no buttons (when processing something), and then swap it to a dialog that does have buttons (process complete - click OK, for example). I'm not trying to define a set process here so I'm basically saying I want to be able to close one dialog and open another whenever needed. This is where the problem comes in.
With Bootstrap the dialogs animate in and out, and I like that and want to keep it. I don't want to do it when swapping dialogs though. I can do this by removing the class fade from the first dialog when it shows, and from the second dialog before it shows, and that works great. I then add the class to the second dialog so that it will animate out. However, the animation goes wrong when I do this and there's an ugly flash where the background div should fade out gently.
I've put together a jsfiddle to demonstrate the issue. You can click the close button on the first dialog to see what the fade out animation should look like.
Any help would be appreciated before I start digging into the Bootstrap source files.
http://jsfiddle.net/ArchersFiddle/0302gLav/1/
tl;dr
Look at the jsfiddle - click "show dialog 2" - click "ok". I want to get rid of the black flash at the end.
CSS
#import url("//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css");
.modal {
display: none;
}
HTML
<div id="dialog1" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Modal Dialog 1</h4>
</div>
<div class="modal-body">This is the first modal dialog</div>
<div class="modal-footer">
<button type="button" id="dialog-ok" class="btn btn-default">Show dialog 2</button>
<button type="button" id="dialog-close" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="dialog2" class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Modal Dialog 2</h4>
</div>
<div class="modal-body">This is the second modal dialog</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
JavaScript
function showDialog2() {
$("#dialog1").removeClass("fade").modal("hide");
$("#dialog2").modal("show").addClass("fade");
}
$("#dialog1").modal("show");
$("#dialog-ok").on("click", function() {
showDialog2();
});
function showDialog2() {
$("#dialog1").removeClass("fade").modal("hide");
$("#dialog2").addClass("fade").modal("show");
}
you want to be this
UPDATED:
I added a click() handler for your last button with an added test identifier id="test" where the dialog and the background gets faded out with the fadeOut() effect. The important thing is to fade out the element .modal-backdrop which encapsules both the dialog and background:
$("#test").on("click", function () {
$(".modal-backdrop").fadeOut("slow");
});
JsFiddle
Okay, I don't like to answer my own question, but I've got a solution that is 100% foolproof (as far as I can tell). I've ended up going for a solution that checks for an existing dialog and modifies that, rather than hiding it and showing a new one.
Here's a working jsfiddle (using echo in the ajax call where it normally loads a html template)...
http://jsfiddle.net/ArchersFiddle/0302gLav/9/
The code is part of a larger library I'm working on, but I'll post it here anyway as it may well prove useful to others.
JavaScript Library
(function () {
var _defaultOptions = {
backdrop: "static",
close: true,
keyboard: true
};
window.bootstrap = {
modal: {
show: function (options) {
options = $.extend(_defaultOptions, options);
var buttonText = "";
for (var key in options.buttons) {
options.buttons[key].id = "button_" + key.split(" ").join("");
var button = options.buttons[key];
buttonText += "<button type=\"button\" " +
"id=\"" + button.id + "\" " +
"class=\"btn " +
(typeof (button.class) == "undefined" ? "btn-default" : button.class) + "\" " +
(typeof (button.dismiss) == "undefined" ? "" : "data-dismiss=\"modal\" ") + ">" +
key + "</button>";
}
$.ajax({
url: "templates/bootstrap-modal.html"
})
.done(function (data) {
data = data
.replace("{:Title}", options.title)
.replace("{:Body}", options.body)
.replace("{:Buttons}", buttonText);
var $modal = $("#bootstrap-modal");
var existing = $modal.length;
if (existing) {
$modal.html($(data).find(".modal-dialog"));
}
else {
$modal = $(data).appendTo("body");
}
for (var key in options.buttons) {
var button = options.buttons[key];
if (typeof (button.callback) !== undefined) {
$("#" + button.id).on("click", button.callback);
}
}
$modal.off("shown.bs.modal").on("shown.bs.modal", function(e) {
if (typeof(options.onshow) === "function") {
options.onshow(e);
}
});
if (!existing) {
$modal.modal(options);
}
if (options.large === true) {
$modal.find(".modal-dialog").addClass("modal-lg");
}
if (options.close === false) {
$modal.find("button.close").remove();
}
});
},
hide: function (callback) {
var $modal = $("#bootstrap-modal");
if (!$modal.length) return;
$modal.on("hidden.bs.modal", function(e) {
$modal.remove();
if (typeof(callback) === "function") {
callback(e);
}
});
$modal.modal("hide");
}
}
};
})();
Template HTML
<div id="bootstrap-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title">{:Title}</h4>
</div>
<div class="modal-body">{:Body}</div>
<div class="modal-footer">
{:Buttons}
</div>
</div>
</div>
</div>
Example usage:
bootstrap.modal.show({
title: "Dialog Title",
body: "<p>This is the dialog body and can contain any old html rubbish.</p>",
buttons: {
Close: {
dismiss: true
}
}
});
Further options explained
bootstrap.modal.show({
backdrop: true, // show the backdrop
close: true, // show the close button
keyboard: true, // allow the keyboard to close the dialog
title: "Dialog Title",
body: "<p>This is the dialog body and can contain any old html rubbish.</p>",
buttons: {
Button1: {
class: "btn-primary", // any class you want to add to the button
dismiss: false, // does this button close the dialog?
callback: function() { // button click handler
// the button was clicked - do something here
}
},
Button2: {
// options as defined as above. You can have as many buttons as you want
}
},
onshow: function(e) {
// this will trigger when the dialog has completed the show animation
}
});
and
bootstrap.modal.hide(function(e) {
// this will trigger when the dialog has completed the hide animation
});
All the options in the show() method are optional, but obviously you'll want a title and a body.
I've code how to close an opened modal before opening another one.
$('[data-toggle="modal"]').on('click', function() { //On click a button which call a modal
if(($(".modal").data('bs.modal') || {}).isShown){ //If a modal is shown
var modal = $(".modal.in"); // Get the current element
$(modal).modal('hide'); // Hide the current modal
}
});
Hope that helped!
A bit late but might help somebody with the same problem.
Removing the fade class when showing the new modal is showing the backdrop without a fade class either.
For a clean transition, hiding the current modal and showing the new one without fade, but with fade out on closing:
// hide the existing modal, but without fade animation
$("#existing-modal").removeClass("fade").modal("hide");
// show the new modal without fade animation, but enable the fade animation for later
$("#new-modal").removeClass("fade").modal("show").addClass("fade");
// enable fade animation of the backdrop, that was removed for the new modal
$(".modal-backdrop.in").addClass("fade");
(with the most recent Bootstrap versions, use $(".modal-backdrop.show").addClass("fade")),
The example with the fix: http://jsfiddle.net/bvalentino/f82z1wex/2/

Categories