Knockout JS data-bind doesn't work - javascript

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;
};
}

Related

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

Jquery Not Synchronized with the DOM

I found a new problem where when i manipulate the dom using jquery. The dom is not updated.
Here we go.
var PostCard = function($root) {
this.$root = $root;
this.stuff_id = this.$root.data('stuff_id');
this.id = this.$root.data('id');
this.is_owner = this.$root.data('is_owner');
this.$propose_button = null;
this.$favorite_button = null;
this.init();
this.initEvents();
};
PostCard.prototype.init = function () {
this.$propose_button = this.$root.find("." + PostCard.prototype.CSS_CLASSES.PROPOSE_BUTTON);
this.$proposal_box_modal = this.$root.find("." + PostCard.prototype.CSS_CLASSES.PROPOSAL_MODAL);
this.$proposal_box = this.$root.find("." + PostCard.prototype.CSS_CLASSES.PROPOSAL_BOX);
this.$favorite_button = this.$root.find(".post-card-button-favorite");
this.$total_favorite = this.$root.find('.post-card-total-favorite');
this.$image_view_editor = this.$root.find("#" + this.id + "-image-view");
this.image_view_editor = new ImageViewEditor(this.$image_view_editor);
this.proposal_box = new HomeProposalBox(this.$proposal_box);
};
PostCard.prototype.initEvents = function() {
var self =this;
$(document).on("click", "#" + this.id,function(e) {
if(e.target && $(e.target).hasClass('post-card-button-propose') ){
if(self.is_owner ) {
return false;
}
if(CommonLibrary.isGuest()) {
return false;
}
self.$proposal_box_modal.modal("show").load($(this).attr("value"));
} else if(e.target && $(e.target).hasClass('post-card-button-favorite')) {
self.clickFavoriteButton_();
}
});
PostCard.prototype.clickFavoriteButton_ = function() {
this.markFavorite();
};
PostCard.prototype.markFavorite = function() {
this.$favorite_button.addClass('post-card-button-red');
};
When Favorite button is clicked, the clickFavoriteButton() will be trigerred, and markfavorite method will be fired next.
The problem is although this.$favorite_button is updated (the class is added to the variable), the dom in the page is not updated
this only happens to dynamically loaded element, and does not happen to element that has been around since the page is loaded.
The PostCard is created in PostList class
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
var PostList = function($root) {
this.$root = $root;
this.stuff_ids = '';
this.id = $root.data('id');
this.location = $root.data('location');
this.query = '';
this.post_cards = [];
this.$post_list_area = null;
this.permit_retrieve_post = true;
this.init();
this.initEvents();
};
PostList.prototype.SCROLL_VALUE = 95;
PostList.prototype.init = function() {
$.each($(".post-card"), function(index, value) {
this.post_cards.push(new PostCard($(value)));
if(this.stuff_ids === '' ) {
this.stuff_ids += $(value).data('stuff_id');
} else {
this.stuff_ids += "," + $(value).data('stuff_id');
}
}.bind(this));
this.$post_list_area = this.$root.find('.post-list-area');
};
PostList.prototype.initEvents = function() {
$(window).scroll({self:this}, this.retrievePostWhenScroll_);
};
PostList.prototype.retrievePostWhenScroll_ = function(e) {
var self = e.data.self;
var scrollPercentage =
((document.documentElement.scrollTop + document.body.scrollTop) /
(document.documentElement.scrollHeight - document.documentElement.clientHeight) * 100);
if(scrollPercentage > PostList.prototype.SCROLL_VALUE) {
if(self.permit_retrieve_post) {
self.permit_retrieve_post = false;
$.ajax({
url: $("#base-url").val() + "/site/get-more-posts",
type: 'post',
data: {'ids' : self.stuff_ids, query: self.query, location: self.location},
success: function(data) {
var parsedData = JSON.parse(data);
if(parsedData['status'] === 1) {
self.$post_list_area.append(parsedData['view']);
$(parsedData['view']).filter('.post-card').each(function(index, value) {
self.post_cards.push(new PostCard($(value)));
self.stuff_ids += "," + $(value).data('stuff_id');
});
}
self.permit_retrieve_post = true;
},
error : function(data) {
self.permit_retrieve_post = true;
}
});
}
}
};
When you scroll the page, the retrieveWhenScroll method will be fired and PostCard will be dynamically added

click event not completely working, using Knockout.js binding and viewmodel

hi this is my code snippet:
self.arrow = function () {
alert("button clicked");
var active_el = $(this);
$('.movie-listing-header').each(function () {
if ($(this).get(0) === active_el.parent().get(0)) {
if ($(this).hasClass('active')) {
$(this).siblings('.showtimes').hide();
} else {
$(this).siblings('.showtimes').show();
}
$(this).toggleClass('active');
} else {
$(this).removeClass('active');
$(this).siblings('.showtimes').hide();
}
});
}
it is part of my viewmodel, and the alert("button clicked") works, but the rest of the code does not ... Here is the button click code:
<a class="icon arrow" data-bind="click: $parent.arrow"></a>
my question is HOW do I get the code after the alert to function.
this is the entire js, containing the the View Model
function TheatreViewModel(theatre) {
var self = this,
initialData = theatre || Regal.userPrimaryTheatre || {},
theatreServiceParams = {
tmsId: initialData.TmsId,
date: initialData.selectedDate || new Date()
};
self.TheatreName = initialData.TheatreName || '';
self.PhoneNumber = initialData.PhoneNumber || '';
self.selectedTheatreTms = initialData.TmsId;
self.theatre = ko.observable();
self.isLoading = ko.observable(false);
self.selectedDate = ko.observable(initialData.selectedDate || new Date());
self.filterFormats = [];
self.selectedFormat = ko.observable(Regal.allFormatsFilterItem);
self.filterFormats.push(Regal.allFormatsFilterItem);
if (Regal.movieFormats) {
var filtered = _.where(Regal.movieFormats, {
Filterable: true
});
_.forEach(filtered, function(f) {
f.enabled = ko.observable(false);
self.filterFormats.push(f);
});
}
self.addressText = ko.computed(function() {
var theat = ko.unwrap(self.theatre);
var addie;
if (!theat || theat && !theat.Address1) {
addie = initialData;
} else {
addie = theat;
}
return addie.Address1 + ', ' + addie.City + ' ' + addie.State + ' ' + addie.PostalCode;
});
self.mapEmbedUrl = ko.computed(function() {
var a = self.addressText();
return 'http://maps.google.com/maps?q=' + encodeURI(a);
});
self.movies = ko.computed(function() {
var thea = self.theatre(),
mov = ko.unwrap((thea || {}).Movies),
format = self.selectedFormat();
if (format && format !== Regal.allFormatsFilterItem) {
return _.filter(mov, function(m) {
return _.contains(_.pluck(m.formats(), 'Id'), format.Id);
});
}
return mov;
});
self.getPerformances = function() {
self.isLoading(true);
Regal.loadTheatre(self.selectedDate(), self.selectedTheatreTms,
function(data) {
self.isLoading(false);
if (data) {
var allFmts = _.uniq(_.flatten(_.map(ko.unwrap(data.Movies), function(mov) {
return mov.formats();
})));
_.forEach(allFmts, function(fmt) {
var filt = _.findWhere(self.filterFormats, {
Id: fmt.Id
});
if (filt) {
filt.enabled(true);
}
});
self.theatre(data);
}
});
};
self.changeFormat = function(format) {
console.log(format);
self.selectedFormat(format);
self.movies();
};
self.selectedDate.subscribe(function(newVal) {
self.getPerformances();
});
self.getPerformances();
self.arrow = function () {
alert("button clicked");
var active_el = $(this);
$('.movie-listing-header').each(function () {
if ($(this).get(0) === active_el.parent().get(0)) {
if ($(this).hasClass('active')) {
$(this).siblings('.showtimes').hide();
} else {
$(this).siblings('.showtimes').show();
}
$(this).toggleClass('active');
} else {
$(this).removeClass('active');
$(this).siblings('.showtimes').hide();
}
});
}
}
I have a feeling that var active_el = $(this) is not what you're expecting. I'm not running the code but I believe that this will be self in this context. However, I'd like to recommend a more fundamental MVVM change. Rather than including all this jQuery code for updating the UI, I would recommend setting properties on your view model instead. Here's a simplified example:
HTML
<section data-bind="foreach: movies">
<article>
<div data-bind="if: displayShowtimes">
<!-- ... show times UI -->
</div>
</article>
</section>
JavaScript
self.arrow = function (movie) {
movie.isActive(!movie.isActive());
}
This will make your JavaScript much less brittle to changes in the HTML.

Strange data binding issue

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());

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