I have a complex knockout.js object which is effectively an observableArray with another observableArray inside. I have a remove function which asynchronously removes chosen element from the second array. An item is being archived in the database, while one of its observable properties on client side is being set to false making it to disappear from the screen.
A remove button event is created using $root keyword:
Remove
What gives me access to details of the chosen element using "this" keyword. My problem is, that while deleting item from the second array I would like to change something to its parent item in the first array. As I mentioned "this" keyword refers to the child item, is there any way I could access the parent item at the same time as well?
mhu's answer is a antipattern because it creates a dependency between ViewModel and the structure of the View.
Instead do
Remove
Parent Viewmodel
removeActivity: function(activity) {
this.activities.remove(activity);
}
Related
Let's say I have two tables parent and child, related that parent has many children. All properly mapped in Sequelize. I want to add new child to existing parent. In place of addition I don't have parent instance available, however I have his id value.
I know I can always fetch proper parent and set in on newly created child instance but my question is - is it necessary? Is there a way to set just id and create valid association? I want to avoid additional SELECT.
It turned out be pretty simple (although not very ORM).
I just add field parent_id when creating child instance like this:
child.create({
parent_id: parentId,
otherFields....
});
If relation between parent and child is defined (ex.using belongsTo), no additional fields in child model are needed.
I am trying to create recursive grid layout using directives.
There is a parent container(appliedgrids) which contains array of grids inside it.
A grid contains array of columns inside it.
Each column has two properties: span(width of column) and data (data inside column)
Each column data contains either grid again or a widget. If it contains grid then it make recursive call to grid directive.
My problem is when I delete a grid using remove button inside it- it gets removed from appliedgrid container but two way data binding doesn't work as it should. In place of current grid, last grid gets removed from the UI.
Link- http://plnkr.co/edit/DzKIHKvJdLoZiYY3jgDx?p=preview
Steps to reproduce:
1) Click remove button on first grid, you will see that in place of first, second grid gets removed. While json data of appliedgrid contains second grid inside it. So two way binding of angular doesn't work as it supposed to.
I did a little thinking in my previous answer and it turns out it was not correct.
Firstly, do not use track by $index. It makes no sense in your case. track by is an optimisation of ng-repeat to correlate (potentially new) objects in the array that are "business-wise" equal with old objects in the array, so that it re-uses scopes and DOM elements in an effort to minimize DOM manipulation. That is: if you give ng-repeat a hint which new object in the new array is "equal" to an old object in the old array, it will reuse its scope and hopping that the new object is not dramatically different compared to the old one, less $watch callbacks will fire and less DOM updates will occur.
Your actual problem is that you are "statically" or "once-off" binding data with statements like:
$scope.gridIndex = $parse($attrs.gridIndex)($scope);
$scope.gridValues=$parse($attrs.appliedgrid)($scope);
$scope.gridParent=$parse($attrs.appliedgrids)($scope);
The first grid item is indeed removed from the array but ng-repeat does not remove its scope and DOM element because track by $index is used. But still, the new 0-index object (2nd, previously) is used to update the scope (the one created for the 1st object).
You do not see this reflecting to the UI because $scope.gridValues was evaluated in the beginning and is not evaluated again.
So, even though $scope.appliedgrid now points to [{span:12,data:[object]}], $scope.gridValues still points to [{span:6,data:[object]},{span:6,data:[grid2]}].
Removing track by $index solves the problem because ng-repeat tracks objects by reference so each object is associated with the same scope until it is removed from the array.
You can verify it with AngScope, a small Firebug-based scope inspector. You have to open it in a separate tab with "Launch the preview in a separate window" in order for it to work in plunker.
I tried to find a quick fix for it but there was no luck. I guess, you have to re-write it using isolated scopes and real 2-way binding.
Short answer: remove track by $index from ng-repeat.
Long answer: When you are write track by $index you're actually saying to ng-repeat that:
The 1st DOM element will be associated to an object that is tagged as the "0" object
The 2nd DOM element will be associated to an object that is tagged as the "1" object
When you remove the 1st object from the array, angular digests and finds out the following:
The 1st DOM element is still associated to an object tagged as the "0" object
The 2nd DOM element is not associated to any object, so it has to be removed
This is because when ng-repeat runs again, your previously 2nd object which was tagged as "1", is now your 1st and only object which is tagged as "0", since the $index is evaluated again starting from 0.
Angular believes that the 1st DOM element still points to the same object cause it finds it tagged as "0", regardless that it's a completly different object. Under the hood, $scope has the correct model values but ng-repeat skips re-rendering of the DOM element.
It very difficult to write down what really happens. Hope I got it right and helped you.
In a list of items, every item has its' own modelbinder object to bind the models' values.
The problem that I'm having could be reproduced: http://jsfiddle.net/goodafternoon/NmgkY/#base
Only the last element that appears in the list gets bound and thus only the that element responds to the models event listener
this.on("change", function() {
console.log('event');
});
I'm using ironcooks famous Modelbinder module : https://github.com/theironcook/Backbone.ModelBinder
Fixed it.
If you had a similar problem, here is the updated fiddle: http://jsfiddle.net/goodafternoon/XwT2k/1/
The problem was that I was creating a new instance of the ModelBinder object in the class rather than creating it when the model is instantiated, so there was only ever one ModelBinder, which is why I only ever got the binding on one element.
I'm trying to add to/remove from a nested observableArray in KnockoutJS. I have an array several elements, each with an attribute object, type object and an attributeValue array which holds objects. So it's a nested array.
The allAttributes array is observableArray. Then I tried making the attributeValue array observable by making a new ViewModel (attributeValueViewModel) with attributeValues as ko.observableArray([]).
I made two Knockout functions (which don't work) and I'm trying to add/remove values to/from that array. The problem is that the array is nested so I have to access the attributeID through this.attribute.id. self.allAttributes[i].attributeValues[j] should be the object I'm adding/removing... where i=attributeID and j=index of the attribute's value object
Why aren't those functions working?
Here is my fiddle: http://jsfiddle.net/M6Hqj/2/
First off, you're overwriting the observable function in your inner view model, e.g. when you assign obj.attribute = item.attribute;, you're overwriting your initial assignment of self.attribute = ko.observable(data.attribute);. Instead assign the value through the observable, like so:
obj.attribute(item.attribute); //instead of obj.attribute = item.attribute;
This will also make your self.addAttributeValue() function call work since the array is now observable.
Next, in your self.removeAttributeValue() function, the this call actually refers to the specific record inside your attributeValues array, therefore, when you do this.attributeValues.splice(), it can't find your attributeValues object property. So, shift the function into the attributeValueViewModel object and use self instead of this, like so:
//This is inside function attributeValueViewModel(data)
self.removeAttributeValue = function() {
alert(JSON.stringify(this));
self.attributeValues.splice(this.id, 1);
}
To call it, just change your data-bind code to use $parent instead of $root, like so:
<button data-bind="click: $parent.removeAttributeValue">REMOVE</button>
Something like this fiddle here: http://jsfiddle.net/UMB79/
(Note that with these changes you still have to modify your logic to correctly add/remove elements, 'cause it's still buggy)
I'm struggling with Knockout.js Options binding, to an Object.
I'm attempting to create a workflow for the user that allows them to add an Item, edit its properties and then save/cancel to propagate those changes.
I've accomplished this type of task before with jquery. However I'd like to avoid the complicated stack calls that jquery would require. (if possible).
I've created an example:
http://jsfiddle.net/nAE2f/
Whats working in the example:
The Add button, creates a new object.
The Save Button will save it to the array.
The Select Dialog will update with a new Option.
Unfortunately, this is where my progress has halted. While the Select Option is created, it doesn't reflect the underlying objects Name. Also Switching between objects doesn't change the forms properties as I would expect.
I've tried assigning the optionValue to the id, but in that case the Select Options isn't created on save.
The problem with the way you binding member name to item. In your case saved item always has empty name that's why select also display empty text. I fixed this by creating selectedMember property within ViewModel to handle selected member and assigning member name to item on Save.
Check fiddle for example