Mdb
Mdb

Reputation: 8568

Kendo-Knockout: Window does not close correctly

I am using RPNiemeyer kendo-knockout library. I have a grid. When the user clicks on a row of grid a pop up window is showed. When You close the window and open it again in the same way the application freezes with the closing animation. I tried as much as I can to reproduce this scenario in fiddle. In fiddle when You close the pop up window and click on the row again nothing happens and the browser is reloaded. I strongly believe that something similar is happening in my application.

html:

<div data-viewId="languageList" >
    <div id="languageList" data-bind="with: viewModel">
        <div id="languageListGrid" data-bind="kendoGrid: { data: languageViewModels, columns: [ 
                { 
                    template: '<a href=\'\' data-bind=\'click: function() { onLanguageSelected(&quot;#=Language#&quot;) }\'>#=Language#</a>', 
                    field: 'Language', 
                    title: 'Language',
                    width: 50
                }

                ], 
            scrollable: false, sortable: true, pageable: false }" style="height: 380px">

        </div>
    </div>
</div>

<div data-viewid="languageDetails">
    <div id="languageDetails" data-bind="with: viewModel" class="hidden">
        <form id="languageDetailsForm" action="" style="font-family: Trebuchet MS, Verdana, Helvetica, Sans-Serif;">
        <div data-bind="kendoWindow: {isOpen: isOpen, title:'Language', width: 400, height: 200, modal: true }" >
            test
            <button id="cancelLanguage" class="k-button" data-bind="click: cancelLanguage">Cancel</button>
        </div>
       </form>
    </div>
</div>​

javascript:

$(function () {

    var elementIsBoundNew = function (element) {
        return !!ko.dataFor(element);
    }

    var applyBindings = function (viewModel, elementId) {
        var element = $('div[data-viewId="' + elementId + '"]')[0];
        if (!elementIsBoundNew(element)) {
            var parentViewModel = { viewModel: viewModel };
            ko.applyBindings(parentViewModel, element);
        }
    };

    var FranchiseDetailsViewModel = function () {
        var 
            self = this,
            initialize = function () {
                self.languagesInfoViewModel(new LanguageListViewModel(self));
                applyBindings(self.languagesInfoViewModel, "languageList");
            };

        FranchiseDetailsViewModel.prototype.languagesInfoViewModel = ko.observable();
        initialize();
    };

    var LanguageListViewModel = function (franchise) {
        var 
            self = this,
            initialize = function () {
                var languageViewModel = new LanguageDetailsViewModel(franchise);
                self.languageViewModels.push(languageViewModel);
            };
        LanguageListViewModel.prototype.languageViewModels = ko.observableArray([]);
        LanguageListViewModel.prototype.selectedLanguageViewModel = ko.observable();

        LanguageListViewModel.prototype.onLanguageSelected = function (selectedLanguage) {
  // when you uncomment this line everyting works fine
  //var language = new LanguageDetailsViewModel();  
            self.selectedLanguageViewModel(self.languageViewModels()[0]);

            applyBindings(self.selectedLanguageViewModel, "languageDetails");

            self.selectedLanguageViewModel().openPopUp();
        };
        initialize();
    };

    var LanguageDetailsViewModel = function () {
        var 
            self = this,
            closePopUp = function () {
                self.isOpen(false);
            };

        self.Language = ko.observable("English");

        LanguageDetailsViewModel.prototype.isOpen = ko.observable(false);

        LanguageDetailsViewModel.prototype.openPopUp = function () {
            self.isOpen(true);
        };

        LanguageDetailsViewModel.prototype.cancelLanguage = function () {
            closePopUp();
        };

    };

    var initialize = new FranchiseDetailsViewModel();
});​

The strange thing is that if I add this line of code to my onLanguageSelected method everyting works fine:

var language = new LanguageDetailsViewModel();

Fiddle:

http://jsfiddle.net/bZF9k/26/

Any help with working example will be greatly appreciated. Thanks!

Update per RPNiemeyer`s post:

I have added these lines of code to use the technique from here Kendo-Knockout: Calling a method that changes viewmodel property from a template with data-binding inside a grid, breaks bindings:

 ko.bindingHandlers.preventBinding = {
      init: function() {
          return { controlsDescendantBindings: true };
      }        
    };

    ko.bindingHandlers.kendoGrid.options.dataBound = function(data) {
      var body = this.element.find("tbody")[0];

      if (body) {
         ko.applyBindings(ko.dataFor(body), body);   
      }
    };

This is exactly what is happening in my application. When I open the pop up, close it and than open it again for the second time it is not closing correctly. Please see my updated fiddle:

http://jsfiddle.net/bZF9k/29/

What am I missing ? Thank You once again for your feedback!

Upvotes: 1

Views: 2922

Answers (2)

Cirdec
Cirdec

Reputation: 24156

This is a bug in knockout-kendo.

The destroy method of the window isn't called. This is because knockout-kendo is detecting that a widget needs to be destroyed when it is removed from the dom. However, kendoWindow moves the element to the end of the dom. When knockout.js clears or removes elements during an update, it doesn't remove the element, since the element has been moved.

This can be fixed by modifying knockout-kendo so that is leaves behind a proxy element in place where the element that created the kendoWindow was, and destroying the kendoWindow widget when the proxy element is disposed. The following changes to knockout-kendo 0.7.0 accomplish this:

--- knockout-kendo-0.7.0.js 
+++ knockout-kendo-0.7.0.js
@@ -62,14 +62,19 @@
                   return { controlsDescendantBindings: true };
               }
         };

         //build the core logic for the init function
         binding.setup = function(element, options, context) {
-            var widget, $element = $(element);
+            var widget, $element = $(element), $disposeProxy;

+            // Create proxy in original location to capture when the element would have been disposed
+            if (widgetConfig.destroyByProxy) {
+                $disposeProxy = $('<div style="display: none" />').insertAfter($element);
+            }
+            
             //step 2: setup templates
             self.setupTemplates(widgetConfig.templates, options, element, context);

             //step 3: initialize widget
             widget = self.getWidget(widgetConfig, options, $element);

@@ -78,12 +83,17 @@

             //step 5: set up computed observables to update the widget when observable model values change
             self.watchValues(widget, options, widgetConfig, element);

             //step 6: handle disposal, if there is a destroy method on the widget
             if(widget.destroy) {
+                if ($disposeProxy) {
+                    ko.utils.domNodeDisposal.addDisposeCallback($disposeProxy[0], function() {
+                        widget.destroy();
+                    });
+                }
                 ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
                     widget.destroy();
                 });
             }
         };

@@ -768,13 +778,16 @@
         }
     },
     watch: {
         content: CONTENT,
         title: TITLE,
         isOpen: [OPEN, CLOSE]
-    }
+    },
+    // The dom element that contains the window isn't going to be disposed when the template containing it is rendered again.
+    // This is because the window's dom element is placed at the end of the document structure by kendoWindow
+    destroyByProxy: true
 });

 createBinding({
     name: "kendoChart",
     watch: {
         data: function(value) {

Upvotes: 1

RP Niemeyer
RP Niemeyer

Reputation: 114792

It looks like the window is not properly cleaned up after it is closed. This is normally not a problem (and is desirable), but if the grid is re-rendered, then a new kendoWindow is initialized that does not know that there is a window already out there.

It might be possible to handle this in the knockout-kendo code. The destroy method of the window is already called, so I would need to look into why it is not actually removing the window elements.

A workaround for now is to configure a global handler for when a window is closed like:

  ko.bindingHandlers.kendoWindow.options.close = function() {
      $('.k-window, .k-overlay').remove();
  };

Sample here: http://jsfiddle.net/rniemeyer/dcYRM/

Upvotes: 1

Related Questions