Using $watch to update data, ng-repeat not reflecting changes - javascript

I have a list of fooditems in $scope.raw and I want to show this data in columns so I'm changing the structure a bit. I do this in the sortStuff() function and store the updated data in $scope.allfood. There's a $watch that calls sortStuff() every time anything changes in $scope.raw (I'm using drag and drop to change the food category):
$scope.$watch('raw', function(){
$scope.allfood = $scope.sortStuff();
console.log($scope.allfood);
}, true);
This is what happens when food is dragged around:
receive:function(event, ui) {
var issueScope = angular.element(ui.item).scope();
scope.$apply(function() {
var recp = _.find(scope.raw, function(lineitem){
return lineitem.name === issueScope.receipe.name;
})
recp.cat = scope.col.name;
})
$(ui.item).remove(); // remove DOM
}
Basically, I search for the right object inside $scope.raw and change cat to new category for the food. I also delete the dom element because I'm counting on ng-repeat to refresh the view. This seems to work fine: console.log inside $watch shows that the object is being moved to the right category and the data looks what it should look like. However, visually, ng-repeat doesn't reflect the data.
Here's the jsfiddle.
Dragging an item from B to C works fine. Dragging one from A to B, makes two items from B disappear... the results are very inconsistent and I have no idea what is happening.
Any ideas what is going wrong? Or maybe any suggestions for a better way to do this?

The problem with your code is that the ng-repeat directive adds the property $$hashKey to every element in the list. This property is used by the directive to associate DOM elements with array elements.
Because you are passing the elements by reference, the ng-repeat directive writes the $$hashKey property directly into the objects of your $scope.raw array. A simple workaround is to copy the objects before inserting them into the $scope.allfood object.
_.each($scope.raw, function(recp){
recp = _.clone(recp);
switch(recp.cat){
...
}
});
Now the ng-repeat updates the objects of $scope.allfood, while the objects of $scope.raw remain untouched.
See the updated fiddle:
http://jsfiddle.net/b8Fa7/5/

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.

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);

Kendo UI - Property Changed MVVM

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.

Categories