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/
Related
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 -->
i faced ploblem solving write repitation code but i don't want that.
so i think how to solve this problem as beautiful programing aspect.
i thought some solution.
1. parent object add event bind to router
for example, user when visit example.com/#aaa , example.com/#bbb
i load router hashtag than handle that event.
if user visit #aaa then load template aaa.
but i find many reference but i don't know how to implement that solution which is parent and child object communication at backbone.js
2. parent object add function
i thought solution that visiting url stored target in router
for example, if i visit example.com/#aaa then router is written target = aaa
then parent object loads function and render ansync ajax load template and toss result child object.
but likewise i haven't ablity implement that is.
who will give hint to me?
many backbone.js reference is poorly and difficulty.
router.js (my source)
var app = app || {};
(function() {
'use strict';
var views = app.view = app.view || {};
app.Router = Backbone.Router.extend({
initialize: function(){
this.bind("all", this.change)
console.log(this.change);
},
routes: {
'situation': 'situationRoute',
'video': 'videoRoute',
'culture': 'cultureRoute',
//와일드카드 디폴트 라우터는 맨 마지막에 삽입.
'*home': 'homeRoute'
},
_bindRoutes: function() {
if (!this.routes) return;
this.routes = _.result(this, 'routes');
var route, routes = _.keys(this.routes);
while ((route = routes.pop()) != null) {
this.route(route, this.routes[route]);
}
},
initialize: function() {
// create the layout once here
this.layout = new views.Application({
el: 'body',
});
},
homeRoute: function() {
var view = new views.Home();
var target = 'Home';
this.layout.renderSubpage(target);
this.layout.setContent(view);
},
situationRoute: function() {
var view = new views.Situation();
var target = 'Situation';
this.layout.setContent(view);
},
videoRoute: function() {
var view = new views.Video();
var target = 'Video';
this.layout.setContent(view);
},
cultureRoute: function(){
var view = new views.Culture();
var target = 'Culture';
this.layout.setContent(view);
}
});
var router = new app.Router();
Backbone.history.start();
})();
view.js (my source)
var app = app || {};
(function() {
'use strict';
//views linitalize
var views = app.view = app.view || {};
views.Application = Backbone.View.extend({
initialize: function() {
this.$content = this.$('#container');
//this.$loading = this.$('#loading');
},
setContent: function(view, target) {
var content = this.content;
this.renderSubpage();
if (content) content.remove();
content = this.content = view;
this.$content.html(content.render().el);
},
showSpinner: function() {
this.$loading.show();
},
hideSpinner: function() {
this.$loading.hide();
},
renderSubpage: function(target){
var target = target;
var templateName = target;
var tmpl_dir = '../assets/static';
var tmpl_url = tmpl_dir + '/' + templateName + '.html';
var tmpl_string = '';
$.ajax({
url: tmpl_url,
method: 'GET',
async: false,
dataType : html,
success: function (data) {
tmpl_string = data;
}
});
var template = _.template(tmpl_string);
this.$el.html(template);
return this;
}
});
views.Home = Backbone.View.extend({
render: function(html) {
return this;
//how to get return result? in parent object?
}
});
views.Stuation = Backbone.View.extend({
render: function() {
var template = _.template("<strong><% print('Hello ' + page); %></strong>");
var pageTxt = {page: "About"};
var html = template(pageTxt);
this.$el.html(html);
return this;
}
});
views.Video = Backbone.View.extend({
render: function() {
var template = _.template("<strong><% print('Hello ' + page); %></strong>");
var pageTxt = {page: "Contact"};
var html = template(pageTxt);
this.$el.html(html);
return this;
}
});
views.Culture = Backbone.View.extend({
render: function() {
var template = _.template("<strong><% print('Hello ' + page); %></strong>");
var pageTxt = {page: "Contact"};
var html = template(pageTxt);
this.$el.html(html);
return this;
}
});
})();
renderSubpage: function(target) is originally under source.
views.Home = Backbone.View.extend({
render: function(html) {
var templateName = home;
var tmpl_dir = '../assets/static';
var tmpl_url = tmpl_dir + '/' + templateName + '.html';
var tmpl_string = '';
$.ajax({
url: tmpl_url,
method: 'GET',
async: false,
dataType : html,
success: function (data) {
tmpl_string = data;
}
});
var template = _.template(tmpl_string);
this.$el.html(template);
return this;
}
});
i don't want repitation code.
views.Home = templateName = 'home';
~~
views.Situation = tamplateName = 'Situation';
~~
How to fix it?
var app = app || {};
(function() {
'use strict';
//views linitalize
var views = app.view = app.view || {};
views.Application = Backbone.View.extend({
initialize: function() {
this.$content = this.$('#container');
//this.$loading = this.$('#loading');
},
setContent: function(view, target) {
var content = this.content;
var subUrl = this.target;
if (content) content.remove();
//if (content || target) content.remove()target.remove();
content = this.content = view;
subUrl = this.target = target;
var templateName = subUrl;
var tmpl_dir = '../assets/static';
var tmpl_url = tmpl_dir + '/' + templateName + '.html';
var tmpl_string = '';
$.ajax({
url: tmpl_url,
method: 'GET',
async: false,
dataType : 'html',
success: function (data) {
tmpl_string = data;
}
});
console.log(tmpl_string);
this.$content.html(content.render(tmpl_string).el);
},
showSpinner: function() {
this.$loading.show();
},
hideSpinner: function() {
this.$loading.hide();
}
});
views.Home = Backbone.View.extend({
render: function(templateName) {
var template = _.template(templateName);
this.$el.html(template);
return this;
}
});
i solve that ploblem use function parameter using ajax call subUrl and toss child object then child object renders this template string.
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;
};
}
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());
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 */
};