I'm having issues with my knockoutjs implementation. Seems to be working fine on Chrome and Safari IE and FF have thrown a hissy fit.
The message which I encounter is as follows:
Unable to parse bindings. Message: TypeError: 'AccountName' is undefined; Bindings value: value: AccountName
The issue is happening within a script tag which serves as a knockout template:
<div id="newAccountDialog" class="dialog" data-bind="dialog: { autoOpen: false, resizable: false, modal: true, width: 350, title: 'Exchange Account'}, template: { name: 'dialogFormTemplate', data: CurrentAccount }, openDialog: IsNew"></div>
<script id="dialogFormTemplate" type="text/html">
<form id="dialogForm">
<h1>Exchange Account Manager</h1>
<p>Add new or edit an existing exchange account settings.</p>
<label for="AccountName">
Account
</label>
<input id="AccountName" name="AccountName" type="text" data-bind="value: AccountName, valueUpdate: 'afterkeydown'" class="ui-widget-content ui-corner-all" />
<div class="buttonsContainer floatRight">
<button id="Save" data-bind="click: $root.SaveAccount, dialogcmd: { id: 'newAccountDialog', cmd: 'close'}, jqButton: { icons: { primary: 'ui-icon-disk' } }">Save & Close</button>
</div>
</form>
</script>
I assume some sort of early binding is being triggered on the template
data : CurrentAccount
where an undefined / null is being passed into CurrentAccount. I have seen this issue outside of script tags, but only if the observable is not defined or null.
My viewmodel looks as following:
var AccountModel = function () {
var self = this;
self.Accounts = ko.observableArray([]);
self.CurrentAccount = ko.observable(null);
self.IsNew = ko.observable(false);
self.LoadAccounts = function () {
$account.invoke("GetAccounts", {}, function (data) {
var mapped = $.map(data, function (item) {
var account = new Account(item);
var innerMapped = $.map(item.Mailboxes, function (mailbox) {
return new Mailbox(mailbox);
});
account.Mailboxes(innerMapped);
return account;
});
self.Accounts(mapped);
});
}
self.EditAccount = function (data) {
self.CurrentAccount(data);
self.IsNew(true);
}
self.SaveAccount = function () {
if (self.CurrentAccount().Id() <= 0) {
$account.invoke('AddAccount', ko.toJS(self.CurrentAccount()), function (data) {
self.Accounts.push(new Account(data));
self.CurrentAccount(new Account(data));
self.IsNew(true);
});
} else {
$account.invoke('UpdateAccount', ko.toJS(self.CurrentAccount()), function (data) {
//self.CurrentAccount(new Account(data));
});
}
}
self.CreateAccount = function () {
self.IsNew(true);
var account = { Id: 0, AccountName: '', IsNTLM: -1, Email: '', Password: '', Domain: 'mydomain', ExchangeVersion: 1, Mailboxes: [] };
self.CurrentAccount(new Account(account));
}
};
My dialog bindingHandler is defined as follows:
ko.bindingHandlers.dialog = {
init: function (element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()) || {};
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).dialog('destroy');
});
$(element).dialog(options);
}
};
I have ommited the Account object, as it is possibly not required in this context.
I would appreciate any help.
Thank you in advance.
There is no "early" binding in Knockout. Everything is bound when you call ko.applyBindings
. But certain bindings can stop or delay binding of their descendant elements. template
is one of those when you use the if
or ifnot
options. In your case, you can use the if
option like this:
template: { name: 'dialogFormTemplate', data: CurrentAccount, 'if': CurrentAccount }
Note: The quotes around if
are required in some older browsers.