Knockout: Bootstrap model do not closed properly when using with knockout js

Created on 15 Sep 2019  路  7Comments  路  Source: knockout/knockout

I am using knockout with bootstrap js.

My requirement :

  • I am creating an HTML form and I have done data binding using knockout.

  • After submission of this form, I am showing a message to the user in bootstrap modal.

  • On closing this modal I need to reset the HTML form. SO on clicking on the close button I and doing a click binding and calling a method. Below is the snippet of that method.

// Re-setting the observable  to default value
me.addStudentForClass(null);
me.parents=ko.mapping.fromJS(new parentInfoModel(undefined));
me.localGuardian=ko.mapping.fromJS(new localGuardian(undefined));
me.lastSchoolEducation=ko.mapping.fromJS(new lastSchoolEducation(undefined));
// Closing bootstrap Modal
$('modal-id').modal('hide');
// Cleaning the knockout node , to reflect the reset change 
ko.cleanNode($('#add-student')[0]);
// and binding the view model 
ko.applyBindings(app.mainViewModel, $('#add-student')[0]);

If we are not using below two lines of code then the modal work fine and modal got closed on clicking the close button

ko.cleanNode($('#add-student')[0]);
ko.applyBindings(app.mainViewModel, $('#add-student')[0]);

But I need to use the above two lines of code because it needs to reset my form.

Please help me with this.

Thank You

Most helpful comment

I need to use the above two lines of code because it needs to reset my form.

I don't think I have ever used, or needed to use, ko.cleanNode. In general you should be able to "reset the form" by simply updating the viewmodel the form is bound to - either "resetting" the viewmodel properties that the form fields are bound to, or creating a whole new viewmodel "for the form".

All 7 comments

It would be helpful to provide the HTML/more context code as well if possible.

I need to use the above two lines of code because it needs to reset my form.

I don't think I have ever used, or needed to use, ko.cleanNode. In general you should be able to "reset the form" by simply updating the viewmodel the form is bound to - either "resetting" the viewmodel properties that the form fields are bound to, or creating a whole new viewmodel "for the form".

Exactly. I've never encountered a situation that .cleanNode fixed that better application design couldn't. Like fastfasterfastest said: reset the things that need to be reset in the ViewModel and let Knockout reflect that onto the view.

If I had to guess, I would say that the 'modal-id' (without #?) lives somewhere IN the #add-student region and in the middle of the asynchrounous transition to hidden-state the whole region gets re-written by the cleaning and rebinding. Probably calling after hiding would solve your problem:

$('#modal-id').modal('hide').one('hidden.bs.modal', function (e) {
   ko.cleanNode($('#add-student')[0]);
   ko.applyBindings(app.mainViewModel, $('#add-student')[0]); 
})

Except: don't do this. Listen to fastfasterfastest.

Thanks, @karimayachi

Your solution works. Now my problem got solved.

But as per @fastfasterfastest and you , I should not use ko.cleanNode but without this, it is not working.

I have an observable complex object which contains many observable properties.

var presonalModel = function(data){
        self=this;
        self.fname=ko.observable(data ? data.fname : "");
        self.mname=ko.observable(data ? data.mname : "");
        self.lname=ko.observable(data ? data.lname : "");
        self.dob=ko.observable(data ? data.dob : "");
        self.gender=ko.observable(data ? data.gender : "");
        self.blood=ko.observable(data ? data.blood : "");
        self.phone=ko.observable(data ? data.phone : "");
        self.email=ko.observable(data ? data.email : "");
     }

var addressModel = function(data){
    self=this;
    self.line1=ko.observable(data ? data.line1 : "");
    self.line2=ko.observable(data ? data.line2 : "");
    self.line3=ko.observable(data ? data.line3 : "");
    self.pin=ko.observable(data ? data.pin : "");
    self.city=ko.observable(data ? data.city : "");
    self.state=ko.observable(data ? data.state : "");
}

var addStudentModel = function(data){
var me = {
  basic: ko.mapping.fromJS(new presonalModel(data ? data.basic : undefined)),
  address: ko.mapping.fromJS(new addressModel(data ? data.address : undefined))
}
}

and in view modal I am doing

app.studentViewModel = (function (ko) {
    "use strict";

    var _common_= new commonFunctionality();
    var me = {
        ....
        student: ko.mapping.fromJS(new addStudentModel(undefined)),
        ...
        //many more other property 
    }

// this method will be called on form submission 
    function addStudentApplicationSubmitStatusModelClose() {
                 // sane the data to database by calling API
        $('#add-student-application-submit-status-model').modal('hide');
        $("#add-other-info").addClass("d-none");
        $("#add-stud-basicInfo").removeClass("d-none");
        _resetAddStudent_();
        // Resetting the form on event : modal completely hide event
        $('#add-student-application-submit-status-model').on('hidden.bs.modal', function (e) {
                  ko.cleanNode(element[0]);
                  ko.applyBindings(app.mainViewModel,element[0]);
        })
    }


    function _resetAddStudent_() {
        console.log("========OLD==========");
        console.log(me.student);//COMMENT  1
        // Resetting observable
        me.student = ko.mapping.fromJS(new addStudentModel(undefined));
        console.log("========NEW==========");
        console.log(me.student);//COMMENT 2
        me.addStudentForClass(null);
        me.parents = ko.mapping.fromJS(new parentInfoModel(undefined));
        me.localGuardian = ko.mapping.fromJS(new localGuardian(undefined));
        me.lastSchoolEducation = ko.mapping.fromJS(new lastSchoolEducation(undefined));
     }


    })(ko);

When first time I submit the form then after submission, the form should reset to empty field. But it does not occur but in javascript, in COMMENT 2 I can see that value got reset but it is not reflecting the UI, the UI still hold the old value.

But after using ko.cleanNode, then I got the reset form.

Please let me know if I am doing something wrong . I am new to knockout js. I am doing POC because my coming project is in knockout. I need your help.

Thank You

Can you provide a jsFiddle or something that illustrates your problem? I have to make too many guesses about the binding and views to help you.

Also, general tips: coupling the view and viewmodel by querying and manipulating the DOM from the VM is as bad as design as using .cleanNode()... You should never have to use that. Decouple by providing a basicInfoVisible property (or something like that) and binding to that should take of that. Except maybe for the modal("show"), which you could wrap in a component or custom binding.

If you have a working example (either by jsFiddle or some other method), I can see if I can make it work the clean way...

Hi @karimayachi

Problem 1

I have kept this POC in git.

Its location is: https://github.com/priyank-eschool/eschool-ko-poc

This project will run with node
Command to run : npm start

Project structure .(code is in src folder)

image

When you will start the project with "npm start" then the index page will open .

index page

image

Click on main

then it will display the main.html

image

You need to go to addstudent . Their you will get a multi-page HTML form. After submitting this form the form should reset. For resetting I have used ko.cleanNode($('#add-student')[0]);

Problem 2

I have one more problem in addProfessor tab
It will add a professor. In the second page of the form, their is a question
"Do you have previous experience?" for this we have a radio button. You need to select Yes.

image

Only after selecting Yes, you will be directed to add experience tab.

You will see a text box and button (This is only for testing purpose , I am testing the length of an observableArray "previousExpe in src\js\viewmodel\professor-view-modal.js")

image

In text box it is showing the size of "previousExpe " is one but on clicking the button it is showing 2.

I cant able to understand why It is showing two different value. The size is 2 , in text box it is showing wrong value.

image

In above you can see that I am initializing the value of previousExpe with one object and after that, I am adding a new value in the previousExpe as you can see the below code.

image

The wrong size of previousExpe is displaying in the text box and I am doing foeach binding on previousExpe to display the form to enter the detail, but I can see only one form.

Thanks in Advance for cleaning my doubt

Hi @priyank-eschool ,

The problem lies in:

me.student = ko.mapping.fromJS(new addStudentModel(undefined));

This doesn't actually _clear_ the observables that the view is bound to. You have a structure which is (semi JSON notation):

viewmodel = {
   student: {
      basic: {
         fname: observable('.....'),
         ....
      }
   }
}

Elements in the view are bound to the observables:
<span data-bind="text:studentViewModel.student.basic.fname"></span>
So when you do me.student = ko.mapping.fromJS(new addStudentModel(undefined));, you are not _resetting_ the fname observable, but you are _replacing_ the whole student property. The original property with it's nested observable (fname) still exists because it (fname) is still being referenced.

If you would in stead explicitly clear the property, it would work:

function _resetAddStudent_() {
   me.student.basic.fname('');
   ...

Of course manually clearing all properties can be suboptimal, but you can do this with .map or ko.mapping, just as long as you are sure you are actually updating observables in stead of replacing object-properties.

Looking at your code I see a lot of problematic things, such as:

  • coupling view and viewmodel by manipulting DOM from the VM in many places
  • the html-binding doesn't bind it's children, so using it to load in views is not really a good solution (using a custom-binding that does bind it's children to a viewmodel or wrapping views in Components would be a lot better)
  • using applyBindings on BODY and subsequently on its children (with the same viewmodel)

All these design decisions lead to needing to use hacks such as cleanNode or re-applying bindings.

As a POC I would have started with a simple example, getting KO's basics right and then extending to add functionality. In stead, you seem to have gone the other way around and start with a full application in which you injected KO. So now you have an application that is build on a fundamentally shaky foundation.

I'm afraid I can't help you further, because I'm not going to rewrite this entire application.. I think you really need to start with the basics and practice with KO until you grasp the concepts in stead of trying to make a full fledged application at once.

I haven't looked into your Professor-problem...
Regards,
Karim

Was this page helpful?
0 / 5 - 0 ratings