Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use ngModel setViewValue from controller

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?

Edit

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();
        }
      })
    }
  }  
}
like image 467
hansmaad Avatar asked Jan 20 '26 02:01

hansmaad


1 Answers

I found two possible solutions (while writing the question:-)). http://plnkr.co/edit/ZNfYKTvSf6coGsohRlot?p=preview

1

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;
    });
  };
}

2

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>    
like image 77
hansmaad Avatar answered Jan 21 '26 16:01

hansmaad



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!