Knockout not disposing of dialog when removing template - jquery-ui

Solution Here: http://jsfiddle.net/lookitstony/24hups0e/6/
Crimson's comment lead me to a solution.
I am having an issue with KO and the Jquery UI dialog. The dialogs are not being destroyed with the template that loaded them.
I was previously storing an instance of the dialog and reusing it over and over without using the binding handler. After reading a few posts about the included binding handler I felt perhaps that was the best way to handle the dialogs. Am I using knockout wrong? Should I stick with the stored reference or does KO have a better way to handle this? If this was an SPA, how would I manage this if I was swapping between pages that may or may not have these dialogs?
You can witness this behaviour by checking out my example here: http://jsfiddle.net/lookitstony/24hups0e/2/
JAVASCRIPT
(function () {
ko.bindingHandlers.dialog = {
init: function (element, valueAccessor, allBindingsAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()) || {};
setTimeout(function () {
options.close = function () {
allBindingsAccessor().dialogVisible(false);
};
$(element).dialog(options);
}, 0);
//handle disposal (not strictly necessary in this scenario)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).dialog("destroy");
});
},
update: function (element, valueAccessor, allBindingsAccessor) {
var shouldBeOpen = ko.utils.unwrapObservable(allBindingsAccessor().dialogVisible),
$el = $(element),
dialog = $el.data("uiDialog") || $el.data("dialog");
//don't call open/close before initilization
if (dialog) {
$el.dialog(shouldBeOpen ? "open" : "close");
}
}
}
})();
$(function () {
var vm = {
open: ko.observable(false),
content: ko.observable('Nothing to see here...'),
templateOne: ko.observable(true),
templateTwo: ko.observable(false),
templateOneHasDialog: ko.observable(true),
showOne: function(){
this.templateTwo(false);
this.templateOne(true);
},
showTwo: function(){
this.templateOne(false);
this.templateTwo(true);
},
diagOpt: {
autoOpen: false,
position: "center",
modal: true,
draggable: true,
width: 'auto'
},
openDialog: function () {
if(this.templateOneHasDialog()){
this.content('Dialog opened!');
this.open(open);
} else {
this.content('No Dialog Available');
}
}
}
ko.applyBindings(vm);
});
HTML
<div id='ContentContainer'>
Experience Multiple Dialogs
<ul>
<li>Click "Open Dialog"</li>
<li>Move the dialog out of the center and notice only 1 dialog</li>
<li>Close Dialog</li>
<li>Now click "One" and "Two" buttons back and forth a few times</li>
<li>Now click "Open Dialog"</li>
<li>Move the dialog and observe the multiple dialogs</li>
</ul>
<button data-bind="click:showOne">One</button>
<button data-bind="click:showTwo">Two</button>
<!-- ko if: templateOne -->
<div data-bind="template:{name:'template-one'}"></div>
<!-- /ko -->
<!-- ko if: templateTwo -->
<div data-bind="template:{name:'template-two'}"></div>
<!-- /ko -->
</div>
<script type="text/html" id="template-one">
<h3>Template #1</h3>
<p data-bind="text:content"></p>
<div><input type= "checkbox" data-bind="checked:templateOneHasDialog" /> Has Dialog </div>
<button data-bind="click:openDialog">Open Dialog</button>
<!-- ko if: templateOneHasDialog -->
<div style="display:none" data-bind="dialog:diagOpt, dialogVisible:open">
The Amazing Dialog!
</div>
<!-- /ko -->
</script>
<script type="text/html" id="template-two">
Template #2
</script>

When using dialog inside template the init method will be called every time when the template is shown and hence multiple dialogs are appeared in your case. To resolve this place the dialog outside the template.
<div style="display:none" data-bind="dialog:diagOpt, dialogVisible:open">
The Amazing Dialog!
</div>
Place this outside the template and now the issue will be resolved.
Updated fiddle: Fiddle
Edit: I went through your code and found that the ko.utils.domNodeDisposal.addDisposeCallback has not been triggered in your case. And hence the dialog has not been destroyed on template change which in returns shows multiple dialog.
But why ko.utils.domNodeDisposal.addDisposeCallback has not called?
The ko.utils.domNodeDisposal.addDisposeCallback will be triggered when the element(rendered using custom binding) in the template is removed from DOM. But in your case, the dialog element is appended to the body instead of template and so it was not triggered
Solution
The jquery ui 1.10.0+ have option to specify where the dialog element has to be appended using appendTo option we can use that to resolve this.
diagOpt: {
autoOpen: false,
position: "center",
modal: true,
draggable: true,
width: 'auto',
appendTo: "#DesiredDivID"
},
<script type="text/html" id="template-one">
<h3>Template #1</h3>
<p data-bind="text:content"></p>
<div><input type= "checkbox" data-bind="checked:templateOneHasDialog" /> Has Dialog </div>
<button data-bind="click:openDialog">Open Dialog</button>
<!-- ko if: templateOneHasDialog -->
<div id="DesiredDivID"></div>
<div id="dlg" data-bind="dialog:diagOpt, dialogVisible:open">
The Amazing Dialog!
</div>
<!-- /ko -->
</script>
Now the dialog element will be appended to the #DesiredDivID and destroyed on template change.
See the updated fiddle: Updated one-April-1

Related

Jquery mobile not rendering after injecting precompiled handlebars

The Environment
Building an app in PhoneGap and using Handlebars for the templating. Jquery-mobile is in the mix to provide widgets and some out of the box themes. The templates are packed with gulp into a build file and rendered onto the page through separate JS files.
The Route
The file structure
/-
--|templates/
----|login.hbs/
--|www/
----|js/
------|index.js
------|render/
--------|LoginView.js
The template before packing, login.hbs:
<div class="app" data-role="page">
<div data-role="header">
<h1>Index Page</h1>
</div>
<div data-role="main" class="ui-content">
</div>
<div data-role="footer">
<h1>Title on Page</h1>
<button id="login-btn" class="ui-btn ui-icon-plus ui-btn-icon-left">Login Page</button>
</div>
</div>
The file that handles the rendering, LoginView.js
var LoginView = function() {
this.initialize = function() {
this.el = $('<div data-role="page"/>');
this.el.on('click', '#index-btn', this.renderIndex);
};
this.render = function() {
this.el.html(LoginView.template());
return this;
};
this.renderIndex = function() {
console.log('button click');
$('body').html(new IndexView().render().el);
}
this.initialize();
}
LoginView.template = App.templates.login;
The place where this file is called index.js
var app = {
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function() {
app.receivedEvent();
},
receivedEvent: function() {
console.log('Received Event!');
$('body').html(new LoginView().render().el);
$('body').trigger('create');
},
initialize: function() {
var self = this;
this.bindEvents();
}
};
The Question
Is very much like this one about Jquery not loading on handlebars inject. But maybe I'm not fully understanding where this method needs to be called. Currently the Jquery works when the rendering is disabled and simple html is present on the page... Once the rendering is wired up again: Blank page.
I've tried several different places so far:
In the this.render part of LoginView
In the place where it is now
tried both 'create' and 'pagecreate'
used the id of the rendered page instead of body
I've also moved the <div data-role="page"> around a bit.

Disable jQuery dialog memory

I'm trying to create dynamic dialog, with one input field and with some texts. Problem is, that dialog remembers value of old input fields and is not updating them. I created JSfiddle example of my problem. If you click on <li> element than dialog will come up. There is one input field, that is changing content of <li> element and some pointless text. Problem comes if, when you change content of input field and save it, from this time is stopped being dynamic and become pure static field. I totally don't understand why. PS Sorry for my bad english
HTML
<div id="dialog" title="text">
<input type="text" id="xxx" value="test">Some text
</div>
<ul>
<li id="menu-item-1">one</li>
<li id="menu-item-2">two</li>
<li id="menu-item-3">three</li>
</ul>
JavaScript
$('li').click(function () {
$('#xxx').attr("value", $(this).text());
$("#dialog").dialog('open').data("opener", this.id);
});
$("#dialog").dialog({
autoOpen: false,
modal: true,
buttons: {
Save: function () {
$('#' + $("#dialog").data("opener")).text($("#xxx").val());
$(this).dialog('close');
},
Storno: function () {
$(this).dialog('close');
}
}
});
jsfiddle
Change the dialog to a <form>, then use its reset() method.
$('li').click(function() {
$('#xxx').attr("value", $(this).text());
$("#dialog").dialog('open').data("opener", this.id);
});
$("#dialog").dialog({
autoOpen: false,
modal: true,
buttons: {
Save: function() {
$('#' + $("#dialog").data("opener")).text($("#xxx").val());
$(this).dialog('close');
this.reset();
},
Storno: function() {
$(this).dialog('close');
this.reset();
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" rel="stylesheet" />
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<form id="dialog" title="text">
<input type="text" id="xxx" value="test">Some text</form>
<ul>
<li id="menu-item-1">one</li>
<li id="menu-item-2">two</li>
<li id="menu-item-3">three</li>
</ul>
I think you can easily achieve what you want to achieve by setting the input value with the jquery val() method on the input.
Here is what i mean
Change this
$('#xxx').attr("value", $(this).text());
to this
$('#xxx').val($(this).text());
And here is a working jsfiddle http://jsfiddle.net/gzx3z6e5/

Append jQuery UI dialog to its parent

I have the following html:
<ul id="sortable">
<li class="ui-state-default">1
<div class="dialog-modal" title="Animal Facts" style="display:none;">
<p>What is the fastest animal on Earth?</p>
</div>
</li>
<li class="ui-state-default">2
<div class="dialog-modal" title="Animal Facts" style="display:none;">
<p>What is the largest animal on Earth?</p>
</div></li>
​
and the following jQuery code:
$( ".dialog-modal" ).dialog({
autoOpen: false,
height: 300,
width: 350,
modal: true
});
$('.ui-state-default').click(function() {
$(this).find("div").dialog( "open" );
});
This does not open the modal dialog on click, what am I missing?
Thanks for the help.
This is a current behavior of the jQuery UI dialog.
When defined the jQuery UI dialog is created and appended to the body.
The workaround solution is to append the dialog to its original parent after the creation like:
$(function () {
$(".dialog-modal").each(function (index) {
var origContainer = $(this).parent();
$(this).dialog({
autoOpen: false,
height: 300,
width: 350,
modal: true,
create: function () {
$(this).closest('div.ui-dialog')
.click(function (e) {
e.stopPropagation();
});
}
}).parent().appendTo(origContainer);
});
$('.ui-state-default').click(function (event) {
$(this).find("div").find(".dialog-modal").dialog("open");
});
});
An important note: look at the create event defined; it's necessary because, the open dialog method executed on the .ui-state-default class elements click is triggered on every click inside the dialog! It's formally correct because now the dialog is contained inside its parent and the click is propagated to the parents with .ui-state-default class. With stopPropagation the problem is solved.
It's a bit hackish, but it works.
Working fiddle: http://jsfiddle.net/IrvinDominin/Avtg9/8/
With the future version of jQuery UI 1.10.0 will be added the option appendTo, to handle this situation without any workaround http://api.jqueryui.com/dialog/#option-appendTo

Issues adding a class to jquery ui dialog

I'm trying to add an additional class to my jQuery dialog with the dialogClass property. Here's the javascript:
$(function(){
$( "#toogleMAmaximized" ).dialog({
title: 'Missions and Achivments',
autoOpen: false,
height: 500,
width: 700,
modal: true,
dialogClass: 'noPadding',
buttons: {
Cancel: function() {
$( this ).dialog( "close" );
}
},
close: function() {
allFields.val( "" ).removeClass( "ui-state-error" );
}
})
$( "#toogleMAminimized" ).click(function() {
$( "#toogleMAmaximized" ).dialog( "open" );
$( "#toogleMAmaximized" ).dialog({dialogClass:'noPadding'});
});
})
<div id="toogleMAminimized" style="" class="noPadding">
<div class="" style="cursor: pointer;position: absolute;right: 0;top: 45px;"><img src ="images/MAminimized.png" alt="missions and achivments"/></div>
</div>
Just in case you need it, my html code
<div id="toogleMAmaximized" >
<div id="missions">
<div id="mission1" missiontitle="A new home!" missionpoint="1" missionicon="images/missions/icon/anewhome-icon.png" missionimage="images/missions/anewhome.png" made="f" class="mission notDone"> </div>
</div>
<div id="achivments">
<div id="achivment1" achivmenttitle="Lucha sin cuartel!" achivmentpoint="10" achivmenticon="images/achivments/icon/1.png" achivmentimage="images/achivments/icon/luchasincuartel-plata-ico.png" made="t" class="achivment done"> </div>
</div>
</div>
As you can see, I've tried to add the class in many ways, I've tried all possible combinations but keep getting the same result: no noPadding class
Your noPadding class is being added successfully to the dialog. I have confirmed this by placing your markup and scripts within a fiddle, and loading jQuery UI 1.8.16 (the version you were testing with). This test is available online at http://jsfiddle.net/QHJKm/3/.
I suspect the confusion here is with the expected effect noPadding is going to have on the dialog itself. It could be that you interpreted its lack of effect as an indication it wasn't added to begin with. As you'll note in my example, I've got with a rather bold style, a red background. This quickly confirms that the class is indeed being added to the dialog.

jquery ui accordion - multiple accordions expand/collapse all - style issues

I'm attempting to create an accordion where I can expand/collapse all sections with a single click. I also need the ability for the user to open and close the sections having 0-n sections open at a time. Using several of the discussions here on stackoverflow and on jquery forums, here's the solution i've come up with:
I've implemented each section as it's own accordion, where each is set to collapsible = true.
<html>
<head>
<title>Accordion Test</title>
<script type="text/javascript" src="../scripts/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="../scripts/jquery-ui-1.8.4.custom.min.js"></script>
<link rel="stylesheet" href="../_templates/css/jquery-ui-1.8.6.custom.css" type="text/css" />
<link rel="stylesheet" href="../_templates/css/jquery.ui.accordion.css" type="text/css" />
</head>
<body>
<a onClick="expandAll()">Expand All</a>
<br>
<a onClick="collapseAll()">Collapse All</a>
<div id="accordion1" class="accord">
<h5>section 1</h5>
<div>
section 1 text
</div>
</div>
<!-- orders section -->
<div id="accordion2" class="accord">
<h5>section 2</h5>
<div>
section 2 text
</div>
</div>
<!-- section 3 -->
<div id="accordion3" class="accord">
<h5>section 3</h5>
<div>
section 3 text
</div>
</div>
<!-- section 4 -->
<div id="accordion4">
<h5>section 4</h5>
<div>
section 4 text
</div>
</div>
</body>
</html>
<script type="text/javascript">
$(function() {
$('#accordion1').accordion({
header: 'h5',
collapsible: true,
autoHeight: false
});
});
$(function() {
$('#accordion2').accordion({
header: 'h5',
collapsible: true,
autoHeight: false,
active: false
});
});
$(function() {
$('#accordion3').accordion({
header: 'h5',
collapsible: true,
autoHeight: false,
active: false
});
});
$(function() {
$('#accordion4').accordion({
header: 'h5',
collapsible: true,
autoHeight: false,
active: false
});
});
</script>
<script type="text/javascript">
$(document).ready(function() {
})
function expandAll() {
alert("calling expandAll");
$("#accordion1, #accordion2, #accordion3, #accordion4")
.filter(":not(:has(.ui-state-active))")
.accordion("activate", 0);
}
function collapseAll() {
alert("calling collapseAll");
$("#accordion1, #accordion2, #accordion3, #accordion4")
.filter(":has(.ui-state-active)")
.accordion("activate", -1);
}
</script>
The problem I'm running into, is when I click the header of an open section, the section is collapsed as expected, but the header still have the "ui-state-focus" class, until I click elsewhere on the page. So what I see in the ui is the header of section just closed has the same background color as my hover effect, until I click elsewhere, and it shifts to the 'default, not focused' color.
In addition, when I use the Collapse All link, all looks great in Firefox. In IE, the last section header has the same hover-focus coloring.
Any suggestions? Do I somehow need to force the accordion to lose focus when it is closed? How would I accomplish that?
After attempting to over-ride my jquery-ui styles on the page, and attempting to hack the accordion javascript to remove the ui-state-focus class, a simple solution came to light.
Because my page is displaying the expected behavior when I click else where on the page, I used blur() to lose focus.
$(document).ready(function() {
// forces lose focus when accordion section closed. IE and FF.
$(".ui-accordion-header").click(function(){
$(this).blur();
});
})
To fix the collapse all issue in IE, I added 1 line to my collapseAll() method.
function collapseAll() {
alert("calling collapseAll");
$("#accordion1, #accordion2, #accordion3, #accordion4")
.filter(":has(.ui-state-active)")
.accordion("activate", -1);
$(".ui-accordion-header").blur();
}
Solution to implement accordion with all open panels. Panels are static and can't be closed.
Do not initialize accordion div with accordion widget!
$("#accordion").addClass("ui-accordion ui-widget ui-helper-reset")
.find('h3')
.addClass("current ui-accordion-header ui-helper-reset ui-state-active ui-corner-top")
.prepend('<span class="ui-icon ui-icon-triangle-1-s"/>')
.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content-active");
this is my answer~ hope its help
for multiple open you can do like this by using existing jquery UI just add in a options beforeActivate:
my code below:
$( "#accordion" ).accordion({
header: "> div > h3",
autoHeight: false,
collapsible: true,
active: false,
beforeActivate: function(event, ui) {
// The accordion believes a panel is being opened
if (ui.newHeader[0]) {
var currHeader = ui.newHeader;
var currContent = currHeader.next('.ui-accordion-content');
// The accordion believes a panel is being closed
} else {
var currHeader = ui.oldHeader;
var currContent = currHeader.next('.ui-accordion-content');
}
// Since we've changed the default behavior, this detects the actual status
var isPanelSelected = currHeader.attr('aria-selected') == 'true';
// Toggle the panel's header
currHeader.toggleClass('ui-corner-all',isPanelSelected).toggleClass('accordion-header-active ui-state-active ui-corner-top',!isPanelSelected).attr('aria-selected',((!isPanelSelected).toString()));
// Toggle the panel's icon
currHeader.children('.ui-icon').toggleClass('ui-icon-triangle-1-e',isPanelSelected).toggleClass('ui-icon-triangle-1-s',!isPanelSelected);
// Toggle the panel's content
currContent.toggleClass('accordion-content-active',!isPanelSelected)
if (isPanelSelected) { currContent.slideUp('fast'); } else { currContent.slideDown('fast'); }
return false; // Cancels the default action
}
});
refer from :jQuery UI accordion that keeps multiple sections open?
and the function collapse and expand
function accordion_expand_all()
{
var sections = $('#accordion').find("h3");
sections.each(function(index, section){
if ($(section).hasClass('ui-state-default') && !$(section).hasClass('accordion-header-active')) {
$(section).click();
}
});
}
function accordion_collapse_all()
{
var sections = $('#accordion').find("h3");
sections.each(function(index, section){
if ($(section).hasClass('ui-state-active')) {
$(section).click();
}
});
}
that's it..
You can try this small, lightweight plugin.
It will have few options available which we can modify as per our requirement.
URL: http://accordion-cd.blogspot.com/

Resources