Knockout JS call function in selectedOptions attribute - javascript

I got the following:
<select data-bind="options: infoLogMessages, selectedOptions: setCurrentInfoLogMessage"></select>
I have a view model which looks like:
var InfoLogFilterVM = function () {
this.propertyChangeSupport = new KnockoutPropertyChangeSupport(this);
this.currentInfoLogMessage = ko.observable();
this.infoLogMessages = ko.observableArray();
}
InfoLogFilterVM.prototype = (function() {
return {
PROP_CURRENTINFOLOGMESSAGE: "currentInfoLogMessage",
PROP_INFOLOGMESSAGES: "infoLogMessages",
addPropertyChangeListener: function(listener) {
this.propertyChangeSupport.addListener(listener);
},
removePropertyChangeListener: function(listener) {
this.propertyChangeSupport.removeListener(listener);
},
setCurrentInfoLogMessage: function(currentInfoLogMessage) {
var oldValue = this.currentInfoLogMessage();
this.currentInfoLogMessage(currentInfoLogMessage);
this.propertyChangeSupport.firePropertyChangeEvent(this.PROP_CURRENTINFOLOGMESSAGE, oldValue, currentInfoLogMessage);
},
getCurrentInfoLogMessage: function() {
return this.currentInfoLogMessage();
},
setInfoLogMessages: function(infoLogMessages) {
var oldValue = this.infoLogMessages();
this.infoLogMessages(infoLogMessages);
this.propertyChangeSupport.firePropertyChangeEvent(this.PROP_INFOLOGMESSAGES, oldValue, infoLogMessages);
},
getInfoLogMessages: function () {
return this.infoLogMessages();
},
}
}());
I wish to call the setCurrentInfoLogMessage function in the view model so I can fire a property change event, which means that I am not interested in calling currentInfoLogMessage property directly like this:
selectedOptions: currentInfoLogMessage
I have tried to change selectedOptions to
value: setCurrentInfoLogMessage
I have also tried to do something like:
selectedOptions: function (_infoLogMessage) { setCurrentInfoLogMessage(_infoLogMessage) }
but nothing works.
Is there actually a way of calling a custom function within the data-bind attribute with selectedOptions or value attribute?

As mentioned in my comment, I'd recommend approaching this from a different way. That said, what you want can be achieved with the use of writable computed observables:
var _logMessageValue = ko.observable();
this.currentInfoLogMessage = ko.pureComputed({
read: function () {
return this._logMessageValue();
},
write: function (value) {
var oldValue = this._logMessageValue();
this._logMessageValue(value);
this.propertyChangeSupport.firePropertyChangeEvent(this.PROP_CURRENTINFOLOGMESSAGE, oldValue, value);
},
owner: this
});

Related

Knockout computed observable dont eval when subscribed

I am creating a ko.computedObservable like so:
var holder = ko.observable();
var makeAjax = function(){
holder('new value');
};
var result = ko.computed({
read: function () {
makeAjax();
return holder();
},
write: function (newValue) {
//not relevant
},
deferEvaluation: true
});
I would like to be able to subscribe to result without causing the read function to execute.
result.subscribe(function(val){
console.log(val);
});
However this call to subscribe causes read to execute. Is there any way to subscribe without causing it to execute?
What version of Knockout are you using? I can't re-create this behavior. Here is a snippet I ran on JSbin with KO 3.0 and running it does not cause the result read function to evaluate until you call getResult()
var vm = function() {
var holder = ko.observable();
var makeAjax = function() {
holder('new value');
};
var result = ko.computed({
read: function() {
console.log('excuting read')
makeAjax();
return holder();
},
write: function(newValue) {
console.log('setting holder to ' + newValue)
//not relevant
holder(newValue)
},
deferEvaluation: true
});
result.subscribe(function(val) {
console.log(val);
});
this.getResult = function() {
return result;
}
this.setResult = function(val) {
result(val);
}
}
var VM = new vm();
ko.applyBindings(VM);
VM.setResult("test");

KnockoutJS - Loading values to dropdown . Selected Value is null always

Here goes my View model, which helps to load the items to drop down. Items are getting loaded but when I inspect the element "value" attribute is empty. How can I get selected value?
$(function () {
tss.Department = function (selectedItem) {
var self = this;
self.id = ko.observable();
self.description = ko.observable();
self.isSelected = ko.computed(function () {
return selectedItem() === self;
});
self.stateHasChanged = ko.observable(false);
};
tss.vm = (function () {
var metadata = {
pageTitle: "My App"
},
selectedDepartment = ko.observable(),
departments = ko.observableArray([]),
sortFunction = function (a, b) {
return a.description().toLowerCase() > b.description().toLowerCase() ? 1 : -1;
},
selectDepartment = function (p) {
selectedDepartment(p);
},
loadDepartments = function () {
tss.departmentDataService.getDepartments(tss.vm.loadDepartmentsCallback);
},
loadDepartmentsCallback = function (json) {
$.each(json, function (i, p) {
departments.push(new tss.Department(selectedDepartment)
.id(p.DepartmentId)
.description(p.Description)
);
});
departments.sort(sortFunction);
};
return {
metadata: metadata,
departments: departments,
selectDepartment: selectDepartment,
loadDepartmentsCallback: loadDepartmentsCallback,
loadDepartments: loadDepartments,
choices: choices,
selectedChoice: selectedChoice
};
})();
tss.vm.loadDepartments();
ko.applyBindings(tss.vm);
});
Here is my HTML
<select data-bind="options:departments, value:selectDepartment,
optionsText: 'description', optionsCaption:'Select a product ...'">
</select>
Also sorting is not happening. departmentDataService used to call external data. which has both "id" and "description"
I also tried setting value as 'Id', but did not work.
You should not use an additional function selectDepartment to pass the value to the observable, but instead directly bind the observable to the value property of the select-box:
<select data-bind="options:departments, value:selectedDepartment, ...
(remember to export the selectedDepartment observable)
The value property is not only used to communicate the current value from view to viewmodel, but also vice versa: to set the selected option. Binding to a function that provides only "write" functionality is therefore not sufficient.
If you need to react to changes of the selected department, you can subscribe to the observable (this is explained in the official docs).

JQuery Knockout ComputedObservable Write not getting called for ObservableArray

I am trying to bind a computed observable which internally uses a observable array.
The "read" method does get called while loading.
But the "write" method does not get called when the values in the table are changed and the focus is moved.
Note that for simple computed observables , which do not wrap a array but a simple string, the "write" method works. However for this scenario it does not work.
I looked on the Knockout api documentation and in online forums but could not find anything about this. Can someone please advice?
Following is the HTML code
<thead>
<tr><th>People Upper Case Name</th></tr>
</thead>
<tbody data-bind="foreach: uppercasepeople">
<tr>
<td ><input type="text" data-bind="value: name"/></td>
</tr>
</tbody>
</table>
Following is the Java Script code
<script type="text/javascript">
var MyViewModel = function () {
var self = this;
var self = this;
//Original observable array is in lower case
self.lowercasepeople = ko.observableArray([
{ name: "bert" },
{ name: "charles" },
{ name: "denise" }
]);
//coputed array is upper case which is data-bound to the ui
this.uppercasepeople = ko.computed({
read: function () {
alert('read does get called :)...yaaaa!!!');
return self.lowercasepeople().map(function (element) {
var o = { name: element.name.toUpperCase() };
return o;
});
},
write: function (value) {
alert('Write does not get called :(... why?????');
//do something with the value
self.lowercasepeople(value.map(function (element) { return element.toLowerCase(); }));
},
owner: self
});
}
ko.applyBindings(new MyViewModel());
I have put the code similar to an example shown on the Knockout API documentation so people easily relate.
The computed observable that you have only deals with the array itself. The write function would only get called if you tried to set the value of uppercasepeople directly.
In this case, you would likely want to use a writeable computed on the person object itself and make the name observable. The writeable computed would then convert the name to upper-case and when written would populate the name observable with the lower-case value.
var MyViewModel = function () {
var self = this;
//Original observable array is in lower case
self.lowercasepeople = ko.observableArray([
{ name: "bert" },
{ name: "charles" },
{ name: "denise" }
]);
self.recententlyChangedValue = ko.observable();
//coputed array is upper case which is data-bound to the ui
this.uppercasepeople = ko.computed({
read: function () {
alert('read does get called :)...yaaaa!!!');
return self.lowercasepeople().map(function (element) {
var o = { name: element.name.toUpperCase() };
return o;
});
},
write: function (value) {
alert('It will be called only when you change the value of uppercasepeople field');
//do something with the value
self.recententlyChangedValue(value);
},
owner: self
});
}

Knockout change models in template

First I'm new to using knockout.
I have bound array1 to my template now I would like change it to use array2 is this possible with knockout?
What I was messing with
var viewModel = function(){
var _this = this;
this.test = [{ name: 'Fruit4'}, {name: 'Vegetables'}];
this.categories = ko.observableArray(this.test);
this.changeItems = function()
{
this.test= [{ name: 'Fruit2'}, {name: 'Vegetables2'}];
categories = ko.observableArray(this.test);
}
};
ko.applyBindings(viewModel());
Create a computed observable that will return one of the two arrays based on your conditions whatever they would be and bind to it. Make sure that the conditions that decide which to choose are also observable so it will update properly.
function ViewModel(data) {
this.array1 = ko.observableArray(data.array1);
this.array2 = ko.observableArray(data.array2);
// change this value to true to use array2
this.chooseArray2 = ko.observable(false);
this.array = ko.computed(function () {
return this.chooseArray2()
? this.array2()
: this.array1();
}, this);
}
<div data-bind="foreach: array">
...
</div>
Of course the logic could be more complex than that. To be more manageable, I would make the condition observable computed as well and create the logic in there. The computed observable that returns the array wouldn't have to change much.
function ViewModel(data) {
this.array1 = ko.observableArray(data.array1);
this.array2 = ko.observableArray(data.array2);
// which to choose depends on a number of conditions
this.someCondition = ko.observable(false);
this.anotherCondition = ko.observable(true);
this.someNumber = ko.observable(132);
this.chooseArray2 = ko.computed(function () {
// some complex logic
if (this.someNumber() < 0) {
return this.someCondition();
}
return this.someCondition() || !this.anotherCondition();
}, this);
this.array = ko.computed(function () {
return this.chooseArray2()
? this.array2()
: this.array1();
}, this);
}

How to return a object method in a jquery event

I am trying to return an object method on the event jQuery.change() of a text field,
here is the code:
var Utente = function(indice){
this.Indice = indice;
this.Dati = new Array();
this.initialize = function() {
this.Dati['stato_civile'] = this.getField('stato_civile').val();
this.onChange('stato_civile',this.checkObbligatorieta);
}
this.getField = function(name) {
return $('#'+indice+name);
}
this.onChange = function(field, func) {
this.getField(field).live('change',function() {
return func.apply();
});
}
this.checkObbligatorieta = function() {
this.Dati['stato_civile'] = this.getField('stato_civile').val();
[...]
}
this.initialize();
}
Using this I get the field "#stato_civile" returns the function this.checkObbligatorieta correctly but it gives me an error:
** this.getField('stato_civile').val() is not a function
I think it's something strictly related with the scope, but I can't figure it out.
That's because you're not invoking func() in the same context as the caller, so this is not bound to the same object.
You can fix the problem by passing this to apply():
this.onChange = function(field, func) {
this.getField(field).live("change", function() {
return func.apply(this);
});
};

Categories