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.
Related
I’m learning javascript and trying to write code that sorts a list, removing elements if they meet certain criteria.
I found this snippet that seems promising but don't have a clue how it works so I can adapt it to my needs:
list = document.getElementById("raffles-list").children; // Get a list of all open raffles on page
list = [].filter.call(list, function(j) {
if (j.getAttribute("style") === "") {
return true;
} else {
return false;
}
});
Can you guys help me learn by explaining what this code block does?
It's getting all the children of the "raffles-list" element, then returning a filtered list of those that contain an empty "style" attribute.
The first line is pretty self-evident - it just retrieves the children from the element with id "raffles-list".
The second line is a little more complicated; it's taking advantage of two things: that [], which is an empty array, is really just an object with various methods/properties on it, and that the logic on the right hand side of the equals sign needs to be evaluated before "list" gets the new value.
Uses a blank array in order to call the "filter" method
Tells the filter to use list as the array to filter, and uses function(j) to do the filtering, where j is the item in the list being tested
If the item has a style attribute that is empty, i.e. has no style applied, it returns true.
Edit:
As per OP comment, [].filter is a prototype, so essentially an object which has various properties just like everything else. In this case filter is a method - see here. Normally you just specify an anonymous function/method that does the testing, however the author here has used the .call in order to specify an arbitrary object to do the testing on. It appears this is already built into the standard filter method, so I don't know why they did it this way.
Array like objects are some of javascript objects which are similar to arrays but with differences for example they don't implement array prototypes. If you want to achieve benefits of array over them (for example like question filter children of an element )you can do it this way:
Array.prototype.functionName.call(arrayLikeObject, [arg1, [arg2 ...]]);
Here in question array like is html element collection; and it takes items without any styling.
list is assigned a collection of elements that are children of the raffles-list element
list is then reassigned by filtering its elements as follows
an empty array is filtered by calling it with the parameter list and a callback function. The formal parameters for call are this (which is the list) and optionally further objects (in this case a callback function)
The callback function receives a formal parameter j and is called for each element
If the element's value for the style attribute is empty the element is retained in the array. Otherwise it is discarded.
At the end list should contain all elements that don't have a value for its style attribute
I am creating a JavaScript class that represents a data-bound table, using jQuery to handle DOM manipulation. The class has a $table.$body.$rows property where I am keeping a collection of the table's jQuery-wrapped rows to avoid performing a $table.$body.children('tr') call whenever a row is added or removed. When a row enters or leaves edit mode, I need to be able to add and remove objects from that $table.$body.$rows property, which I accomplish with jQuery's .add() and .not() methods.
These methods are inadequate, however, when the row being edited is not at the very end of the table, since the .add() method adds the new item to the end of the internal collection maintained in the jQuery instance. In order to make sure the $table.$body.$rows collection is correctly ordered, I need to be able to insert the new item at a specified index within the jQuery collection. Does such a method already exist, or am I going to have to write it myself?
I could just let the HTMLTableSectionElement.rows property keep track of the rows for me and simply wrap a particular row in an jQuery object when necessary, but this seems inefficient. This raises a secondary question: how expensive is a call like .children('tr') anyway, and would I be better off simply reassigning $table.$body.$rows each time a row is added or removed?
Technically, jQuery do add splice() to jQuery.fn, but it isn't documented. They almost removed it from the public interface recently, but decided against it;
We want to avoid the appearance that using these methods [jQuery.fn.{push,sort,splice}] is encouraged, but we don't want to remove them either.
... make of that what you will.
You could use Array.prototype.splice.call, e.g.:
Array.prototype.splice.call($table.$body.$rows, 3, 0, newItem);
... and I can't see how that'd be wrong.
Although in all honesty, I think you're trying to solve a problem that doesn't exist.
I could just let the HTMLTableSectionElement.rows property keep track of the rows for me and simply wrap a particular row in an jQuery object when necessary
... I'd do this. Calling .children('tr'), and updating $table.$body.$rows when rows are modified is not going to kill your application.
You could the splice function on the native array object.
arr.splice(index, 0, item); will insert item into arr at the specified index.
I'm using inheritance to implmenet a reuseable forms app.
Expression parameter can be dependent on one or more other numeric/constant ot other expression parameters (the relation is many to many so heirarchy wont work here). Once all my dependent parameters evaluated them self (calcualted if expression or validated if user input) I can now calculate my self.
I'm looking for a way to subscribe to my siblings but the problem is that during creation the sibling does not neccasarily exists yet.
I set up a small example on this Fiddle.
To see this not working in action lets change the values from the console
d.parameters()[0].value(10) // expecting parameter C to sum up to 20.
d.parameters()[2].value() // Nothing (this should also update ont he screen).
d.parameters()[1].value(20) // expecting parameter C to sum up to 30.
d.parameters()[2].value() // Still nothing.
So Ive tried sevral things but i think i'm barking up the wrong tree here.
Fetching the parent using ko.dataFor(document.body) or
fetching the object from the DOM using the same function.
I've played around with deferEvaluation as you can see in the code
to force KO to first create the parameters and then apply the
binding. seems to do nothing.
Tried creating a observableArray with the links to the params and a
computed based on the array but the problom remain.
Appreciate any help here.
Bonus question, without losing focus from the main one, somehing I cant explain going on on line 73.
Does anybody have a minute to take a look at a puzzling problem I'm having with Knockout JS and binding select lists?
The person's favourite color should be selected in the list by calling value: favColorId in the select list, rather than with the function-based call of value: favColorId(), something very strange is going on here, I've never used () in the past, it's also causing some other weird issues where it won't recall the value into the span (so changing the selected item does nothing).. I have tried recreating a simple sample as best I can demonstrating the issue.
http://jsfiddle.net/goneale/ph8Jw/
I have included my mapDictToArray() function but it simply converts a
javascript object into a key-value JS array. I wouldn't think that is
contributing to the problem.
Actually, that was part of the problem. The function returns a JavaScript array, not an observable array and therefor can't be used properly by Knockout. I've made the following changes to your code:
// The "mapDictToArray" makes a normal JS array, not a ko.observableArray();
// You can't simply "merge" a JS array with an observableArray(); you'll need
// some extra functionality for that: ko.utils.arrayPushAll()
// viewModel.colors(mapDictToArray(dict));
ko.utils.arrayPushAll(viewModel.colors(), mapDictToArray(dict));
// Apply the bindings *after* you've added the contents to the "colors" observable, in order to
// get the correct selected value
ko.applyBindings(viewModel);
That should do the trick (with the correct HTML without the () )!
JSFiddle.
UPDATE
I thought about my solution, but something wasn't correct. The only thing that was correct, was the part that you need to apply the bindings after you've added the contents of the colors observable. This is your fiddle, with that part moved down.
This works in your case, but you'll need to use the arrayPushAll method when there is already data inside the observableArray. This method merges, while you overwrite it when not using it (example with data inside the observable).
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);
}