I bind an array of values to a number of input elements. There are directives on the input elements, that set $parsers, $formatters and $validators. The controller should not care about the pipeline from viewValue to modelValue.
The view:
<ul>
<li ng-repeat="value in main.values">
<input ng-model="value.v" twice /> {{value.v}}
</li>
</ul>
Controller / Directive:
function MainController($scope) {
this.values = [
{v: 1}, {v: 2}, {v: 3}
];
}
function twice() {
return {
require: 'ngModel',
link: function(scope, elem, attr, ngModel) {
ngModel.$formatters.push(function(x) { return 2 * x });
ngModel.$parsers.push(function(x) { return 0.5 * x });
}
}
}
I want to implement a copy & paste feature. The values in all the input elements should be overwritten from clipboard data. Therefore the controller implements a function which parses the clipboard data and sets the value for each input element. The values from clipboard are view values. Since the controller has no idea how to calculate model values from these view values, it has to use the '$parsers' pipeline from 'ngModelController'. How can I implement MainController.paste() to set the view value on each input element?
I currently solved the actual problem (see comments) with a directive on the list element. http://plnkr.co/edit/9c2q2X?p=preview
function pasteValues() {
return {
link: function(scope, elem, attr, ngModel) {
elem.on('paste', function($event) {
var data = $event.clipboardData || window.clipboardData;
var text = data.getData('Text');
var values = text.split(' ');
var inputs = elem.find('input');
if (values.length === inputs.length) {
for(var i = 0, e = values.length; i != e; ++i) {
var input = inputs[i];
var ngModel = angular.element(input).controller('ngModel');
ngModel.$setViewValue(values[i]);
input.value = values[i];
}
$event.preventDefault();
}
})
}
}
}
I found two possible solutions (while writing the question:-)). http://plnkr.co/edit/ZNfYKTvSf6coGsohRlot?p=preview
The first is not really the angular way, because the controller has to know about DOM structure. But it is straighforward and doesn't need additional bindings and watches. To set the view value it uses the angular.element.controller() method to retrieve the ngModelController for each input element.
function MainController($scope) {
this.paste = function() {
var value = this.pasteValue;
var inputs = angular.element(document.getElementById('values')).find('input');
angular.forEach(inputs, function(input) {
var ngModel = angular.element(input).controller('ngModel');
ngModel.$setViewValue(value);
input.value = value;
});
};
}
The second solution is more the angular way and uses an addtional directive that whatches on paste data.
function setView() {
return {
require: 'ngModel',
scope: {
setView : '='
},
link: function(scope, elem, attr, ngModel) {
scope.$watch('setView', function(newValue) {
if (angular.isDefined(newValue)) {
elem.val(newValue);
ngModel.$setViewValue(newValue);
}
})
}
}
}
function MainController($scope) {
this.paste = function() {
var value = this.pasteValue;
this.values.forEach(function(v) { v.i = value });
};
}
The view:
<ul>
<li ng-repeat="value in main.values">
<input ng-model="value.v" twice set-view="value.i"/> {{value.i}}({{value.v}})
</li>
</ul>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With