Reputation: 53
So my use case is:
cols = [{field="product.productId"},{field="product.productPrice"}];
data = {products:[{product:{productId:1,productPrice:10}, {product:{productId:2, productPrice:15}}]}
What I would like to do is:
<div ng-repeat="product in data.products">
<div ng-repeat="col in cols">
<input type="text" ng-model="product[col.field]"></>
</div>
</div>
Now if col.field was just 'someField' and not 'some.deep.field' this would work. Because the field has many elements, the correct way to do ng-model would be "product[some][deep][field]" if I didn't want to be generic and allow my data and columns to change. I've tried this approach and it worked for a non-generic use case.
What I have tried to make it generic:
Recompiling my 'input' element. This creates the perfect HTML E.G it has ng-model="product['some']['deep']['field']" on it, but in no way is the field bound whatsoever. Perhaps I am compiling with the wrong scope here? I have tried addingAttribute ng-init="hello='Hey'" ng-model="hello" at this point and it worked and bound properly... so I feel I am missing something regarding scope here.
compile: function (templateElement) {
templateElement[0].removeAttribute('recursive-model');
templateElement[0].removeAttribute('recursive-model-accessor');
return {
pre: function (scope, element, attrs) {
function convertDotToMultiBracketNotation(dotNote) {
var ret = [];
var arrayOfDots = dotNote.split('.');
for (i = 0; i < arrayOfDots.length; i++) {
ret.push("['" + arrayOfDots[i] + "']");
}
return ret.join('');
}
if (attrs.recursiveModel && attrs.recursiveModelAccessor) {
scope[scope.recursiveModel] = scope.ngModel;
element[0].setAttribute('ng-model', scope.recursiveModel + convertDotToMultiBracketNotation(scope.recursiveModelAccessor));
var res = $compile(element[0])(scope);
console.info('new compiled element:', res);
return res;
}
}
}
}
Messing with the NgModelController to format and parse. In this case I have put the entire 'row' object into ng-model and then used formatter/parser to only mess with the 1 field I was interested in. This works until you clear the field. At that point it seems to wipe out modelCtrl.$modelValue completely. In other words - my console.log says:
Setting field to val 'Text' on row [object]
Setting field to val 'Tex' on row [object]
Setting field to val 'Te' on row [object]
Setting field to val 'T' on row [object]
Setting field to val '' on row [object]
Setting field to val 'A' on row undefined
link: function (scope, element, attrs, ctrls) {
if(ctrls[2] && scope.recursiveModelAccessor){
var modelCtrl = ctrls[2];
modelCtrl.$formatters.push(function (inputValue) {
function getValue(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i < l; i++) {
object = object[explodedString[i]];
}
return object;
};
function getValueRecursive (row, field) {
if (field instanceof Array) {
var ret = [];
for (var i = 0; i < col.field.length; i++) {
ret.push(getValue(row, field[i]));
}
return ret.join('/');
} else {
return getValue(row, field);
}
};
return getValueRecursive(modelCtrl.$modelValue, scope.recursiveModelAccessor);
});
modelCtrl.$parsers.push(function (inputValue) {
function setValueRecursive (row, field, newValue) {
if (field instanceof Array) {
var firstField = field.shift();
if(field.length==1){
field = field[0];
}
setValueRecursive(row[firstField], field, newValue);
} else {
console.log("Setting "+field+" to val:"+newValue+" on row:"+row);
row[field]=newValue;
}
};
setValueRecursive(modelCtrl.$modelValue, scope.recursiveModelAccessor.split('.'), modelCtrl.$viewValue);
return modelCtrl.$modelValue;
});
Upvotes: 0
Views: 550
Reputation: 53
Long story short (8 solid hours wasted on this) - don't put ng-model="something" on your object, if you then plan to re-compile after modifying the ng-model attribute.
A working directive for rebinding the ngModel (Just don't have the attribute already on your object!)
<div ng-repeat="product in data.products">
<div ng-repeat="col in cols">
<input type="text" recursive-model="product" recursive-model-accessor="some.deep.field"></input>
</div>
</div>
Just make sure you don't have ng-model="something".
Of course - a 100% perfect solution would throw exception if ng-model attribute was present :)
module.directive('rebindModel', ['$compile','$parse',function($compile,$parse){
return {
restrict:'A',
compile: function (templateElement) {
templateElement[0].removeAttribute('recursive-model');
templateElement[0].removeAttribute('recursive-model-accessor');
return {
post: function (scope, element, attrs) {
function convertDotToMultiBracketNotation(dotNote) {
var ret = [];
var arrayOfDots = dotNote.split('.');
for (i = 0; i < arrayOfDots.length; i++) {
ret.push("['" + arrayOfDots[i] + "']");
}
return ret.join('');
}
if (attrs.recursiveModel && attrs.recursiveModelAccessor) {
var parsedModelAccessor = $parse(attrs.recursiveModelAccessor)
var modelAccessor = parsedModelAccessor(scope);
element[0].setAttribute('ng-model', attrs.recursiveModel + convertDotToMultiBracketNotation(modelAccessor));
var res = $compile(element[0])(scope);
return res;
}
}
}
},
}
}]);
Upvotes: 1