Knockout : find out which observable triggerred computed - javascript

I have an object with multiple observables. Is there a way in a computed to know which observable changes, therefore which observable fired the computed?
Thank you in advance
Matthew

Without details of exactly what you are trying to achieve, I'll post this in the hope it might help.
A simple way to track changes is to use the .subscribe method on an observable you want to track. Each time the observable gets updated, this method will fire.
self.myValue = ko.observable('initial value');
self.myValue.subscribe(function (item) {
alert('myValue has changed to: ' + item);
});
The item passed in to the subscribe function is optional, so you can use the new value if required.
Here's a simple example of it in use with a computed:
Sample JSFiddle
JS:
var viewModel = function () {
var self = this;
self.firstName = ko.observable('Mod');
self.lastName = ko.observable('dinu');
self.valueChanged = ko.observable('');
self.fullName = ko.computed(function () {
var val = '';
if (self.valueChanged() !== '') {
val = ' (' + self.valueChanged() + ' Changed)';
}
return self.firstName() + ' ' + self.lastName() + val;
});
self.firstName.subscribe(function () {
self.valueChanged('First Name');
});
self.lastName.subscribe(function () {
self.valueChanged('Last Name');
});
};
ko.applyBindings(new viewModel());
HTML:
<div>
<label for="fname">First Name:</label>
<input id="fname" data-bind="value: firstName" />
</div>
<div>
<label for="lname">Last Name:</label>
<input id="lname" data-bind="value: lastName" />
</div>
<hr />
<div>Hello <span data-bind="text: fullName"></span></div>
<hr />
<div>Value Changed: <span data-bind="text: valueChanged"></span></div>

Related

Sending only the updated object from ko.observableArray

How can I send only the updated model from an observable Array instead of sending the entire array?
var student = function (){
this.studentId=0;
this.firstName=ko.obserable();
this.lastName=ko.obserable();
}
var course= function (){
this.courseId=0;
this.students=ko.obserableArray([]);
this.Name=ko.obserable();
}
Now I want to get only that particular student from course whose info is updated. Assuming that when we add a new class we can dynamically add new students to it on the go. Supposing that you have to validate the previous student before adding a new one. When I get that particular student I want to send that student info back to the server.
Thanks.
If I understood your task right, you could use "arrayChange" event type to get exact changed (added/removed) items:
sourceArray = ko.observableArray();
sourceArray.subscribe(function (changes) {
changes.forEach(function(arrayChange) {
if(arrayChange.status === 'added') {
// some code on add
} else if(arrayChange.status === 'deleted') {
// some code on delete
}
});
}, null, "arrayChange");
If you want to get list of students which have been modified, you can provide a flag to identify if an object has been modified in student object. Use .subscribe to modify that flag whenever a value is updated. Then use ko.computed or ko.pureComputed to get that list.
Also it supposes to be observable.
var student = function (id, firstName, lastName) {
var self = this;
self.hasChanged = ko.observable(false);
var modified = function(){
self.hasChanged(true);
};
self.studentId = ko.observable(id);
self.firstName = ko.observable(firstName);
self.firstName.subscribe(modified);
self.lastName = ko.observable(lastName);
self.lastName.subscribe(modified);
}
var course= function (){
var self = this;
self.courseId = 0;
self.students = ko.observableArray([new student(1, "Cristiano", "Ronaldo"), new student(2, "Lionel", "Messi")]);
self.modifiedStudent = ko.computed(function(){
return ko.utils.arrayFilter(self.students(), function(student) {
return student.hasChanged();
});
}, self);
self.Name = ko.observable("Programming 101");
}
$(document).ready(function () {
var myViewModel = new course();
ko.applyBindings(myViewModel);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
List of all students:
<div data-bind="foreach: students">
<div>
<span data-bind="text: studentId"></span>
<input type="text" data-bind="value: firstName" />
<input type="text" data-bind="value: lastName" />
</div>
</div>
<br/>
List of students which has been modified:
<div data-bind="foreach: modifiedStudent">
<div>
<span data-bind="text: studentId"></span>
<input type="text" data-bind="value: firstName" readonly />
<input type="text" data-bind="value: lastName" readonly />
</div>
</div>

How to correctly format currency with Knockoutjs?

I need to format number as localized currency with Knockoutjs. But I have issues with it. I created a jsfiddle for it. If somebody could be so nice, and check it (not fix it), please write it to me, what should I fix in it.
Example is here:
ko.extenders.formattedMoney = function (target, arg) {
var result = ko.computed({
read: function () {
var targetValue = target().toString().replace(/[^0-9-.]/g, '');
return parseInt(targetValue).toLocaleString('hu-HU') + " HUF";
},
write: target
});
result.raw = target;
return result;
};
function MyViewModel() {
var self = this;
self.minValue = ko.observable(20000);
self.curValue = ko.observable(40000).extend({ formattedMoney: 0 });
};
ko.applyBindings(new MyViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div>
MinValue: <input type="text" data-bind="value: minValue" />
</div>
<div>
CurValue: <input type="text" data-bind="value: curValue" />
</div>
Thank you very much!

AngularJs - facing an issue when using ng-init

I am facing an issue with using ng-init and assign it model inside my html.
The following code works fine. The following code is for Add/Edit functionality. For example, when row is is opened in Edit mode than it persist existing value and shows it in textbox.
<div>
<div ng-if="title == 'Add Student'">
<input type="text" name="name"placeholder="Student Name" data-ng-model="registration.Student.FirstName" maxlength="50">
</div>
<div ng-if="title == 'Edit Student'">
<input type="text" name="name"placeholder="Student Name" data-ng-model="student.Student.FirstName" maxlength="50">
</div>
</div>
However, the following code which is short version of above code does not work. I mean when the row is opened in edit mode it shows text field but does not show existing value (first name) in it. Why?
<div ng-init="model = (title == 'Add Student' ? registration.Student : student.Student)">
<input type="text" name="name" placeholder="Student Name" data-ng-model="model.FirstName" maxlength="50">
</div>
Please suggest whether ng-init can't be used in this way or some issue in my code?
thanks
Controller
var currentState = $state.current.name;
if if (currentState == "Add")
{
$scope.registration = {
Student: {
FirstName: '',
}
var _init = function () {
}
$scope.title = " Add Student";
}
else
{
$scope.student= {};
$scope.student= response[0];
var _init = function () {
$scope.title = " Edit Student";
}
}
You are ng-init block is wrong currently it is returning true or false, you are messing with brackets.
Markup
<div ng-init="model = (title == 'Add Student') ? registration.Student : student.Student">
<input type="text" name="name" placeholder="Student Name" data-ng-model="model.FirstName" maxlength="50">
</div>
Update
In your current case you ng-init is getting executed while element rendered on the DOM, at that instance on time registration.Student & student.Student doesn't have any value. Evaluation of ng-init setting null object to the model student. I'd suggest you do set model value from the controller logic that would be more safer.
Code
var currentState = $state.current.name;
if (currentState == "Add")
{
$scope.registration = {
Student: {
FirstName: '',
}
var _init = function () {
}
$scope.title = " Add Student";
}
else
{
$scope.student= {};
$scope.student= response[0];
var _init = function () {
$scope.title = " Edit Student";
}
//shifted logic in controller
$scope.model = (title == 'Add Student' ? registration.Student : student.Student);
}
Markup
<div>
<input type="text" name="name" placeholder="Student Name"
data-ng-model="model.FirstName" maxlength="50"/>
</div>
Other way you could add one more flag like loadedData which will says that ajax response has been fetched & registration.Student & student.Student values are available in the scope.
Markup
<div ng-if="loadedData">
<input type="text" name="name" placeholder="Student Name" data-ng-model="model.FirstName" maxlength="50">
</div>
Code
var currentState = $state.current.name;
if (currentState == "Add")
{
$scope.registration = {
Student: {
FirstName: '',
}
var _init = function () {
}
$scope.title = " Add Student";
}
else
{
$scope.student= {};
$scope.student= response[0];
var _init = function () {
$scope.title = " Edit Student";
}
//set flag
$scope.loadedData = true;
}

Knockout js add a new list

I am trying to create a form with a list which adds a list dynamically!
However I just can't get my add function working and it keep complains that my list is not defined.
Here is my viewmodel in jave script
function DeliveryCategoryViewModel() {
var self = this;
self.DeliveryCategory = ko.observable(new DeliveryCategory(1, "new Name", [new DeliveryOption("first"), new DeliveryOption("second")]));
self.addDeliveryOptions = function () {
self.DeliveryOptions.push(new DeliveryOption("new Option"));
}
self.removeDeliveryOptions = function (option) {
self.DeliveryCategory.remove(option);
}
}
and these are actual model which holds the data
function DeliveryCategory(id, name, option) {
this.Id = id;
this.Name = name;
this.DeliveryOptions = ko.observableArray(option);
}
function DeliveryOption(name) {
this.Id = "2";
this.Name = name;
}
$(document).ready(function () {
ko.applyBindings(new DeliveryCategoryViewModel());
});
This is my form
<div id="newDelCategory">
<input data-bind="value:DeliveryCategory().Id" type="hidden" />
<label class="newDelCategoryLabel">New delivery category name: </label>
<input type="text" data-bind="value:DeliveryCategory().Name" class="newDelCategoryText" id="newDelCategoryText" placeholder="Delivery category name" />
</div>
<div id="addOption">
<a id="addOptionLink" href="#" data-bind="click:addDeliveryOptions" class="link">+Add delivery option</a>
</div>
<div id="deliveryOptionContent" data-bind="foreach: DeliveryCategory().DeliveryOptions">
<div class="newDelOption">
<input data-bind="value:$data.Id" type="hidden" />
<div class="divider"></div>
<label class="newDelOptionLabel">New delivery option name: </label>
<input type="text" data-bind="value:$data.Name" class="categoryName" id="newDelOptionText" placeholder="Delivery option name" />
<a id="removeOptionLink" data-bind="click:$parent.removeDeliveryOptions" class="link removeOptionLink">Remove</a>
</div>
</div>
When I try to click click:addDeliveryOptions, it return on Firebug console.
TypeError: self.DeliveryCategory.DeliveryOptions is undefined
self.DeliveryCategory.DeliveryOptions.push(new DeliveryOption("new Option"));
I tried different things such as click:$root.addDeliveryOptions, and also tried to add addDeliveryOptions function as a prototype (e.g. DeliveryCategory.prototype.addDeliveryOptions(...)) and still getting Typeerror...
Is it because DeliveryOptions is not initialised? I expected that it would be when DeliveryCategory is initialised from the DeliveryCategoryViewModel() ...
Any idea? Thanks!
Small Oversight. Easy Fix.
You were calling push and remove off the observable array from the view model but it does not exists as a direct member of the view model.
This is because you never add the observable array directly to this view model. You use a constructor to create an object to observe with DeliveryCategory. One of the properties on that object is an observable array DeliveryOptions. To get access to the observable array from this scope, you have to evaluate DeliveryCategory to get access to it's property DeliveryOptions before you run any array methods. So, instead of this:
self.addDeliveryOptions = function () {
self.DeliveryOptions.push(new DeliveryOption("new Option"));
}
self.removeDeliveryOptions = function (option) {
self.DeliveryCategory.remove(option);
}
The Solution:
self.addDeliveryOptions = function() {
self.DeliveryCategory().DeliveryOptions.push(new DeliveryOption("new Option"));
}
self.removeDeliveryOptions = function(option) {
self.DeliveryCategory().DeliveryOptions.remove(option);
}
See the snippet below
function DeliveryCategoryViewModel() {
var self = this;
self.DeliveryCategory = ko.observable(new DeliveryCategory(1, "new Name", [new DeliveryOption("first"), new DeliveryOption("second")]));
self.addDeliveryOptions = function() {
self.DeliveryCategory().DeliveryOptions.push(new DeliveryOption("new Option"));
}
self.removeDeliveryOptions = function(option) {
self.DeliveryCategory().DeliveryOptions.remove(option);
}
}
function DeliveryCategory(id, name, option) {
this.Id = id;
this.Name = name;
this.DeliveryOptions = ko.observableArray(option);
}
function DeliveryOption(name) {
this.Id = "2";
this.Name = name;
}
$(document).ready(function() {
ko.applyBindings(new DeliveryCategoryViewModel());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="newDelCategory">
<input data-bind="value:DeliveryCategory().Id" type="hidden" />
<label class="newDelCategoryLabel">New delivery category name:</label>
<input type="text" data-bind="value:DeliveryCategory().Name" class="newDelCategoryText" id="newDelCategoryText" placeholder="Delivery category name" />
</div>
<div id="addOption">
<a id="addOptionLink" href="#" data-bind="click:addDeliveryOptions" class="link">+Add delivery option</a>
</div>
<div id="deliveryOptionContent" data-bind="foreach: DeliveryCategory().DeliveryOptions">
<div class="newDelOption">
<input data-bind="value:$data.Id" type="hidden" />
<div class="divider"></div>
<label class="newDelOptionLabel">New delivery option name:</label>
<input type="text" data-bind="value:$data.Name" class="categoryName" id="newDelOptionText" placeholder="Delivery option name" />
Remove
</div>
</div>

read multiple inputs with backbone.js

I'm playing around with the todos.js backbone.js demo.
In the demo, they have one input text box where they take data from in the initialize function like this:
<input id="new-todo" type="text" placeholder="Todo item...">
initialize: function () {
this.input = this.$("#new-todo");
My question is, would it be possible to take the data from 3 input textboxes instead of just one?
I could try this, but this doesn't seem to scale very well:
<input id="new-todo1" type="text" placeholder="Todo item...">
<input id="new-todo2" type="text" placeholder="Todo item...">
<input id="new-todo3" type="text" placeholder="Todo item...">
initialize: function () {
this.input = this.$("#new-todo1");
this.input =+ this.$("#new-todo2");
this.input =+ this.$("#new-todo3");
Is there a better way?
Thanks
I assume you want to take the values from multiple inputs, and put them as title in a todo item. I suggest storing references to inputs in initialize with:
initialize: function () {
this.input = this.$("#new-todo");
...
}
And the createOnEntermethod should change into this:
createOnEnter: function(e) {
//concatenate the values from all inputs
var val = "";
this.input.each(function() {
val += ($(this).val());
});
if (e.keyCode != 13) return;
if (!val) return;
Todos.create({title: val});
//reset all the input elements
this.input.each(function() {
$(this).val('');
});
}
The input elements should all have the same id - "new-todo".
I am not very experienced with Backbone.js but you could use jQuery each to loop through all of the inputs and get their value.
if you have:
<input class="todo" type="text" placeholder="Todo item...">
<input class="todo" type="text" placeholder="Todo item...">
<input class="todo" type="text" placeholder="Todo item...">
then
initialize: function () {
this.$inputs = this.$(".todos");
will cache those inputs (not get the value as you said).
then
this.$inputs.each(function() {
console.log($(this).val());
});
will print their values, or you could put their values in an array like so:
var values = this.$inputs.map(function() {
return $(this).val();
});
then you can make a string out of those values with
values.join(' ');
or you can use Underscore's reduce for extra style points:
var string = _(this.$inputs).reduce(function(memo, el) {
return memo + ' ' + $(el).html();
}, '');

Categories

Resources