0

On my view I have an option to add users, each user is a div with a few fields:

<div>
    <input data-bind="value: name" />
    <input data-bind="value: position" />
    <input data-bind="value: email" /></div>
    <button data-bind="click: function(){ $root.addUser()}">SAVE</button>
</div>

This div is being added by a button click, and didn't exist when the page loaded.

When I try to save the new user by clicking on the SAVE button, it doesn't work, nothing happens, it just ignores my click.

So my question is- how can I make knockout data-bind work on dynamically generated element?

I read a few posts online but couldn't find a solution, any help would be appreciated.

Users.js script:

var User = function (data) {
    this.name= ko.observable(data.name);
    this.position= ko.observable(data.position);
    this.email= ko.observable(data.email);
}
self.users = ko.observableArray([]);
self.addUser= function () {
  console.log("test");   
}

And this is the code that adds the div: Users.ui.js:

$('body').on('click', '.add', function () {
        var strClientSecHtml = "<div class=\"sec\"><div class=\"secIn\">\n...
        $(strClientSecHtml).insertBefore($(this).closest('.sec'));
        showPrimaryLbl();
})
user3378165
  • 6,546
  • 17
  • 62
  • 101
  • Impossible to know what's going on without seeing your code, but I'm guessing the problem is that your Contacts collection isn't an observableArray. – Jason Spake Jun 20 '17 at 15:10
  • @JasonSpake That's not the case. please see my edited question. – user3378165 Jun 20 '17 at 15:16
  • How are you adding the div, through jQuery? – Jason Spake Jun 20 '17 at 15:29
  • @JasonSpake Yes. – user3378165 Jun 20 '17 at 15:35
  • 1
    This tutorial (http://learn.knockoutjs.com/#/?tutorial=collections) shows you how Knockout work with collections. You add data to your observable array, and Knockout creates the html to show the content. – Jose Luis Jun 20 '17 at 15:59
  • 1
    @JoseLuis Thank you I know Knockout well, I'm not asking here about collections, this is a different question. – user3378165 Jun 20 '17 at 16:03
  • Oh, ok, I'm sorry. :-). Perhaps this helps: https://stackoverflow.com/questions/11066732/knockout-data-bind-on-dynamically-generated-elements. – Jose Luis Jun 20 '17 at 16:09
  • 2
    It's not a different question. When you use knockout, you do not modify the DOM any other way than via data-binds (both custom and default). You can't brush of @JoseLuis' advice as a "different question": the core of the problem is that you're not using knockout as it is intended which will hurt your project eventually. I'd suggest to follow Jose's advice and look at the tutorial, even though you think you already "know knockout well". – user3297291 Jun 20 '17 at 16:34
  • @user3297291 Thank you for your comment, it is not up to me, this is how the graphic designer did it and I can't do anything about it. – user3378165 Jun 20 '17 at 19:07

2 Answers2

3

You can apply a new set of bindings to dynamic content if you create it such that you have an element reference.

$('body').on('click', '.add', function () {
        var strClientSecElem = $('<div class=\"sec\"><div class=\"secIn\">\n...');
        $(this).closest('.sec').before(strClientSecElem);
        ko.applyBindings(new User({}), strClientSecElem.get(0)); //get the native element from jQelement
        showPrimaryLbl();
})

That being said, with knockout you would normally want to modify your javascript objects, and leave DOM modifications up to the library instead of dynamically inserting html. You could push a new User object to the users array, and a foreach:users binding would take care of creating the new elements in the DOM.

Jason Spake
  • 4,293
  • 2
  • 14
  • 22
  • Thanks for your answer, I tried that but I'm getting an error: `strClientSecHtml.get is not a function` any idea? – user3378165 Jun 20 '17 at 17:44
  • @user3378165 There is no "strClientSecHtml" in my example. I changed it to "strClientSecElem" because it was no longer a variable holding html – Jason Spake Jun 20 '17 at 17:45
  • Sorry my mistake. – user3378165 Jun 20 '17 at 17:48
  • 2
    Working perfectly, thank you so much, I see what you are saying about the normal behavior of KO and I also saw @user3297291's comment, but it's not up to me, this is how the graphic designer did it and I can't do anything about it. Thank you very much for your answer! – user3378165 Jun 20 '17 at 17:50
  • I keep getting an error: `$root.addUser() is not a function` would you know what can causes that? Thanks. – user3378165 Jun 21 '17 at 08:39
  • I managed, instead of `ko.applyBindings(new User({})` i wrote the name of my View Model. – user3378165 Jun 21 '17 at 09:54
  • But now I run into another issue, how can I pass the data filled in the added div to the `addUser()` function on submit? – user3378165 Jun 21 '17 at 10:50
1

As Jason so eloquently mentioned, you wouldn't/shouldn't normally do this with knockout. Below is an optimized "knockout-y" version of your code. The jQuery code has been converted to a custom binding which will run each time the element has been added.

HTML:

<!-- ko template: { name: 'user-tmpl', foreach: users } -->
<!- /ko -->

<script type="text/html" id="user-tmpl">
    <div data-bind="userDivOnLoad">
        <input data-bind="value: name" />
        <input data-bind="value: position" />
        <input data-bind="value: email" /></div>
        <button data-bind="click: $parent.addUser()">SAVE</button>
    </div>
</script>

JavaScript Models:

var User = function (data) {
    var self = this;

    self.name = ko.observable();
    self.position = ko.observable();
    self.email = ko.observable();

    if(data)
    {
        self.name(data.name);
        self.position(data.position);
        self.email(data.email);
    }    
}

var AppModel = function(){
    var self = this;

    self.users = ko.observableArray([]);

    self.addUser= function () {
      console.log("DSA");

      self.users.push(new User());
    }

    //hydrate
    (function(){
        self.users.push(new User());
    }();
};

Custom Binding:

ko.bindingHandlers.userDivOnLoad = {
    init: function(element) {
        showPrimaryLbl();
    }
};

My recommendation is to remove the DOM manipulation from the binding handler and determine a logical construct in which you can apply it within knockout models.

pim
  • 12,019
  • 6
  • 66
  • 69
  • Thank you for your anser, I would like to try it, but I think I need more of an explanation, could you? – user3378165 Jun 22 '17 at 07:11
  • I'd be happy to. Can you elaborate on what you'd like explained? – pim Jun 22 '17 at 11:04
  • Thank you, what is the Custom Binding part? What does is do? Where should I place it? – user3378165 Jun 22 '17 at 11:27
  • You can think of custom bindings as "the place where you write the JavaScript/jQuery you used to write all over the place". I usually place them in it's own file, but on small projects in the main.js is also acceptable. Custom bindings, allow you to use the ko binding syntax and inject your own JavaScript logic/bindings. The documentation is here: http://knockoutjs.com/documentation/custom-bindings.html – pim Jun 22 '17 at 12:48