6

I'm trying to complete something that should be fairly simple, in my opinion. And it also is, when using something like a text input. I'm trying to create model binding, on a textarea, where the value, when the user is typing, is shown with a prefix and a suffix. The prefix and suffix being quotation marks:

“My awesome quote”

The problem is, that i'm currently using ng-model, which i ofcourse cannot use for this. I was thinking about binding to a variable, holding the value without the prefix and suffix, and then watching that variable. When the variable, with the original value, then changes, i would write the value with a pre and suffix to another variable, on the scope. That variable would then be shown in the textarea, as the user types. The only problem is, that a textarea, unlike an input field, doesn't have a value property.

Is this even possible?

EDIT

If i where to achieve this with an input text field, i would create a variable called A, to hold the raw value that changes when the user is typing. When A changes, i would then take the raw value, put quotes around it and store that new value in another variable, also on the scope. That new variable is called B

The input field would then use ng-bind on the A variable, and show the content from the B variable, using the input fields value attribute. Something like below:

<input type="text" ng-bind="A" value="{{B}}">

I don't have time to create a fiddle right now, but i will try to do it later this week. The description above is all in theory, as i have not tested it yet.

Teilmann
  • 2,146
  • 7
  • 28
  • 57
  • Just to clarify, you want the prefix and suffix to be added in the textarea without affecting the model value? – Cosmin Ababei Jun 24 '16 at 09:37
  • Yes, exactly. If it could be done, i mean. I would have no problem with removing the quotes before posting the value, if that's required. I have a way to do it now, but the problem is, that the cursor is in the "wrong" position. "my quote"(cursor stands here) – Teilmann Jun 24 '16 at 09:39
  • If i could just place the quotes around the textarea, that would be great, but the textarea does not have dynamic width. I could of course write some javascript to adjust the width as you type. – Teilmann Jun 24 '16 at 09:40
  • What about using an editable paragraph tag? using contentEditable. And then wrapping that in quotes – Teilmann Jun 24 '16 at 09:42
  • You said that it's very simple to achieve this effect using an input instead of a textarea. Can you create an example on jsfiddle? – Cosmin Ababei Jun 25 '16 at 10:38
  • I'm afraid i'm very busy at work the rest of the week, because of a deadline, but i can explain it pretty quickly. I will add it as an edit to the original question. – Teilmann Jun 29 '16 at 08:09

2 Answers2

1

UPDATE

so i used ng focus and blur to remove and add the quotes, it wont be live while he is using the textarea but it will work

angular.module('myApp', [])
  .controller('demoCtrl', ['$scope',
    function($scope) {
      $scope.myModel = '';

      $scope.removeQuotes = function() {
        $scope.myModel = $scope.myModel.replace(/“|”/gm, ''); // remove the quotes
      }

      $scope.addQuotes = function() {
        if ($scope.myModel == '') {
          $scope.myModel = ''; // dont add the quotes for an empty value
          return;
        }


        $scope.myModel = "“" + $scope.myModel + "”" // add the quotes again
      }
    }
  ])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="myApp">
  <div ng-controller="demoCtrl">
    <textarea ng-model="myModel" ng-focus='removeQuotes()' ng-blur='addQuotes()'></textarea>
  </div>

</div>
WalksAway
  • 2,769
  • 2
  • 20
  • 42
  • The first solution your posted was actually better than this one, because you had the user typing inside a textarea, which is required for this to work for my scenario. I'm having trouble seeing where you're going with this answer, using an input field. The only problem with your first answer was, that the cursor was on the wrong side of the quotation mark. i guess thats not possible – Teilmann Jun 24 '16 at 09:19
  • That is actually a really good idea. Since its not 100% a solution to the problem, i will let it hang for awhile, before accepting it! Upvote to you! – Teilmann Jun 29 '16 at 12:49
1

Your requirements are quite interesting and can be split into two main functionalities:

1. Add a prefix and suffix to a view value without affecting the model value

This has been achieved using NgModelController. I've created a watcher on the ngModelController.$viewValue and whenever it changes, if it doesn't contain the prefix or suffix, I add those values.

2. Don't allow the user to interfere with the prefix or suffix

This is a bit tricky, but I found a clever way on SO of getting and setting the caret position of an input or textarea element.

Here's a working example:

angular
  .module('myApp', [])
  .directive('beautifyText', function() {
    return {
      link: function(scope, iElement, iAttrs, ngModelController) {
        // Adds the prefix and suffix
        (function() {
          var prefix = iAttrs.beautifyTextWithPrefix;
          var suffix = iAttrs.beautifyTextWithSuffix;

          ngModelController.$parsers.push(function(value) {
            if (angular.isString(value)) {
              return value.replace(new RegExp('^' + prefix), '').replace(new RegExp(suffix + '$'), '');
            }

            return '';
          });

          scope.$watch(function() {
            return ngModelController.$viewValue;
          }, function(newValue) {
            if (angular.isString(newValue) && newValue.length > 0) {
              if (angular.isString(ngModelController.$modelValue) && ngModelController.$modelValue.length === 0 && newValue.length === 1) {
                ngModelController.$viewValue = '';
                ngModelController.$render();
                return;
              }

              if (!isBeautifiedWithPrefix(newValue)) {
                ngModelController.$viewValue = prefix + newValue;
              }

              if (!isBeautifiedWithSuffix(newValue)) {
                ngModelController.$viewValue = newValue + suffix;
              }

              ngModelController.$render();
            } else {
              ngModelController.$viewValue = '';
              ngModelController.$render();
            }
          });

          function isBeautifiedWithPrefix(value) {
            return value.match(new RegExp('^' + prefix)) !== null;
          }

          function isBeautifiedWithSuffix(value) {
            return value.match(new RegExp(suffix + '$')) !== null;
          }
        })();

        // Changes the caret position
        (function() {
          var element = iElement[0];

          // https://stackoverflow.com/questions/7745867/how-do-you-get-the-cursor-position-in-a-textarea#answer-7745998
          function getCursorPos() {
            if ("selectionStart" in element && document.activeElement == element) {
              return {
                start: element.selectionStart,
                end: element.selectionEnd
              };
            } else if (element.createTextRange) {
              var sel = document.selection.createRange();
              if (sel.parentElement() === element) {
                var rng = element.createTextRange();
                rng.moveToBookmark(sel.getBookmark());
                for (var len = 0; rng.compareEndPoints("EndToStart", rng) > 0; rng.moveEnd("character", -1)) {
                  len++;
                }
                rng.setEndPoint("StartToStart", element.createTextRange());
                for (var pos = {
                  start: 0,
                  end: len
                }; rng.compareEndPoints("EndToStart", rng) > 0; rng.moveEnd("character", -1)) {
                  pos.start++;
                  pos.end++;
                }
                return pos;
              }
            }
            return -1;
          }

          // https://stackoverflow.com/questions/7745867/how-do-you-get-the-cursor-position-in-a-textarea#answer-7745998
          function setCursorPos(start, end) {
            if (arguments.length < 2) {
              end = start;
            }

            if ("selectionStart" in element) {
              element.selectionStart = start;
              element.selectionEnd = end;
            } else if (element.createTextRange) {
              var rng = element.createTextRange();
              rng.moveStart("character", start);
              rng.collapse();
              rng.moveEnd("character", end - start);
              rng.select();
            }
          }

          iElement.bind('mousedown mouseup keydown keyup', function() {
            if (ngModelController.$viewValue.length > 0) {
              var caretPosition = getCursorPos();

              if (caretPosition.start === 0) {
                setCursorPos(1, caretPosition.end < 1 ? 1 : caretPosition.end);
              }

              if (caretPosition.end === ngModelController.$viewValue.length) {
                setCursorPos(caretPosition.start, ngModelController.$viewValue.length - 1);
              }
            }
          });
        })();
      },
      restrict: 'A',
      require: 'ngModel'
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>

<div ng-app="myApp">
  <textarea ng-model="myVariable" beautify-text beautify-text-with-prefix="“" beautify-text-with-suffix="”"></textarea>
  <p>myVariable: {{ myVariable }}</p>
</div>
Community
  • 1
  • 1
Cosmin Ababei
  • 7,003
  • 2
  • 20
  • 34
  • That is a really clever solution! The only thing missing is to remove the quotes, when the value is empty. I can probably add that myself! Thanks! – Teilmann Jul 01 '16 at 07:54
  • 1
    @ThomasTeilmann That was pretty simple. I've updated my code. `if (angular.isString(ngModelController.$modelValue) && ngModelController.$modelValue.length === 0 && newValue.length === 1)` does the trick. – Cosmin Ababei Jul 01 '16 at 09:53