ObservableArray not notifying when item changed - asp.net

I try to bind observableArray to div on my page and everything is ok. This array contains simple JSON objects, not observable, obtained from WebService.
After that, I want to be able to modify those objects in array and would like view to be refreshed with each modification. For example, when checkbox gets clicked I would like to change the flag on my JSON object (this seems to work automatically all right) and at the same time my UI should get updated, which does not happen. Could anyone provide me with the reason (is this because those objects are simple, not observable?) and solution?
var DocumentContentModel = function () {
var self = this;
self.content = ko.observableArray();
self.ElementApprovalChanged = function (element) {
DocumentService.DoSomething(
element.Id,
function (result) {
if (!result) {
var negatedApproved = !element.Approved;
element.Approved = negatedApproved;
}
},
function (error) {
alert(error);
});
return true;
};
};
$(document).ready(function () {
var contentModel = new ContentModel();
DocumentService.GetContent(1,
function (result) {
contentModel.content(JSON.parse(result));
});
ko.applyBindings(contentModel);
});
UI
<div class="ContentContainer">
<div data-bind='foreach: content'>
<div class="ContentElement" data-bind='css: { NotApproved: !Approved} '>
<div class="ContentValue" data-bind='html: Value'></div>
<div class="Approval">
<input type="checkbox" data-bind='checked: Approved, click: $root.ElementApprovalChanged' />
</div>
</div>
</div>
</div>
What is happening is on checkbox click I send request to webservice and if this call returns false I want to reset element's Approved flag. And even whithout that, selecting checkbox should change div class attribute to mark it as NotApproved when needed. But none of this happens.

An observableArray only tracks the array. So if something is added, removed or replaced in the array this will trigger an update to your view.
An observableArray does NOT track the state of individual properties on the items in the array. So if you have an Approved flag on your objects this needs to be an observable for the UI to reflect changes to that property.
So you would have something like:
element.Approved = ko.observable(false);
....
....
if (!result) {
var negatedApproved = !element.Approved();
element.Approved(negatedApproved);
}
(or if you want to be more consise:
element.Approved(!element.Approved());
)

Related

Can a Meteor method call get invoked via Collection.findOne

In my Meteor code. Can I define a method "or a function" on the server and call it on collectionName.findOne({id: 'someId'}).methodName; on the client?
Being new to Meteor, I don't know if this is possible and if so, what would the syntax look like? Thanks
This code is just a brain dump.
//server
Meteor.method({
doWork1: function (args) {
//do work
return something;
}
});
MyCol = new Mongo.Collection('myCol');
MyCol.insert({item: "envelopes", qty : 100, myFunc: doWork1});
//client
Meteor.call(MyCol.findOne({item: 'envelops'}).myFunc;
Edited:
Blaze Sahlzen comments made me think and add the following.
The reasons why I thought to give a mongo collection a try is this:
Stage one: the user fills up a form and click a button, the input values need to be used as arguments for a method which when successfully returns, the form gets modified to show different input fields for the user to fill up again and click the SAME button.
Stage two: same as stage one but with different input fields again.
Stage n: same as stage n-1 but with different input fields again.
I need to store all input values and group them by their stage identifier.
Each method may add/remove different kind of input controls for the next stage.
Since the SAME button will be used thus I don't have different buttons for different methods, so I came up with my original question "brain dump code". You are welcome to reinvent or change it and offer yours. :) Thanks again.
edited 2
A practical example could look like this:
var stageOne = {};
//for each DOM input
stageOne[inputName][i]= [inputValue][i];
myCol.insert({'stageOne': stageOne});
//then an observer for stageOne
But I just can't get my head around how to "link" each stage with the correct method to call without having to use a long if or switch conditional statement.
Alright, if I understand what you mean, I don't think you need observeChanges. The following solution might be a bit extensive, so bear with me.
Firstly you need a session variable to control on the client side which form values need to be shown. You could introduce this variable in your Template.name.onRendered.
Session.set('stage',1);
Then you have your input fields
<form>
<label id="label1">{{label1}}</label>
<input id="field1" type="text"/>
<label id="label1">{{label2}}</label>
<input id="field2" type="text"/>
<label id="label1">{{label3}}</label>
<input id="field3" type="text"/>
<button id="form-submit" type="submit"/>
</form>
I can imagine that you want to switch up the names of their labels to reflect the change in forms as you go to different stages. As a result you can write helpers as such:
'label1': function(){
var myStage = Session.get('stage');
if (myStage == 1){return '<label-title-for-stage-1>';
} else if (myStage == 2){return '<label-title-for-stage-2>';}
} else if .... etc.
}
Any changes to the session variable 'stage' will force the helper to reload, making it ideal to update form titles as you go through your stages.
You can then write an event for the button click event as such:
'submit #form-submit': function(){
var options = {
stage: Session.get('stage'),
values: [
{ value: $('#field1').val(), name:$("#label1").text() },
{ value: $('#field2').val(), name:$("#label1").text() },
{ value: $('#field3').val(), name:$("#label3").text() }]
}
Meteor.call('storeValues', options, function(error, result) {
if (error) {
console.log(error);
}
Session.set('stage',result);
});
}
This will essentially combine the filled fields into one object and call upon a server method, waiting for a callback from the server that tells the client which stage to move to.
Now, on the server side you can insert the values for this particular user in your collection and see if a particular stage has filled up and return whether the client can move on to the next stage.
Meteor.methods({
'storeValues': function(options){
for (var i = 0; i < options.values.length; i++){
myCol.insert({
value:options.values[i].value,
name:options.values[i].name,
stage: options.stage
});
}
if (options.values.length > 'amount-per-stage'){
return options.stage + 1;
} else {
return options.stage;
}
}
});
This way you store the data that gets entered in each form, while moving up one stage each time if all fields have been entered.
What you could do is use observeChanges:
var query = MyCol.find({item: 'envelops'});
var handle = query.observeChanges({
added: function () {
somethingHappened();
},
changed: function () {
somethingHappened();
}
});
var somethingHappened = function(){
// do something
}
Query contains your collection, and the handle function automatically checks whether any changes are being made to that collection, triggering the somethingHappened function if there are.
Inside somethingHappened you can place the behaviour that you would normally place in your method.
You can use observeChanges both client side and server side, but in this case you only need it on the server side.

KnockoutJS - How to reassign URL when checkbox is checked vs. unchecked

I want to assign an URL in a function everytime a checkbox is checked. But I would also like the URL to be reassigned its initial value when the checkBox is unchecked. I was able to create the code below from the following thread, but the URL is not reassigned when I uncheck the box. Sorry I am new to KnockoutJS and to JavaScript in general.
HTML
<input type="checkbox" name="myCheckBox" data-bind="checked:isChecked, click: setUrl">Search Supplier<br>
JS
searchShippingCodesUrl = '/Parteners/Search';
...
...
ischecked: ko.observable(false),
setUrl: function () {
searchShippingCodesUrl = '/Suppliers/Search';
return true;
},
Thank you for your time.
Only use the click binding on a checkbox if you need to differentiate whether the checkbox changed state by click vs. by internal setting. The checked binding captures the state of the view for you, so you can then work with it in your viewmodel. To take action when it changes, you subscribe to the observable (I borrow the variables here from dfperry's example):
ischecked.subscribe(function (newValue) {
searchShippingCodesUrl = newValue ? supplierSearch : partnerSearch;
});
You need to check against isChecked in your setUrl function to get that toggle effect:
var partnerSearch = '/Partners/Search',
supplierSearch = '/Suppliers/Search';
searchShippingCodesUrl = partnerSearch;
...
ischecked: ko.observable(false),
setUrl: function () {
searchShippingCodesUrl = (ischecked() ? supplierSearch : partnerSearch);
return true;
}

Multiple view model bindings, shared error dialog with bindings

I have two view models on a single page that correspond to two different sets of content on different tabs.
I'm binding each view model to its corresponding root element in the markup. However, I want to have a generic section of markup that either model can use (a generic modal dialog for errors on a master page in my scenario) that is not contained within any of the bound root elements.
How should I handle this? Should I nest the multiple models inside of one master view model or something and not bind to specific elements? I'm fairly new to knockout so I'm open to all suggestions.
This shows what I'm trying to accomplish minus the duplicated modal sections. Fiddle: http://jsfiddle.net/z3wGr/3/
JS:
ko.bindingHandlers.errorModal = {
update: function (element, valueAccessor) {
var valueAccessorValue = valueAccessor();
var value = ko.utils.unwrapObservable(valueAccessorValue);
if (value.length > 0) {
// $(element).modal('show');
console.log("would be showing modal here");
} else {
// $(element).modal('hide');
}
}
};
var sectionOneViewModel = {
sectionOne: ko.observable("section one"),
errors: ko.observableArray([]),
aSectionOneMethod: function (self) {
// make AJAX call here
// error returns from server
// push error into array triggering modal because of custom binding handler
self.errors.push({
errorText: "something went wrong."
})
}
}
var sectionTwoViewModel = {
sectionTwo: ko.observable("section two"),
errors: ko.observableArray([]),
aSectionTwoMethod: function (self) {
// make AJAX call here
// error returns from server
// push error into array triggering modal because of custom binding handler
self.errors.push({
errorText: "something went wrong."
})
}
}
ko.applyBindings(sectionOneViewModel, document.getElementById('section-one'));
ko.applyBindings(sectionTwoViewModel, document.getElementById('section-two'));
HTML:
<div id="section-one">
<span data-bind="text: sectionOne"></span>
<br />
<input type="button" value="call section one function" data-bind="click: aSectionOneMethod" />
<!-- This is a generic modal that all of my viewmodels use to show errors, I don't want to include it in every binding section -->
<div id="generic-error-modal" data-bind="errorModal: errors">
<p>I'm a modal dialog that would actually only display when an error is returned from the server after an AJAX call.</p>
<ul data-bind="foreach: errors">
<li data-bind="text: errorText"></li>
</ul>
</div>
</div>
<div id="section-two">
<span data-bind="text: sectionTwo"></span>
<br />
<input type="button" value="call section two function" data-bind="click: aSectionTwoMethod" />
<!-- This is a generic modal that all of my viewmodels use to show errors, I don't want to include it in every binding section -->
<div id="generic-error-modal" data-bind="errorModal: errors">
<p>I'm a modal dialog that would actually only display when an error is returned from the server after an AJAX call.</p>
<ul data-bind="foreach: errors">
<li data-bind="text: errorText"></li>
</ul>
</div>
</div>
**Edit: Working example based off the answer and comments below: http://jsfiddle.net/z3wGr/6/
I'd recommend to have one viewModel which nests the other two models. You may need to pass in the errors to your nested models so they can interact with the errors.
I'd also recommend to use functions for your models and new the objects.
Here is a cutdown version. I haven't tested it so there may be some minor errors:
var ViewModel = function() {
var self = this;
self.errors = new Errors();
self.sectionOne = new SectionOneViewModel(self.errors);
self.sectionTwo = new SectionTwoViewModel(self.errors);
};
var SectionOneViewModel = function(errors) {
var self = this;
self.sectionOne = ko.observable("section one");
self.errors = errors;
//this is how to add an error
self.errors.add("This is an error");
};
var SectionTwoViewModel = function(errors) {
var self = this;
self.sectionTwo = ko.observable("section two");
self.errors = errors;
};
var Errors = function() {
ver self = this;
self.errors = ko.observableArray();
self.add = function (errorText) {
self.errors.push(new Error(errorText));
};
self.clear = function() {
self.errors.removeAll();
};
};
var Error = function(errorText) {
var self = this;
self.errorText = errorText;
};

How do you update item value(s) in ko.observableArray and rebind?

I have a ko.observableArray that when the page gets initialized 1 item is added. I then use a and a data-bind="foreach items" to create a div for each item in the ko.observableArray. I have a button and textbox on the page that when you add text to the input and click the button a new item gets pushed on to the ko.observableArray. This works fine I can add a new items with each button click. The items in the ko.observableArray have a price associated with them and the price changes. I want to update the price while still being able to add new items to the ko.observableArray. The price and item name are also ko.observable.
self.items= ko.observableArray(ko.utils.arrayMap(items, function(item) {
return { name: ko.observable(item.name), price: ko.observable(item.price) };
How to I update the underlying item values (price) and not recreate the ko.observable array? Do I have to loop through each item in the ko.observable array? The data is coming from a JSON call. BTW I am new to Knockout.js.
Here is my attempt at a JSFiddle but I could not get it fully working. Adding an item works fine but when I update if I have a different amount of item..like less items the ones not getting updated are destroyed. Anyway around this? I do not want to fetch data that does not have any changes in it.
Do something like this instead
Javascript
var itemObject = function(data){
var self = this;
//I recommend using the mapping plugin
//ko.mapping.fromJS(data, {}, self);
//If you use the mapping plugin, you don't have to hand bind each property
this.Id = ko.observable(data.Id);
.... etc ....
};
var baseViewModel = function(){
var self = this;
this.Items = ko.observableArray();
this.Setup = function(items){
//using underscore.js to map the items.
//This turns each item into an "itemObject", which you can
//then manipulate and have all changes shown on the screen, even
//inside the array.
self.Items(_.map(items, function(item){
return new itemObject(item);
}));
};
};
$(function(){
var myApp = new baseViewModel();
myApp.Setup(items);
ko.applyBindings(myApp);
});
html
<div data-bind="foreach: Items">
<div data-bind="text: Id"></div>
<!-- other binding things here -->
</div>

knockout.js - data-bind auto update after function call

I have a case where I databind to a date field inside model in a list:
function Model(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
}
<div id="fieldOnPage" data-bind="text: formatDate(myDateField())"></div>
Then, in a modal, I display the same date field so it can be edited:
<div id="fieldInModal" data-bind="text: formatDate(myDateField())"></div>
However, since I'm calling the formatDate function does its work on the unwrapped observable, I'm unable to see the changes get written real-time back onto the main page when I edit the value in the modal.
Another caveat is that I using the ko.mapping plugin so I don't necessarily have a specific ko.computed field on myDateField. Is this possible to do with an external function like this? If not, how would I do it using the ko.computed if I had to specifically override the myDateField binding?
You could do something like
function Model(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
this.formattedDate = ko.computed(function () {
return formatDate(ko.utils.unwrapObservable(self.myDateField));
});
}
The bind to the formatted Date
<div id="fieldInModal" data-bind="text: formattedDate"></div>
Hope this helps.

Resources