I'm trying to dynamically load partial view with a different model instances inside of main view. Everything is working fine, until I have to reload partial view content. Then I have to reload 1 select list options, that is not in model. I have js method for that. Problem is, sometimes options are added, and disapear instantly. What am I doing wrong?
There are no any methods, that are changind this select control.
Main view:
<div id="tableContainer">
#{Html.RenderPartial("_TableView", Model.Tables[0]);}
</div>
<script type="text/javascript">
function loadTable () {
var selectedElement = $("#tableSelect").children("option:selected").val();
$("#tableContainer").load("/Home/UpdateTable/" + selectedElement);
getDishes();
};
window.onload = function () {
getDishes();
};
</script>
getDishes function:
function getDishes() {
$.ajax({
type: "GET",
url: "/GetDishes",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: "",
success: function (result) {
if (result.success) {
fillDishList(result.message);
}
else
alert("Błąd podczas pobierania dostępnych dań! Treść błędu: " + result.message);
}
})
}
And fillDishList function:
function fillDishList(list) {
for (var a = 0; a < list.length; a++) {
var element = { val: a, text: list[a].Name };
$("#dishes").append($("<option>").attr('value', element.val).text(element.text));
}
updateDishPrice();
}
EDIT
Ok I got it working. There was timing issue. Function that should append elements to select list were launched before partial view were completely loaded. Here is solution:
<script type="text/javascript">
function loadTable () {
var selectedElement = $("#tableSelect").children("option:selected").val();
$("#tableContainer").html("");
$("#tableContainer").load("/Home/UpdateTable/" + selectedElement);
window.requestAnimationFrame(ready);
};
var ready = function () {
var dishes = $("#dishes");
if (dishes) {
getDishes();
if (dishes.children().length != 0)
return;
};
window.requestAnimationFrame(ready);
};
window.onload = function () {
getDishes();
};
</script>
i am using the knockout js, i am finding diffcult to bind the data while in ajax get method, i have created model, viewModel, and ajax function, i have the ajax method in the same js file where i have created viewModel i am calling the ajax on page load and trying to bind my html with konckout js, i am getting the error userModel is undefined if i give this.name = ko.observale(result[0].name) before the ajax call, after the ajax called it give name is undefined need help
<html>
<head>
<script src="js/jquery1.9.js"></script>
<script src="js/knockout-3.3.0.js"></script>
<script src="js/knockout.mapping.js"></script>
<script src="model/usermodel.js"></script>
</head>
<body>
<div>
<h1><span data-bind="text:user().name"></span></h1>
<h1><span data-bind="text:user().userName"></span></h1>
</div>
<script src="ViewModel/userDetailsViewModel.js"></script>
</body>
</html>
////Model////
function userModel(result) {
var self = this;
this.name = ko.observable(result[0].name); /// give me error undefined before the ajax call and after ajax call i get the value in result
this.userName = ko.observable();
}
/////View Model////
var result
var userDetailsViewModel = function(result) {
self = this;
self.user = ko.observable(new userModel(result));
};
var mainUserDetailsViewModel = new userDetailsViewModel(result);
ko.applyBindings(mainUserDetailsViewModel);
////ajax called on the page load ////
$.ajax({
type: "POST",
dataType: "json",
url: baseUrl + 'api/xx/xxx',
data: jason.strigfy(),
success: function(data) {
result = data;
////I am getting in result json data object 0=["name":"nnnn","Username":"mmmmmm"],
//// i am passing this result to ViewModel and to Usermodel Constructor//
mainUserDetailsViewModel.user(new userModel(result));
},
error: function(error) {
jsonValue = jQuery.parseJSON(error.responseText);
//jError('An error has occurred while saving the new part source: ' + jsonValue, { TimeShown: 3000 });
}
});
Here is my suggestion to have a clean nested view model.
Example : https://jsfiddle.net/kyr6w2x3/28/
function UserViewModel() {
var self = this;
self.UsersList = ko.observableArray([]);
self.GetUsers = function() {
$.ajax({
type: "POST",
dataType: "json",
url: baseUrl + 'api/xx/xxx',
data: jason.strigfy(),
success: function (data) {
//Here you map and create a new instance of userDetailVM
self.UsersList($.map(data, function (user) {
return new UserDetailViewModel(user);
}));
}
});
}
//call to get users list when the VM is loading or you can call it on any event on your model
self.GetUsers();
}
function UserDetailViewModel(data){
var self = this;
self.Name = ko.observable(data.name);
self.UserName = ko.observable(data.username);
}
ko.applyBindings(new UserViewModel());
View :
<h1 data-bind="foreach: UsersList">
<div data-bind="text: Name"></div>
<div data-bind="text: UserName"></div>
</h1>
I am trying to pull data from a database using an ajax call and then put that data in a view model which will then be bound to a table using knockout. Here is my code:
<script>
$(document).ready(function () {
var LoadFiles = '#Url.Action("Files", "Home")';
var HomeModel = function () {
debugger
var self = this;
self.rows = ko.observableArray([]);
$.ajax({
method: "POST",
url: LoadFiles,
success: function (data) {
alert('inside ajax call');
self.rows = JSON.parse(data);
},
error: function (data) {
alert('error');
}
});
}
alert('outside ajax call');
var model = new HomeModel();
debugger
ko.applyBindings(model);
});
</script>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
However, the problem is that my view model.rows is empty because as I discovered through some alert("") calls, the `alert('outside ajax call'); is called first, then the page loads and binding is applied, then the alert('inside ajax call'); is called. I don't understand how this is possible especially since I call var model = new HomeModel() before the bindings are applied. How can I ensure that the self.rows of my HomeModel are populated before the page loads to ensure my .rows are not empty?
UPDATE:
Thanks to #RoyJ, this now works as expected:
<script>
$(document).ready(function () {
var LoadFiles = '#Url.Action("Files", "Home")';
var HomeModel = function () {
debugger
alert('above');
var self = this;
self.rows = ko.observableArray([]);
$.ajax({
method: "POST",
url: LoadFiles,
success: function (data) {
alert('inside ajax call');
self.rows(JSON.parse(data))
alert('below');
},
error: function (data) {
alert('error');
}
});
}
alert('outside ajax call');
var model = new HomeModel();
debugger
ko.applyBindings(model);
});
</script>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
TL;DR: I need to set a default value for display on a select2 field, bound via knockout, but the select2 binding keeps overriding my viewmodel value to "" instead of accepting the value.
The Ingredients
I am utilizing the following:
KnockoutJS
Select2 on input fields
Custom knockout binding to select2
An ajax call to load an object (an invoice) as part of a Start() method on my viewmodel.
The Goal
Load my values as part of the initial viewmodel's Start() function
Bind the select2 default values to the values of my VM at the time it loads the invoice.
Allow users to select other options as they choose
the default values wold be included in the select2 options as well due to the way we're bringing down the select2 values, so no need to
The Problem
Select2 would be working entirely fine if I was starting from a blank form. It loads my values from an Ajax call upon dropdown, etc.
However, when I load the invoice for display, the viewmodel values aren't set on the select2 controls.
It appears the select2 control actually loads the data and overwrites my viewmodel's value with "" when it loads, as a value hasn't been selected yet -- rather than letting me show a default item based on my bound value..
Thoughts so far on Trying to Solve it
I'll be investigating all of these:
I might not be properly using the knockout binding to allow for a default element choice that isn't a part of its values.
If there is a way I could verify that the select2 boxes are loaded and then trigger an element update, that would be fine, too.
The Code
Document Load
$(document).ready(function () {
'use strict';
console.log("creating viewmodel");
vm = new invoiceDetailsPage.ViewModel();
vm.Start();
console.log("applying bindings");
ko.applyBindings(vm);
});
The invoiceDetailsPage NameSpace(some irrelevant parts removed)
var invoiceDetailsPage = invoiceDetailsPage || {
InvoiceDetailItem: function () {
'use strict';
var self = this;
self.DatePayable = new Date();
self.Fees = 0.00;
self.Costs = 0.00;
self.Adjustments = ko.observable();
self.AdjustmentNote = ko.observable();
self.Total = ko.computed(function () {
});
self.hasAdjustments = ko.computed(function () {
});
},
Invoice: function (invoiceID, documentTypeID, firmID, invoiceNumber, invoicePeriod, datePayable, privateComment, statusID, vendorFirmID) {
'use strict';
var self = this;
self.TypeID = ko.observable(documentTypeID);
self.PrivateComment = ko.observable(privateComment);
self.Status = ko.observable(statusID);
self.FirmID = ko.observable(firmID);
self.VendorFirmID = ko.observable(vendorFirmID);
self.InvoicePeriod = ko.observable(invoicePeriod);
self.DatePayable = ko.observable(datePayable);
self.InvoiceNumbers = ko.observable(invoiceNumber);
self.DetailItems = ko.observableArray([]);
self.isFinalized = ko.computed(function () {
//finalized if it has the appropriate status (anything except)
});
self.hasPrivateComments = ko.computed(function () {
// if self.privatecomment isn't null or empty, true
});
self.TotalFees = ko.computed(function () {
//foreach item in detailitems, sum of fees.
});
self.TotalCosts = ko.computed(function () {
//foreach item in detailitems, sum of Costs.
});
self.TotalAdjustments = ko.computed(function () {
//foreach item in detailitems, sum of adjustments.
});
self.GrandTotal = ko.computed(function () {
//foreach item in detailitems, sum of totals.
});
},
LoadInvoice: function (clientSiteID, invoiceID, callbackFunction, errorFunction) {
'use strict';
var self = this;
self.clientSiteID = clientSiteID;
self.invoiceID = invoiceID;
$.ajax({
url: '/api/DefenseInvoice/GetDefenseInvoice?ClientSiteID=' + self.clientSiteID + "&InvoiceID=" + invoiceID,
type: 'GET',
processData: false,
contentType: 'application/json; charset=utf-8',
dataType: "json",
data: null,
success: function (data) {
console.log(data);
callbackFunction(data);
},
error: function (jqXHR, textStatus, errorThrown) {
errorFunction(jqXHR, textStatus, errorThrown);
}
});
},
ViewModel: function () {
'use strict';
var self = this;
self.InvoiceLoaded = ko.observable();
self.Invoice = ko.observable(new invoiceDetailsPage.Invoice()); // load blank invoice first
self.clientSiteID = -1;
self.invoiceID = -1;
self.SaveInvoiceDetails = function () {
// can only save the details prior to approval / rejection
// should update only general invoice fields, not private comments or adjustments
};
self.LoadInvoice = function() {
self.InvoiceLoaded(false);
invoiceDetailsPage.LoadInvoice(self.clientSiteID, self.invoiceID, function(result) {
//success
vm.Invoice(new invoiceDetailsPage.Invoice(
result.InvoiceInfo.DefenseInvoiceID,
result.InvoiceDocumentTypeID,
result.InvoiceInfo.FirmID,
result.InvoiceInfo.InvoiceNumber,
result.InvoiceInfo.InvoicePeriod,
result.InvoiceInfo.DatePayable,
result.InvoiceInfo.PrivateComment,
result.InvoiceInfo.StatusID,
result.InvoiceInfo.VendorFirmID
));
self.InvoiceLoaded(true);
}, function() {
//error
toastr.error("We're sorry, but an error occurred while trying to load the invoice. Please contact support or refresh the page to try again.", "Invoice Approval");
console.log("LoadInvoice -- ERROR");
console.log(" error: " + errorThrown);
toastr.clear(notifier);
});
};
self.Start = function () {
self.LoadInvoice();
};
},
utils: {
GetSelect2Options: function (placeholder, url) {
'use strict';
var options = {
allowClear: false,
placeholder: placeholder,
query: function (query) {
var dto = {
query: query.term,
filters: {
ClientSiteID: Number(vm.clientSiteID)
}
};
$.ajax({
type: "POST",
url: url,
data: JSON.stringify(dto),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
query.callback(msg);
}
});
}
};
return options;
}
}
};
The Knockout Binding we're using
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindingsAccessor) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor(),
lookupKey = allBindings.lookupKey;
$(element).select2(obj);
if (lookupKey) {
var value = ko.utils.unwrapObservable(allBindings.value);
$(element).select2('data', ko.utils.arrayFirst(obj.data.results, function (item) {
return item[lookupKey] === value;
}));
}
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).select2('destroy');
});
},
update: function (element) {
$(element).trigger('change');
}
};
The HTML Element and its bindings
<input type="text" id="ddlInvoiceType" placeholder="Invoice Type" class="select2-container" data-bind="select2: invoiceDetailsPage.utils.GetSelect2Options('Invoice Type', '/api/DefenseInvoiceType/Post'), value: Invoice().TypeID"/>
Not sure I understood the question properly, at first I see the possible issue in the update part of the custom binding:
update: function (element, valueAccessor) {
//added next row to update value
$(element).val(ko.utils.unwrapObservable(valueAccessor()));
$(element).trigger("change");
}
I got it working and I think the difference is in the init with an select like
<input type="hidden" class=""
data-bind="value: observedValue, select2: --your options--">
Here is mine:
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).select2('destroy');
});
select2 = ko.utils.unwrapObservable(allBindings().select2);
$(element).select2(select2);
},
I am trying to fetch data from restful wcf service in page load and bind to drop down which is not working.
function CreateItem(name, value) {
var self = this;
self.itemName = ko.observable(name);
self.itemValue = ko.observable(value);
};
function AppViewModel() {
var self = this;
self.titleList = ko.observableArray();
self.load = function () {
alert("click fired");
$.ajax({
url: "https://mydomain/RestfulService/Service1.svc/CreateData?name=venkat",
type: "POST",
cahce: false,
async: false,
data:'',
dataType: "jsonp",
success: function (data) {
for (var i = 0; i < data.length; i++) {
self.titleList().push(new CreateItem(data[i].Description, data[i].TitleID));
}
alert("success " + data);
},
error: function (error) {
alert("failed " + error);
}
});
};
};
<div>
<select data-bind="options: titleList(), optionsText: 'itemName', optionsValue: 'itemValue', value: selectedTitleValue, optionsCaption: 'Please choose'"></select>
</div>
<script type="text/javascript">
$(document).ready(function() {
var model = new AppViewModel();
model.load();
ko.applyBindings(model);
});
</script>
The issue is, knockout array is populating in load function correctly but drop down is not refreshing updated data. I am not understanding where is the problem. Please give inputs.
Replace:
self.titleList().push(new CreateItem(data[i].Description, data[i].TitleID));
with
self.titleList.push(new CreateItem(data[i].Description, data[i].TitleID));
The reason is self.titleList() returns the underlying array, when you push data to it, Knockout is unaware of the changes and does not notify the views.