I'm trying to set a value to an element of my observableArray, and get an error i can't resolve:
Uncaught TypeError: Property 'Locked' of object #<Object> is not a function
This is my piece of code i have written:
$(function () {
// -----------------------------------------------------------------------//
// ** brand - model ** //
// -----------------------------------------------------------------------//
var Brand = function (e) {
var self = this;
self.Id = ko.observable(e ? e.Id : '');
self.Name = ko.observable(e ? e.Name : '').extend({ required: true });
self.Description = ko.observable(e ? e.Description : '');
self.LogoId = ko.observable(e ? e.LogoId : '');
self.Logo = (e ? (e.LogoId != null ? ko.observable(new Logo(e.Logo)) : null) : null);
self.DisplayOrder = ko.observable(e ? e.DisplayOrder : '');
self.Deleted = ko.observable(e ? e.Deleted : '');
self.State = ko.observable(e ? e.State : '');
self.DateChanged = ko.observable(e ? e.DateChanged : '');
self.DateCreated = ko.observable(e ? e.DateCreated : '');
self.Locked = ko.observable(e ? e.Locked : '');
// validation
self.Errors = ko.validation.group(self);
self.IsValid = function () {
if (self.Errors().length > 0) {
self.Errors.ShowAllMessages();
return false;
}
return true;
};
};
// -----------------------------------------------------------------------//
// ** logo - model ** //
// -----------------------------------------------------------------------//
var Logo = function (e) {
var self = this;
self.Id = ko.observable(e ? e.Id : '');
self.FileName = ko.observable(e ? e.FileName : '');
self.URL = ko.observable(e ? e.URL : '');
self.PictureType = ko.observable(e ? e.PictureType : '');
self.Deleted = ko.observable(e ? e.Deleted : '');
self.State = ko.observable(e ? e.State : '');
self.DateChanged = ko.observable(e ? e.DateChanged : '');
self.DateCreated = ko.observable(e ? e.DateCreated : '');
};
// -----------------------------------------------------------------------//
// ** view - model ** //
// -----------------------------------------------------------------------//
var BrandViewModel = function (hub) {
// init
var self = this;
var url = "/api/brand/brands";
// public data properties
self.Brands = ko.observableArray([]);
self.NewBrand = ko.observable(new Brand());
// -----------------------------------------------------------------------//
// ** Web API Actions ** //
// -----------------------------------------------------------------------//
// load
self.Load = function () {
// block
self.BlockBrands();
// Initialize the view-model
$.ajax({
url: url,
type: 'GET',
contentType: 'application/json; charset=utf-8',
success: function (data) {
// add brands
self.Brands(data);
// tooltip
self.InitTooltip();
},
error: function (err) {
self.ShowError(err);
},
complete: function () {
self.UnblockBrands();
}
});
}
// -----------------------------------------------------------------------//
// ** SignalR Actions ** //
// -----------------------------------------------------------------------//
self.LockItem = function (id) {
// find item
var brand = self.getBrandById(id);
brand.Locked(true);
}
self.UnlockItem = function (id) {
// find item
var brand = self.getBrandById(id);
brand.Locked(false);
}
// -----------------------------------------------------------------------//
// ** Utilities ** //
// -----------------------------------------------------------------------//
self.getBrandById = function (Id) {
return ko.utils.arrayFirst(self.Brands(), function (item) {
if (item.Id == Id) {
return item;
}
});
}
self.Load();
};
// -----------------------------------------------------------------------//
// ** init ** //
// -----------------------------------------------------------------------//
var hub = $.connection.brand;
var brandViewModel = new BrandViewModel(hub);
// -----------------------------------------------------------------------//
// ** signalR ** //
// -----------------------------------------------------------------------//
hub.client.LockItem = function (id) {
brandViewModel.LockItem(id);
}
hub.client.UnlockItem = function (id) {
brandViewModel.UnlockItem(id);
}
$.connection.hub.start();
// -----------------------------------------------------------------------//
// ** knockout ** //
// -----------------------------------------------------------------------//
// knockout validation
ko.validation.configure({
insertMessages: true,
decorateElement: true,
errorElementClass: 'error'
});
// knockout binding
ko.applyBindings(brandViewModel);
});
HTML - Knockout binding
<table data-bind="visible: Brands().length > 0" class="table table-striped table-bordered table-hover" id="brands">
<tbody data-bind="foreach: Brands">
<tr>
<td class="align-center">
<!-- ko if: Logo -->
<img data-bind="attr: { src: Logo.URL() + '?width=50&height=50' }" class="img-polaroid" />
<!-- /ko -->
</td>
<td data-bind="text: Name()"></td>
<td data-bind="date: DateChanged()" class="align-center"></td>
<td class="align-center">
<!-- ko ifnot: Locked -->
<a data-bind="click: $root.Edit" class="btn blue tip" data-original-title="wijzigen"><i class="icon-edit"></i></a>
<a data-bind="click: $root.ShowDeleteModal" class="btn red tip" data-original-title="verwijderen"><i class="icon-trash"></i></a>
<!-- /ko -->
<!-- ko if: Locked -->
<div class="btn black"><i class="icon-lock"></i></div>
<!-- /ko -->
</td>
</tr>
</tbody>
</table>
Am I doing something wrong assigning the value? I've also tried to do it like this : brand.Locked = true;. This time i get no error, but knockout doesn't respond.
The problem is that when you're calling ko.utils.arrayFirst(self.Brands())
, your Brands
are no longer observables
but regular javascript objects
(with 'Locked'
as a property and not as an observable
function), and that is because when you're retrieving the data from the server and pushing it into your Brands
array, you're not wrapping it with ko.observableArray
.
Try:
success: function (data) {
// add brands
self.Brands(ko.observableArray(data));
And in your getBrandById
function:
self.getBrandById = function (Id) {
return ko.utils.arrayFirst(self.Brands(), function (item) {
if (item.Id() == Id) {
return item;
}
});
EDIT:
Actually, since your Brands
array contains Brand
objects, ko.observableArray
is not sufficient to convert the inner properties of each Brand
to obversable
as well. you're gonna need to use the mapping
plugin, as follows:
success: function (data) {
// add brands
self.Brands = ko.mapping.fromJS(data);
More about the ko.utils.mapping plugin here.