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

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

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.

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.

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>.

Related

how to use same component in multiple place with some logic in angularjs

Well I have a directive/component/controller i.e <app-form></app-form> I am using this html page into multiple place but I want to to remove some specific field from different in place component how can I do in angularjs
In reactjs we can do like this <app-form disableField></app-form> and if disabledField then disable particular field other wise nothing to do
Again for better understanding for example we have one form having name, email and dob same form is using multiple place but at one place we are not interesting to display dob how can we disable or remove from specific place ?
please guide
You have to use bindings in your component declaration. something on the lines of:
angular.module('app').component('appForm', {
controller: 'AppFormCtrl',
templateUrl: 'app-form.html',
bindings: {
disableField: "<", // use '<' to generate input on the component
}
});
then in your app-form.html you can access the input var using the $ctrl object:
<form>
<input ng-if="$ctrl.disableField == true" type="text"/>
</form>
And the you can pass whatever value you want in the scope of your root view:
<div>
<!-- displays the form input according to the passed property's value -->
<app-form disable-field="isFieldEnabled"></app-form>
<!-- displays the form input -->
<app-form disable-field="true"></app-form>
<!-- does NOT display the form input -->
<app-form disable-field="false"></app-form>
<!-- does NOT display the form input, as disableField is evaluated an NULL in the component instance -->
<app-form></app-form>
</div>
isFieldEnabled is the property in your root controller $scope that will control the enabling / disabling of the input field in your component, but you can simply pass true or false if no logic is used.
You can attach whatever property you want, it doesn't have to be a boolean (but in this case I think it makes sense).
Also, notice that when defining the binded property 'disableField' in the Javascript environment, we use camelCase. The same property will be defined in the view / html environment using kebab-case.
You can also check the following answer to see how to generate output from the component instances:
Angular.js, how to pass value from one component to any other

AngularJS text input validation with counter

I'm developing an app containing a form which has a text input with ng-model="description".
Now I want to validate this text input using ng-maxlength="50" and required. This works fine, but I also want to add a character counter (like 67/50) shown at all times. I'm using the following code to display the counter: {{description.length || 0}}/50
The issue with this, however, is that description.length doesn't return a value when it's greater than 50, because description input is invalid. In my scenario the counter would default to 0/50 when there's no input (and that's fine) but also when input exceeds max length.
I would also like to display my counter in red when the input length's invalid, but that shouldn't be too hard using angular validation css classes.
Of course I could provide custom validation, but I'm positive there's an easier solution.
Use the form element $viewValue instead like this:
<form name='form'>
<input type="text" name='description' ng-model='description' ng-maxlength="50">
{{form.description.$viewValue.length || 0}}/50
</form>
See this plunker

How to change valid HTML tags that get rendered in ng-bind-html?

I have a text editor (textAngular) that I've modified to limit the number of valid HTML tags I can generate using that tool. Now, I want to only support a limited number of HTML elements (h3, h4, h5, h6, ol, ul) to produce a news story but I want to disable some of the valid HTML rendered by ng-bind-html. Namely, I want to remove , tags as a valid tags because they could have disastrous results for this user generated content.
Is it possible to remove and tags as something rendered by ng-bind-html?
Unfortunately no, it isn't possible to config the valid HTML tags.
The ng-bind-html use the $sanitize service to strip invalid tags/attributes, and you can see in the source code that all the configurations are private.
// Safe Block Elements - HTML5
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
If you really want it, one way you could do is to copy the angular-sanitize.js and modify the valid HTML tags configuration directly.
Please note that if you do it that way, all the ng-bind-html in your entire application will be also affected. If that is undesired, you have to write your own custom directive and inject/use your modified version of $sanitize instead.
If you're into modifying textAngular already, you could modify something around the taCustomRenderers Section of the code and use ta-bind instead of ng-bind-html. They do nearly the same thing except ta-bind runs all the extra renderers.
Custom Renderers Code: textAngularSetup, textAngular - probably in this one you can do your stripping out of unwanted code.

angularjs + cross-site scripting preventing

Is Angularjs takes care of XSS attack. I have read that ng-bind takes care. But When i try to do a sample to test that, it allows me to insert html tags in input type with ng-model...it didn't escape the Html tags.
I have lot of input element in our page, which binds with ng-model, what should I do to make sure if I input a html tags ,angular ignores the html/scrip tags.
ex.
<input id="name" ng-model="name"></input>
if I input as
'Hello, <b>World</b>!'
$scope.name contains the same what I entered ,didn't exclude the tags. i.e
var val = $scope.name;
console.log(val);
prints as same
'Hello, <b>World</b>!'
Please let me know how to solve this in angularjs.
thank
Look at here : http://docs.angularjs.org/api/ngSanitize/service/$sanitize
If you want escape use ng-bind, it ll render the tag without interpretation like that :
Hello <b>World</b> not like Hello World !
Do you understand ? so ng-bind is safe because it doesn't care about HTML tags.
If you want that your HTML tags be interpreted but safely just use ng-bind-html !
For example if you want to display this string :
'Hello <b>World</b><input type="text" />'
The result will be : Hello World but without the input because AngularJS compiler uses $sanitize service and check a whitelist of HTML elements and an iput is not authorized.
Maybe ng-bind-html is what you're looking for.
If you just want be sure that the user can't put html tags in your input just use the directive ng-pattern on your inputs !
http://docs.angularjs.org/api/ng/directive/input
It takes a regex for allowed characters in your input !
Hope it helps !
I don't believe that AngularJS has default whitelist input validation, which is what your test exercises. So a user can pretty much input anything they like. This is not surprising - whitelists are very domain specific, and Angular is a framework designed for a wide range of domains.
The main defense against XSS is to properly encode all untrusted data (see https://www.owasp.org/index.php/Top_10_2013-A3-Cross-Site_Scripting_(XSS)). This, Angular does by default.
Bottom line is that AngularJS is intended to be secure from XSS by default, no special action required. You can verify some basic scenarios by trying to output what you input into a view using the normal {{scopevariable}} notation.
I did find a detailed analysis of AngularJS XSS vulnerability: https://code.google.com/p/mustache-security/wiki/AngularJS. At the end of the comments, there is a link to a google doc with further discussion and response from the angular team.

How to format carriage returns in a Backbone model in a Mustache template

I'm using Backbone models as input into Mustache templates to generate HTML.
I have a Backbone model with a number of attributes, such as name, description and id. The description attribute can contain carriage returns, which I want to render as <br> tags when they're rendered in the template.
By default, Mustache simply outputs the carriage returns directly, so the markup looks tidy, but the rendered result has no breaks.
I don't particularly want to replace \n\r in the description attribute, as that property could be used elsewhere (e.g. in alt or meta tags).
The only idea I have so far is to add a duplicate description attribute that has the formatted text.
Is there nothing in Mustache that formats HTML line breaks as <br> tags?
Mustache is very limited on purpose. If you need anything special in a Mustache template, you prepare your data in JavaScript so that Mustache's interpolation and loops can handle it. In your case, that means splitting your string on EOLs to get an array:
// Adjust the regex to suit your data, this one is pretty loose.
var lines = string.split(/[\r\n]+/)
.map(function(line) { return { line: line } });
and then loop over that array in Mustache:
{{#lines}}
{{line}}<br>
{{/lines}}
mu is too short's answer is correct. I just want to add that the .map function isn't supported in IE8 (and older).
I ended up using a loop to achieve the same affect as we need to support IE8:
var descriptionArray = description.split(/[\r\n]+/);
var descriptionLines = new Array();
for (var line = 0; line < descriptionArray.length; line++) {
descriptionLines.push({ Line: descriptionArray[line] });
}

Resources