I'm already looking for a solution for several days to add a character counter associated with my directive contenteditable.
Unfortunately I can not find a solution.
To highlight the problem different browsers have different behaviors with contenteditable.
Here is the directive I'm using :
var app = angular.module("App", []);
app.directive("contenteditable", function($timeout) {
return {
restrict: 'A',
require : ['^?ngModel'],
link : function(scope, element, attrs, args){
var ngModel = args[0];
if (ngModel === null) {
return null;
}
var modelKey = getModelKey();
opts = {
onlyText: false,
convertNewLines: false,
noLf: false,
};
angular.forEach(['onlyText', 'convertNewLines', 'noLf'], function (opt) {
if (attrs.hasOwnProperty(opt) && attrs[opt] && attrs[opt] !== 'false') {
opts[opt] = true;
}
});
$timeout(function () {
return (opts.onlyText && opts.noLf) ? element.text(ngModel.$modelValue) : element.html(ngModel.$modelValue);
});
var validate = function (content) {
var length = content.length;
refreshFn = function(content) {
if (content == undefined || content == null) {
content = '';
}
scope.maxCharacter= attrs.ngMaxlength;
scope.remaining = (length);
};
scope.$watch('ngModel', function(content){
refreshFn(content);
}, true);
if (length > attrs.ngMaxlength) {
ngModel.$setValidity(modelKey, false);
return element.addClass('-error');
}
if (element.hasClass('-error')) {
ngModel.$setValidity(modelKey, true);
return element.removeClass('-error');
}
};
var read = function () {
var content = '';
if ((opts.onlyText && opts.noLf)) {
content = element.text();
} else {
content = element.html();
if (content) {
content = parseHtml(content);
}
}
if (content !== '') {
content = content.replace(/ /g, '');
content = content.trim();
}
ngModel.$setViewValue(content);
validate(content);
};
ngModel.$render = function () {
if ((opts.onlyText && opts.noLf)) {
element.text(ngModel.$viewValue || '');
} else {
element.html(ngModel.$viewValue || '');
}
};
element.bind('blur keyup change focus', function (event) {
scope.$apply(read);
scope.displayCount = true;
if (event.type === 'blur') {
scope.$apply(ngModel.$render);
}
});
function getModelKey() {
if (typeof attrs.ngModel === 'undefined') {
return null;
}
var split = attrs.ngModel.split('.');
return split[split.length-1];
}
function parseHtml(html) {
html = html.replace(/ /g, '');
if (opts.convertNewLines || opts.noLf) {
var lf = '\r\n',
rxl = /\r\n$/;
if (opts.noLf) {
lf = ' ';
rxl = / $/;
}
html = html.replace(/<br(\s*)\/*>/ig, ''); // replace br for newlines
html = html.replace(/<[div>]+>/ig, lf); // replace div for newlines
html = html.replace(/<\/[div>]+>/gm, ''); // remove remaining divs
html = html.replace(/ /g, '');
html = html.replace(/<[p>]+>/ig, lf); // replace p for newlines
html = html.replace(/<\/[p>]+>/gm, ''); // remove remaining p
html = html.replace(/<[span>]+>/ig, lf); // replace p for newlines
html = html.replace(/<\/[span>]+>/gm, ''); // remove remaining p
html = html.replace(rxl, ''); // remove last newline
html = _.unescape(html); // replaces &, <, >, ", ` and ' with their unescaped counterparts.
}
if (opts.onlyText) {
html = html.replace(/<\S[^><]*>/g, '');
}
return html;
}
}
};
});
and the html file :
<div class="content-editable" contenteditable="true"
ng-model="line.value"
ng-maxlength=255
only-text="true"
convert-new-lines="true"
no-lf="false">
</div>
<div ng-if="displayCount" class="ctn" ng-if="countChar">
<span class="numberChar">
<span class="italic" style="margin-right:5px;">
<span ng-class="remaining>=maxCharacter ? 'errorMsg': ''">
{{remaining}}
</span> / {{maxCharacter}}
</span>
<span class="errorMsg error" ng-if="remaining>=maxCharacter"></span>
</span>
</div>
</div>
the example on codepen : https://codepen.io/gregand/pen/yLBjWYW
On Chrome and Firefox, when I enter 6 characters (line break included), the counter counts 9 characters
On Edge, the counter is good
I tried to change the contenteditable css of display: block to inline-block, it corrects the problem on Chrome but there are problems on Edge.
I tried to use
document.execCommand('defaultParagraphSeparator', false, 'p');
but without success.
If anyone knows a solution that works on all browsers, I would be interested
I found the solution, in fact the problem came from the next line :
var lf = '\r\n'
I ask each tag of the replaced by the variable lf
html = html.replace(/<[div>]+>/ig, lf);
'\r\n' counts two characters, one for '\r' and another for '\n'
whereas the line break should only have one character.
For the solution I therefore replace the variable lf
var lf = '\n'
and it works multi browsers