AngularJS + TinyMCE: ng-maxlength is not working on <textarea>

2.6k Views Asked by At

I want to limit max text length so it doesn't exceed a column length in a DB. I've put this limitation on backend, and now I want to enforce it on frontend.

I use TinyMCE v4.3.3 with angular-ui-tinymce v0.0.12 plugin and AngularJS v1.4.6.

JS:

var tinymceOpts = {
    toolbar: false,
    menubar: false,
    // do not add <p></p> from the start:
    forced_root_block: ''
}

HTML:

<textarea ui-tinymce="tinymceOpts"
          ng-model="body"
          name="body"
          ng-maxlength="100"
          required>
    {{ body }}
</textarea>
<span ng-show="form.body.$error.maxlength" class="error">
    Reached limit!
</span>

As you can see, I use ng-maxlength attribute here to limit a length of <textarea>.

Expected result: input is validated and error message is displayed only if content length (with tags included) has exceeded a limit (100 characters in this case).

Actual result:
Screenshot

Form input state is set to invalid when the input contains some text (no matter what length).


Number of characters (in the right bottom corner) is calculated for testing:

this.getCharCount = function() {
    var tx = editor.getContent({format: 'raw'});
    return tx.length;
};
3

There are 3 best solutions below

4
On

The problem here is that TinyMCE uses an own <iframe> to edit text contents and writes them back to your <textarea> on special events. No wonder ng-maxlength does not work here.

In order to achieve what you want you will need to check for the editor content itself and disallow entering more characters in case the maxlength is reached.

0
On

I managed to get it working! It was necessary to disable SCE mode in AngularJS:

angular.module('myApp', ['ui.tinymce'])
.config(['$sceProvider', function($sceProvider) {
    $sceProvider.enabled(false);
}])
.controller('myCtrl', ['$scope', function($scope) {
    // ...
}]);

jsFiddle

! Beware of security vulnerabilities !

Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain contexts to result in a value that is marked as safe to use for that context. With SCE disabled, an AngularJS application allows to render arbitrary HTML into the <div>, and rendering user controlled input creates security vulnerabilities.

1
On

I believe what you really want is to have the TinyMCE directive calculate the "length" of the visible characters as opposed to counting the characters in the <textarea>. TinyMCE is going to inject its own iFrame into the page and the editor is part of that iFrame - its not the <textarea>.

When you put validation on the <textarea> you are asking Angular to count the characters in the <textarea>...this is going to be an issue for you. The issue is that the standard directives just count characters so a simple (empty) HTML sample:

<p></p>

Would indeed be counted as 7 characters when in reality there is no "visible" content. I built a custom directive for another editor and what I ended up doing is using jQuery's .text() function against the HTML. This removes all of the HTML tags and provides an approximation for the number of actual text characters in the editor. This is a portion of the code in the directive:

var jStrippedString = jQuery(modelValue).text().trim();
return (maxlength < 0) || ngModelCtrl.$isEmpty(jStrippedString) || (jStrippedString.length <= maxlength);

I believe that you would want to create a custom Attribute directive that allows you to grab the model data for the editor and perform this validation yourself as opposed to simply counting the characters in the <textarea>.