Strange knockout js select option binding issue - javascript

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

Related

Detect array mutation

I have made a simple proof-of-concept Polymer 1.0 app that demonstrates my problem: JSBin.
In my problem, I am using array mutation methods to alter the array, which contains the list of shopping items.
However, this doesn't seem to work as intended. I do get a change in dom-repeat and when printing the length of the array. But I do not get the change event when I am printing the array itself nor when I wrap it in a function.
In short, why does this work?
<p>Number of items: [[list.length]]</p>
And why does this not work?
<p>Items inline: [[list]]</p>
<p>Observe function : [[_observe(list)]]</p>
Also, when I uncomment the following line (in the JSBin), things seem to work as indened. But I don't like it since it's a bit hackish.
app.notifyPath('list', app.list.slice());
I have stumbled upon the slice() fix by reading this issue: https://github.com/Polymer/polymer/issues/2068
EDIT
So, after reviewing the comments, the answer to the question "Is this by design" is YES. The array itself doesn't change (since it's only a reference), but it's property do change. That's why the slice() forces the reload since it creates a shallow copy.
However, one could argue whether this is OK. Yes, the variable list does not change per se. But putting [[list]] in the HTML code actually triggers toString(). And result of that function has changed.
I guess I'm stuck with piggybacking the length property for now...
As alluded to in the comments the notifyPath and slice calls are creating a shallow copy of the array and assigning a different reference back to the list variable - triggering an update of the binding. Without maintaining a separate (watchable) variable or messing around with object references, the only other workaround I can think of would be to piggy back on the list.length property instead of the list itself and pass that through some kind of "formatting" function. e.g.
<p>Items inline: [[format(list.length)]]</p>
app.format = function(){
return app.list.toString();
};
» Fiddle
As pointed out by #zb you could expand on this and make the function reusable with any array by passing the relevant variable as an argument too:
<p>Items inline: [[format(list, list.length)]]</p>
app.format = function(list){
return list.toString();
};
» Fiddle

How do you add an object to a jQuery collection at a specified index?

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.

Angularjs: buggy two way data binding

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.

Subscribing to changes in siblings with Knockout.js

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.

Control references in jQuery

function eegetdropdownvalue_str(ctl){return ctl.selectedIndex>=0&&ctl[ctl.selectedIndex]?ctl[ctl.selectedIndex].value:''}
The above function is called with
co.p1A10=eegetdropdownvalue_str(document.formc.p1A10);
I want to switch the call over to jQuery to drop the document.form reference however doing this
co.p1A10=eegetdropdownvalue_str($('p1A10'));
Does not reference the control correctly - How should I do this?
There's two things wrong with your code.
First, $('p1A10') references nothing.
jQuery selectors work almost identically (if not completely identically) to the way css works.
So, just ask yourself how you would reference the object(s) in question in CSS and you're half way there.
I'm assuming that p1A10 is the name or id of an object. Since we're using CSS/jQuery syntax, this should be an id, although you can select by other attributes such as $("select[name='p1A10']") .
To reference an object by ID we use the # character (again, just like in CSS). So we can select your node via $('#p1A10').
The second problem is that your function is expecting a DOM object not a jQuery object. To keep your code intact, we need to say $('#p1A10')[0] where 0 is the first element within the collection of jQuery elements.
I've provided two examples to explain this a little better. One uses your existing infrastructure and one replaces it.
http://jsfiddle.net/TD6Uu/5/
Hope it helps.
Given a form with id formc and a select with name p1A10 you could e.g. use:
o.p1A10 = eegetdropdownvalue_str($('#formc select[name="p1A10"]').get(0));
If this doesn't do it, please provide use with the exact HTML structure

Categories