Reputation: 888
I have some javascript code which opens a modal and applies bindings to my knockout viewmodel. When the modal opens there is a second tab and when clicked, it fires an ajax call and sets an observableArray with the data returned (people, sometimes an empty array). The html code below iterates through the array, and displays the names of the people.
This works fine the first time I click to open the modal, but it doesn't work for any other click, displaying the error Anonymous template defined, but no template content was provided
in console.
I have spent 2 days on this and cannot see where the issue is, although I suspect it could be in my binding method.
I cannot recreate the error in a pen because it uses an Ajax call and I don't know how to imitate that.
HTML:
...<other html, modal, tabs etc...>
<div id="people" class="tab-pane">
<form class="well form-horizontal" id="peopleForm">
<fieldset>
<legend>Registered</legend>
<div class="form-group" data-bind="visible: people().length > 0">
<div data-bind="foreach: people">
<div class="form-group">
<div class="col-md-6 inputGroupContainer">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<input type="text" class="joinEventModalButtons" data-bind="value: $data.FullName" disabled />
</div>
</div>
<div class="col-md-4 inputGroupContainer">
<input type="button" value="Remove" class="btn btn-danger" />
</div>
</div>
</div>
</div>
</fieldset>
</form>
</div>
JS:
var myModel = function myViewModel() {
self.people = ko.observableArray([{ "FullName": "Joe", "Email": "[email protected]" }]);
// setTimeout to try mimic ajax call?
setTimeout(function() {
self.people([])
}, 6000)
....
// ajax call within 'myModel' but also doesn't work if I put it in the section where I show the modal
$.ajax({
url: "/api/people",
type: "GET",
success: function (data) {
self.people(data);
},
error: function (err) {
alert(err.responseJSON.message);
}
});
}
JS For showing Modal, apply bindings etc...:
.. on some button click...:
$('#myPeopleModal').on('hidden.bs.modal', function () {
// do something....
});
ko.cleanNode(document.getElementById('myPeopleModal'));
var myM = new myModel();
ko.applyBindings(myM, document.getElementById('myPeopleModal'));
$('#myPeopleModal').removeClass("hide").modal('show');
foreach: people
the modals will open and close without any issue.Knockout 3.5.1.
Thanks for any help.
Upvotes: 1
Views: 1612
Reputation: 1707
see the first function for faking an async call with a simple promise on a timeout :)
if you don't want to hook onto the bs events from withing your knockout just remember to reset all knockout properties to their init states so next modal-show everything looks right.
this is probably not the 'cleanest' way since we're resetting to the first tab with jquery on closing of the modal but honestly if you only need the applyBindings on the modal, what the heck it works.
console.clear();
var $fakeAsync = function(api, result) {
var dfd = $.Deferred(() => setTimeout(function() {
dfd.resolve(result);
}, 500));
return dfd.promise();
};
ko.applyBindings(() => {
var self = this;
self.people = ko.observableArray([]);
self.loading = ko.observable(false);
self.activatePeople = function() {
self.loading(true);
self.people([]);
$fakeAsync('/api/fake', ['john', 'karen', 'franz']).done(function(result) {
self.people(result);
}).always(() => self.loading(false));
};
self.resetOnClose = function() {
$('#exampleModal #home-tab').click();
self.people([]);
self.loading(false);
return true;
};
}, $('#exampleModal').get(0));
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" />
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.min.js"></script>
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal" data-backdrop="static" data-keyboard="false">
Launch demo modal
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-bind="click: resetOnClose">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" id="people-tab" data-toggle="tab" href="#people" role="tab" aria-controls="people" aria-selected="false" data-bind="click: activatePeople">People</a>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">click the people-tab above</div>
<div class="tab-pane fade" id="people" role="tabpanel" aria-labelledby="people-tab"><span data-bind="visible: loading">loading..</span>
<ul data-bind="foreach: people">
<li data-bind="text: $data"></li>
</ul>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" data-bind="click: resetOnClose">Close</button>
<button type="button" class="btn btn-primary" data-bind="click: resetOnClose">Save changes</button>
</div>
</div>
</div>
</div>
Upvotes: 2