Strange data binding issue - javascript

I have a crazy issue with my code. I'm trying to implement this jsfiddle code
In my code, but I have no success. Here is what I have done:
ViewModel:
viewModelInbox = function(){
query: ko.observable('');
var checked = false,
mainData = ko.observableArray([]),
showView = ko.observable(),
currentView = ko.observable(),
approve = function(){
},
disapprove = function(){},
toggle = function () {
if(checked){
$.each(mainData(), function(){
this.checkB(false);
});
checked = false;
return;
}
if(!checked){
$.each(mainData(), function(){
this.checkB(true);
});
checked = true;
return;
}
};
viewModelInbox.mainData = ko.dependentObservable(function() {
var search = this.query().toLowerCase();
return ko.utils.arrayFilter(viewModelInbox, function(test) {
return test.name.toLowerCase().indexOf(search) >= 0;
});
}, viewModelInbox);
return {
mainData: mainData,
showView: showView,
currentView: currentView,
approve: approve,
disapprove: disapprove,
toggle: toggle
};
},
The mainData observable array is holding some values as name, code, date, etc.
The issue I have is that I'm getting this error:
TypeError: this.query is not a function
var search = this.query().toLowerCase();
I'm pretty sure that I'm missing something really small, but as I'm a total noob in knockoutjs I can not spot it.

It seems not something small.
Your view model should be either
var ViewModel = function() {
this.query = ko.observable(''); // use ';'
this.mainData = ko.observableArray([]);
};
or
var viewModel = {
query: ko.observable(''), // use ','
mainData: ko.observableArray([])
};
You can't mix them.

Or you can write like this:
var ViewModelInbox = function() {
var self = this;
self.query = ko.observable('');
self.dataSource = []; // data source
self.mainData = ko.computed(function() {
var search = self.query().toLowerCase();
return ko.utils.arrayFilter(self.dataSource, function(item) {
return item.name.toLowerCase().indexOf(search) >= 0;
});
});
self.showView = ko.observable();
self.currentView = ko.observable();
self.approve = function() {
};
self.disapprove = function() {
};
self.checked = ko.observable(true);
self.toggle = function() {
var toCheck = !self.checked();
ko.arrayForEach(self.mainData(), function(data) {
data.checkB(toCheck);
});
self.checked(toCheck);
};
};
ko.applyBindings(new ViewModelInbox());

Related

clearing all elements from Javascript knockout observableArray. Items remain causing duplicates

I am trying to create an observableArray of "Board" objects to populate a view.
I can currently add new Board objects to the array after each timed page refresh. But instead of clearing the array and then adding new boards from the foreach loop, it just adds to the existing ones causing duplicates.
$(document).ready(function() {
refreshPage();
});
function refreshPage() {
getGames();
setTimeout(refreshPage, 10000);
console.log("Page refreshed");
};
function Board(data) {
this.gameChannel = ko.observable(data.GameChannel);
this.HomeTeamImage = ko.observable(data.HomeTeamImage);
this.HomeTeamName = ko.observable(data.HomeTeamName);
this.HomeBeerPrice = ko.observable(data.HomeBeerPrice);
this.HomeTeamArrow = ko.observable(data.HomeTeamArrow);
this.HomeBeer = ko.observable(data.HomeBeer);
this.HomeBeerAdjustedPrice = ko.observable(data.HomeBeerAdjustedPrice);
this.AwayTeamArrow = ko.observable(data.AwayTeamArrow);
this.AwayBeerPrice = ko.observable(data.AwayBeerPrice);
this.AwayTeamName = ko.observable(data.AwayTeamName);
this.AwayBeerAdjustedPrice = ko.observable(data.AwayBeerAdjustedPrice);
this.AwayBeer = ko.observable(data.AwayBeer);
this.awayTeamImage = ko.observable(data.AwayTeamImage);
this.FullScore = ko.computed(function() {
return data.HomeTeamScore + " | " + data.AwayTeamScore;
}, this);
}
function vm() {
var self = this;
self.gameCollection = ko.observableArray([]);
}
getGames = function() {
var _vm = new vm();
$.ajax({
type: "GET",
dataType: "json",
url: "/Dashboard/PopulateMonitor/",
error: errorFunc,
success: function(data) {
_vm.gameCollection = [];
$.each(data, function() {
_vm.gameCollection.push(new Board(this));
});
}
});
function errorFunc() {
alert("Error, could not load gameboards");
}
ko.applyBindings(_vm);
}
The issue appears within the getGames() function on or around the line
_vm.gameCollection = [];
I appreciate any help available. Not very well versed with Knockout.js
Every time you're calling getGames you're creating a new '_vm':
getGames = function () {
var _vm = new vm();
Move var _vm = new vm(); to
$(document).ready(function () {
var _vm = new vm(); // <-- HERE
refreshPage();
});
Some lines have to be moved too, see the snippet :
$(document).ready(function() {
_vm = new vm();
refreshPage();
});
function refreshPage() {
getGames();
setTimeout(refreshPage, 10000);
console.log("Page refreshed");
};
function Board(data) {
this.gameChannel = ko.observable(data.GameChannel);
this.HomeTeamImage = ko.observable(data.HomeTeamImage);
this.HomeTeamName = ko.observable(data.HomeTeamName);
this.HomeBeerPrice = ko.observable(data.HomeBeerPrice);
this.HomeTeamArrow = ko.observable(data.HomeTeamArrow);
this.HomeBeer = ko.observable(data.HomeBeer);
this.HomeBeerAdjustedPrice = ko.observable(data.HomeBeerAdjustedPrice);
this.AwayTeamArrow = ko.observable(data.AwayTeamArrow);
this.AwayBeerPrice = ko.observable(data.AwayBeerPrice);
this.AwayTeamName = ko.observable(data.AwayTeamName);
this.AwayBeerAdjustedPrice = ko.observable(data.AwayBeerAdjustedPrice);
this.AwayBeer = ko.observable(data.AwayBeer);
this.awayTeamImage = ko.observable(data.AwayTeamImage);
this.FullScore = ko.computed(function() {
return data.HomeTeamScore + " | " + data.AwayTeamScore;
}, this);
}
function vm() {
var self = this;
self.gameCollection = ko.observableArray([]);
ko.applyBindings(this);
}
getGames = function() {
$.ajax({
type: "GET",
dataType: "json",
// placeholder:
url: 'data:application/json;utf8,[]',
//url: "/Dashboard/PopulateMonitor/",
error: errorFunc,
success: function(data) {
_vm.gameCollection.removeAll();
$.each(data, function() {
_vm.gameCollection.push(new Board(this));
});
}
});
function errorFunc() {
alert("Error, could not load gameboards");
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Couple of things:
You shouldn't call applyBindings more than once. So, move it outside of your setTimeout.
_vm.gameCollection = [] won't work. To clear your observableArray, use removeAll. You can also set it to an empty array like this: _vm.gameCollection([])
Also, if you want to call the same function after an interval of time, you can make use of setInterval.
Here's a minimal version of your code. Click on Run code snippet to test it out. I have created a counter variable which updates gameCollection with new data every second.
let counter = 0;
function refreshPage() {
getGames();
console.log("Page refreshed");
};
function Board(data) {
this.gameChannel = ko.observable(data.GameChannel);
}
function vm() {
var self = this;
self.gameCollection = ko.observableArray([]);
}
getGames = function() {
let data = [
{
GameChannel: `GameChannel ${++counter}`
},
{
GameChannel: `GameChannel ${++counter}`
}];
_vm.gameCollection.removeAll(); // <- Change here
data.forEach(function(item) {
_vm.gameCollection.push(new Board(item));
});
}
var _vm = new vm();
ko.applyBindings(_vm); // this needs to be only called once per page (or element)
setInterval(refreshPage, 1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko foreach: gameCollection -->
<span data-bind="text: gameChannel"></span><br>
<!-- /ko -->

Data not binding as it should be after calling ajax with knockoutjs

I have this grid that i am creating using knockoutjs, it works perfectly at first, now i am using a window.location.hash to run another query, it works too and query returns the correct amount of data however when i insert it within the observableArray (which gets inserted correctly as well), the grid doesn't update the data and shows the old data... I'm using removeAll() function on the observableArray as well before inserting new set of data but it wont update my grid, i suspect there is something wrong with the DOM?
I should mention when i reload the page (since the page's url keeps the hash for query) my grid shows the data and works perfectly. for some reason it needs to reload the page and doesn't work without,
Here is my JS:
if (!ilia) var ilia = {};
ilia.models = function () {
var self = this;
this.pageCount = ko.observable(0);
//this is the observableArray that i am talking about ++++++++
this.items = ko.observableArray();
var $pagination = null;
var paginationConfig = {
startPage: 1,
totalPages: 20,
onPageClick: function (evt, page) {
self.generateHash({ pageNum: page });
self.getData();
}
}
var hashDefault = {
pageNum: 1,
pageSize: 20,
catId: null,
search: ""
}
this.dataModel = function (_id, _name, _desc, _thumb, _ext) {
var that = this;
this.Id = ko.observable(_id);
this.Name = ko.observable(_name);
this.Desc = ko.observable(_desc);
this.Url = '/site/ModelDetail?id=' + _id;
var b64 = "data:image/" + _ext + ";base64, ";
this.thumb = ko.observable(b64 + _thumb);
}
this.generateHash = function (opt) {
//debugger;
var props = $.extend(hashDefault, opt);
var jso = JSON.stringify(props);
var hash = window.location.hash;
var newHash = window.location.href.replace(hash, "") + "#" + jso;
window.location.href = newHash;
return jso;
}
this.parseHash = function () {
var hash = window.location.hash.replace("#", "");
var data = JSON.parse(hash);
if (data)
data = $.extend(hashDefault, data);
else
data = hashDefault;
return data;
}
var _cntrl = function () {
var _hdnCatName = null;
this.hdnCatName = function () {
if (_hdnCatName == null)
_hdnCatName = $("hdnCatName");
return _hdnCatName();
};
var _grid = null;
this.grid = function () {
if (_grid == null || !_grid)
_grid = $("#grid");
return _grid;
}
this.rowTemplate = function () {
return $($("#rowTemplate").html());
}
}
this.createPagnation = function (pageCount, pageNum) {
$pagination = $('#pagination-models');
if ($pagination && $pagination.length > 0)
if (paginationConfig.totalPages == pageCount) return;
var currentPage = $pagination.twbsPagination('getCurrentPage');
var opts = $.extend(paginationConfig, {
startPage: pageNum > pageCount ? pageCount : pageNum,
totalPages: pageCount,
onPageClick: self.pageChange
});
$pagination.twbsPagination('destroy');
$pagination.twbsPagination(opts);
}
this.pageChange = function (evt, page) {
var hash = self.parseHash();
if (hash.pageNum != page) {
self.generateHash({ pageNum: page });
self.getData();
}
}
this.getData = function () {
var _hash = self.parseHash();
inputObj = {
pageNum: _hash.pageNum,
pageSize: _hash.pageSize,
categoryId: _hash.catId
}
//debugger;
//console.log(_hash);
if (inputObj.categoryId == null) {
ilia.business.models.getAll(inputObj, function (d) {
//debugger;
if (d && d.IsSuccessfull) {
self.pageCount(d.PageCount);
self.items.removeAll();
_.each(d.Result, function (item) {
self.items.push(new self.dataModel(item.ID, item.Name, item.Description, item.Thumb, item.Extention));
});
if (self.pageCount() > 0)
self.createPagnation(self.pageCount(), inputObj.pageNum);
}
});
}
else {
ilia.business.models.getAllByCatId(inputObj, function (d) {
if (d && d.IsSuccessfull) {
self.pageCount(d.PageCount);
self.items.removeAll();
console.log(self.items());
_.each(d.Result, function (item) {
self.items.push(new self.dataModel(item.ID, item.Name, item.Description, item.Thumb, item.Extention));
});
// initializing the paginator
if (self.pageCount() > 0)
self.createPagnation(self.pageCount(), inputObj.pageNum);
//console.log(d.Result);
}
});
}
}
this.cntrl = new _cntrl();
};
And Initialize:
ilia.models.inst = new ilia.models();
$(document).ready(function () {
if (!window.location.hash) {
ilia.models.inst.generateHash();
$(window).on('hashchange', function () {
ilia.models.inst.getData();
});
}
else {
var obj = ilia.models.inst.parseHash();
ilia.models.inst.generateHash(obj);
$(window).on('hashchange', function () {
ilia.models.inst.getData();
});
}
ko.applyBindings(ilia.models.inst, document.getElementById("grid_area"));
//ilia.models.inst.getData();
});
Would perhaps be useful to see the HTML binding here as well.
Are there any console errors? Are you sure the new data received isn't the old data, due to some server-side caching etc?
Anyhow, if not any of those:
Are you using deferred updates? If the array size doesn't change, I've seen KO not being able to track the properties of a nested viewmodel, meaning that if the array size haven't changed then it may very well be that it ignores notifying subscribers. You could solve that with
self.items.removeAll();
ko.tasks.runEarly();
//here's the loop
If the solution above doesn't work, could perhaps observable.valueHasMutated() be of use? https://forums.asp.net/t/2056128.aspx?What+is+the+use+of+valueHasMutated+in+Knockout+js

Knockout JS data-bind doesn't work

When I set the debugger at the applyBidnigns line, I can see the followings:
All properties retain an actual value but I can't map any of them to my rendering. The form is completely empty.
Binding the Model:
$(document)
.ready(function() {
ko.applyBindings(wqsvm, jQuery('#div_wQualitySearch')[0]);
});
function ViewModel() {
var self = this;
self.search = ko.observable(new Search());
self.submit = function() {
if (validator != null && validator.validate('waterqualitysearch')) {
self.search.geolocation = getGeocodeByZip(self.search.geolocation.zip);
window.location.href = '#' + self.search.buildUrl() + self.buildUrl();
}
};
self.buildUrl = function() {
var locHash = encodeQueryData(self);
return locHash;
};
}
function Search() {
var self = this;
self.zip = ko.observable('');
self.distance = ko.observable(25);
self.currentPage = ko.observable(1);
self.pageSize = ko.observable(10);
self.availableRadiuses = ko.observableArray([25, 50, 100, 200, 250]);
self.geolocation = ko.observable(new geoLocation());
self.buildUrl = function () {
var locHash = encodeQueryData(self);
return locHash;
}
self.initFromUrl = function() {
var locHash = window.location.hash.substring(1);
var location = JSON.parse('{"' +
decodeURI(locHash).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}');
self.geolocation(new geoLocation(0, 0, '', location));
if (location.zip !== 'undefined')
self.zip(location.zip);
if (location.distance !== 'undefined')
self.distance(location.distance);
if (location.currentpage !== 'undefined')
self.currentPage(location.currentpage);
},
self.initialize = function() {
if (window.location.hash) {
self.initFromUrl();
}
}
self.initialize();
}
var wqsvm = new ViewModel();
Rendering:
<div class="find-form" id="div_wQualitySearch">
<input type="text" id="txt_QWaterZip" placeholder="ZIP Code" data-bind="value: search.zip">
<select id="ddl_Radius" placeholder="Radius" data-bind="options: search.availableRadiuses, value: search.distance"></select>
<a data-bind="click: submit, attr: {'class': 'button dark-blue'}" id="btn-waterSearch">Search</a>
</div>
I'm posting the answer to help others in the future.
Thanks to #haiim770, I was able to resolve this issue.
There is no need for search to be an observable. You can still try value: search().zip etc, though (that's because Knockout won't automatically unwrap observables that are part of an expression, it will only automatically unwrap direct references to observables [like value: SomeObservable]). Bottom line is, try: self.search = new Search(); instead.
function ViewModel() {
var self = this;
self.search = new Search();
self.submit = function() {
if (validator != null && validator.validate('waterqualitysearch')) {
self.search.geolocation = getGeocodeByZip(self.search.geolocation.zip);
window.location.href = '#' + self.search.buildUrl() + self.buildUrl();
}
};
self.buildUrl = function() {
var locHash = encodeQueryData(self);
return locHash;
};
}

knockoutjs calling function which is defined in sub-viewModel from root-viewModel

I have a single page application with structure:
ViewModel-RootVM (page header, footer, common functions, ...):
SubModelA (page1 - template)
SubModelB (page2 - template)
I would like to call a function fnTest which is defined on page 2 (SubModelB) from page header (ViewModel-RootVM). How can I do that from ViewModel and from HTML? Is this even possible? If so, please help me with an example. I'm a little lost here.
I'm using knockoutjs v2.2.1 and jQuery v1.9.1
ViewModel (you can see the rest of the code in jsfiddle, link below)
var View = function(title, templateName, data) {
var self = this;
self.title = title;
self.templateName = templateName;
self.data = data;
self.myPostProcessingLogic = function(element1, index1, data1) {
console.log('post processing');
};
};
var SubModelA = function(root) {
var self = this;
self.items = ko.observableArray([
{ id: 1, name: "one" },
{ id: 2, name: "two" },
{ id: 3, name: "three" }
]);
};
var SubModelB = function(root) {
var self = this;
self.firstName = ko.observable("Bob");
self.lastName = ko.observable("Smith");
self.fnTest = function() {
alert('calling function from subModelB');
};
self.fnSubModelB = function() {
root.allert('calling function allert from root');
};
};
var ViewModel = function() {
var self = this;
self.views = ko.observableArray([
new View("one", "oneTmpl", new SubModelA(self)),
new View("two", "twoTmpl", new SubModelB(self))
]);
// default open page 'two'
self.selectedView = ko.observable(self.views()[1]);
self.allert = function() {
alert('alert from rootVM');
};
self.allert2 = function() {
// how can I call function 'fnTest' which is defined in SubModelB
self.views()[1].fnTest(); // this is not working
};
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
link to jsfiddle
This is not working because fnTest() is not declared in the "View", but in its "data". It works using:
self.views()[1].data.fnTest()
See here: http://jsfiddle.net/LJBqp/

Knockout js foreach grid not working

Here is the fiddle: http://jsfiddle.net/7RDc3/2096/
The 'Add Service' button doesn't work. I need it to mirror the functionality of the 'Add Hardware' button.
Something is wrong with my code below: You can see it in action on the fiddle above though.
var viewModel = function(hardware, services) {
var self = this;
self.hardwares = ko.observableArray(hardware);
self.services = ko.observableArray(services);
self.addHardware = function() {
self.hardwares.push({
name: "",
price: ""
});
};
self.removeHardware = function(hardware) {
self.hardwares.remove(hardware);
};
self.addService = function() {
self.services.push({
name: "",
price: ""
});
};
self.removeService = function(services) {
self.services.remove(services);
};
self.save = function(form) {
var allModel = [];
ko.utils.arrayForEach(services(), function (service) {
allOrders.push(ko.toJS(service));
});
ko.utils.arrayForEach(hardwares(), function (hardware) {
allOrders.push(ko.toJS(hardware));
});
alert("Could now transmit to server: " + ko.utils.stringifyJson(allOrders));
};
};
var FinalViewModel = new viewModel([]);
ko.applyBindings(FinalViewModel);
You're not passing an argument in for the services parameter when you construct the viewmodel:
var FinalViewModel = new viewModel([], []);
ko.applyBindings(FinalViewModel);
Updated fiddle: http://jsfiddle.net/7RDc3/2097/
You could also augment your constructor to use empty arrays if an argument isn't supplied:
var viewModel = function(hardware, services) {
var self = this;
self.hardwares = ko.observableArray(hardware || []);
self.services = ko.observableArray(services || []);
/* snip */
};

Categories