Kendo UI - Property Changed MVVM - javascript

I seem to be having issues databinding 'calculated' fields with Kendo UI.
I am attempting to do data-binding with several what I will call 'calculated' fields. I have a grid, several buttons, filters and some sorting on a page that are all using the same datasource, an observable array called 'allItems'. allItems is populated via a service call and sorted, manipulated and otherwise changed while the user is on the page via buttons.
There are several navigational buttons and several div's that are populated based on information in the previous item, current item and next item in accordance to the current filters and sorts applied. Those buttons contain information in them that is extracted from the previous, current and next items as they related to the allItems list (ie the objects are actually held in the allItems array and are in fact observable objects).
so in the viewmodel object I have something like this (please excuse short handing):
var viewmodel = kendo.observable({
var allItems = observablearray[]
var currentIndex = 1; //or other default value
var changeCurrentItem = function(){
var self = this;
//do some stuff
//stuff might include modification to allItems
self.set("currentIndex", someNewValue);
}
var previousItem = function(){
return self.get('allItems')[currentIndex - 1];
}
var currentItem = function(){
return self.get('allItems')[currentIndex];
}
var nextItem = function(){
return self.get('allItems')[currentIndex + 1];
});
return viewmodel;
The buttons and other info boxes are bound to previous,current and next Items. But this does not appear to work. I've had to make previous, current and nextItems copies of what lives in the allItems array and update those 3 objects at the same time. Not that big of a deal, but I just would like to, you know, not store copies of objects if I don't have to. I was hoping there might be a NotifyPropertyChanged("MyProperty") similiar to C#/Xaml that I missed while going through the API. This sort of functionality will be most useful to me for future tasks I have on my list due to some of the complexities of our calculated fields and the need to reducing memory consumption as devices get smaller.
Thanks for any help,
~David

Whenever a property of the view model changes, the change event is triggered.
To make this work in calculated fields, you need to access the relevant fields using ObservableObject.get(). See this section in the documentation.

2 possible problems.
1) How do you get items into allitems?
2) You should also .get("currentIndex") since that is the value that you update in changeCurrentItem
var currentItem = function(){
return self.get('allItems')[self.get('currentIndex')];
}
That should cause viewModel to fire its change event for thecurrentItem calculated field whenever allItems or currentIndex changes.
If all else fails, you can manually fire the change event by doing:
viewmodel.trigger("change", { field: "currentItem" });
viewmodel.trigger("change", { field: "previousItem" });
viewmodel.trigger("change", { field: "nextItem" });
which would be similar to calling NotifyPropertyChanged("currentItem") in XAML.

Related

knockout js, add new item to an array

So this follows on from my previous question:
knockout js, add additional elements to an array
Basically I have an app where a user fills in certain data, clicks next and this is added to an array. However, what I'd like to do is add some items into the array before the user even begins using the app (these items I get from a database). The idea being that at the start they can view each item in the array and then choose and an item and edit this item. I've got a feeling I'm missing something blindingly obvious but I cannot seem to figure it out
Knockout observable arrays have equivalent functions to native JavaScript arrays. See: http://knockoutjs.com/documentation/observableArrays.html
So you need just to use arr.pop(item) or arr.push(item).
In case you need to replace all items and want to avoid multiple events to raise, use observableArray.valueWillMutate() and valueHasMutated() functions. See sample where I do swap the entire array:
ko.observableArray.fn.replaceWith = function (valuesToPush) {
// NOTE: base on - ko.observableArray.fn.pushAll
var underlyingArray = this();
var oldItemcount = underlyingArray.length;
this.valueWillMutate();
// adding new items to obs. array
ko.utils.arrayPushAll(underlyingArray, valuesToPush);
// removing old items (using KO observablearray fnc.)
if (oldItemcount > 0)
this.removeAll(underlyingArray.slice(0, oldItemcount));
this.valueHasMutated();
return this; //optional
};

IN CQ, how to set value of all the items in Panel to blank

In ExtJS panel I need to set value of all items (e.g. textfield, pathfield) to blank. I don't want to set value of each individual item to blank but of whole panel in one go.
I am able to get list of items
function getAllChildren (panel) {
/*Get children of passed panel or an empty array if it doesn't have thems.*/
var children = panel.items ? panel.items.items : [];
/*For each child get their children and concatenate to result.*/
CQ.Ext.each(children, function (child) {
children = children.concat(getAllChildren(child));
});
return children;
}
but how to set to blank for whole panel? Please suggest what need to be done in this case.
Actually, it's not possible to do it with one liner - all at the same time. What your method returns is purely an array of objects. In fact if such syntax existed, it would iterate over all fields anyway.
Though clearing all fields, having the method you've proposed is very trivial to do. Just iterate over them all and call reset method. Mind some (especially custom) widgets might not handle it.
var fields = getAllChildren(panel);
CQ.Ext.each(fields, function(field) {
if (child.reset) {
child.reset();
}
});
You've got similar loop in your getAllChildren code - you might reset field at the same place.
The method is defined in Field type which is usually a supertype of each dialog widget. You can read more here.

How to implement an efficient filter using knockoutjs

I have a list of items(about 100 items), which are displayed in a list.
There is also a provision to apply a couple of filters.
I'm using knockoutJS for data binding. The current implementation is like so :
var vmModel = function() {
var self = this;
self.items = ko.observableArray([])
self.filter1 = ko.observable()
self.filter2 = ko.observable()
self.filter3 = ko.observable()
self.filteredItems = ko.computed(function(){
var allItems = self.items()
if (self.filter1()) {
allItems = ko.utils.arrayFilter(allitems,
function(item) {return "some condition";})}
...
Other filters
...
}) // end of computed function
})// end of vmModel
In My HTML
<ul data-bind="foreach: filteredItems">
<li data-bind="text: some data"></li>
</ul>
Whenever one of the filters change the whole filteredItems is recomputed. So when the user sets filter1 and then filter2, filter1 is applied for 100 items. Then, again filter1 and filter2 is applied for the 100 items.
I wanted know if there is more efficient way to do apply the filters ? i.e re-use previously filtered result ?
KO just knows you're using multiple Observable values in your computed value, but it doesn't know why. I think you can't specify to apply specific part of your code for an observable change event.
The only way i see to do that (but i think it's ugly), is to store your value into a private variable (not observable, juste a standard JS Var). When the computed is called, you compare the new variable with the latest. If there are different, you apply the filter and store de new value on the private variable.
You may want to use Knockout Projections
It provides efficient map and filter functions:
The key point of this library is that these transformations are done efficiently. Specifically, your callback function that performs the mapping is only called when strictly necessary (usually, that's only for newly-added items). When you add new items to the source data, we don't need to re-map the existing ones. When you reorder the source data, the output order is correspondingly changed without remapping anything.
This efficiency might not matter much if you're just squaring numbers,
but when you are mapping complex nested graphs of custom objects, it
can be important to perform each mapping update with the minumum of
work.

Grid value not updating on changing dropdown in Knockout JS

Please find the updated fiddle in the comment section.
I have provided the HTML code and the ViewModel. On changing the value in drop down I want the pip value in grid to be updated. The pip value is getting calculated for new drop down value but the return is not able to bind the value to grid.
In order to make seats change in the UI on dropdownAValue change you need to declare empty observable array first:
self.seats = ko.observableArray();
And update it in the subscribe function instead of creating new observableArray every time:
self.seats([new PipCalculation(self.rates[0], self.rates[0].price, self.rates, self.newLot[0],currentlotvalue) ]);
See updated fiddle
well my answer will be addition to what Max Brodin has given .
We can further normalize you code by doing something like this
View model:
self.dropdownAValue = ko.observable(0.1);
self.seats = ko.observableArray([]);
var newItems = ko.utils.arrayMap(self.rates,function(item){
return new PipCalculation(item, item.price, self.rates, self.newLot[0], self.dropdownAValue)
});
self.seats(newItems);
//computed code
self.formattedPrice2 = ko.computed(function() {
//alert("hi");
var cur = self.rate().pair;
//var price = self.rate().price;
//alert(self.myQuote());
var price = self.myQuote();
var pip = 1;
var lot1 =currentlotvalue().lotSize;
Working fiddle here
Things to do :
There is no need to additionally write a subscribe for subscribing drop-down changes . All you need to do is send right DD value instance further in your case .
There is a performance hit as your are filling observablearray inside DD subscribe i.e every-time dropdown changes . That can be avoided by doing like i mentioned above.
Rather pushing into Observable array push into a normal array and the later using that array (performance improved) - max mentioned in his comments
There is a other way to do this like (i done previously like this)
self.seats([]);
var mutatedArray = self.seats();
ko.utils.arrayForEach(self.rates,function(item){
mutatedArray.push(new PipCalculation(item, item.price, self.rates, self.newLot[0],self.dropdownAValue)); // push to the array(normal array)
});
self.seats.valueHasMutated();
For more on this do refer my answer here .

Angular, comparing 2 objects and replacing (possibly concatinating?)

I have 2 multi-select lists in angular that are dependent on on another. One is the parent list and one is the child list. When landing, there is nothing in the child list and once you select (one or many) items from the parent multi-select, it will then populate the child list with the children of the selected item(s) from the parent list. This works great - just for reference I have a $watch on the parent model, so when it changes (the user selects something in the parent list - it will then call an $http and fetch the results for the children list
like so -
$scope.$watch('selectedResources', function (newValue) {
angular.forEach($scope.selectedResources, function(data){
$scope.generalIDArray.push(data.id);
});
//id's = $scope.imageIDArray
//place new data in $scope.imageOptionsSub
$http({
method: 'POST',
url: '/listSubCategories',
data: {
page: 0,
ids: $scope.generalIDArray
}
})
.success(function(data){
//empty options
$scope.resourceOptionsSub = [];
//push new data in
angular.forEach(data.subCategories, function(index) {
$scope.resourceOptionsSub.push(index);
});
});
So - I push all the id's in and send them to get the results back.
However - here is my problem. I realized after trying this out that I don't want to completely replace resourceOptionsSub with all the new results, because the user has interacted with the child results - they have a .checked value on them that means the user has selected them, this will be wiped out and refreshed each time I make a new call because it empties out the scope and replaces it, even if it is the same items.
What I would like to happen is to kind of compare if there are items that already exist in resourceOptionsSub that are coming in with the call, and sort of keep the original resourceOptionsSub and compare it to the new one coming in, and maybe pull off the items that don't exist any more?
I'm thinking I should somehow compare the 2 objects 1 being the original, 2 being the new - and if 2 has anything 1 does not have, then pull it out of 1, because then I could keep the items that are the same untouched. If anyone could point me in the right direction here as to where to look I would much appreciate it, as I am at a bit of a loss as to how to handle this. Thanks for reading!
angular.extend will work but you will need start with an empty object like so:
var updatedObj = angular.extend({}, obj1, obj2);

Categories