ng-content-editable
Version:
Use ngModel and validations with contenteditable HTML5 attribute
145 lines (120 loc) • 6.06 kB
JavaScript
(function () {
'use strict';
angular.module('content-editable', [])
.directive('contenteditable', ['$timeout', function ($timeout) {
return {
restrict: 'A',
require: ['^?ngModel', '^?form'],
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,
onlyNum: false,
noTrim: false
};
angular.forEach(['onlyText', 'convertNewLines', 'noLf', 'onlyNum', 'noTrim'], function (opt) {
if (attrs.hasOwnProperty(opt) && attrs[opt] && attrs[opt] !== 'false') {
opts[opt] = true;
}
});
// when model has already a value
$timeout(function () {
return (opts.onlyText && opts.noLf) || opts.onlyNum ? element.text(ngModel.$modelValue) : element.html(ngModel.$modelValue);
});
var validate = function (content) {
var length = content.length;
if (length > attrs.ngMaxlength || length < attrs.ngMinlength) {
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) || opts.onlyNum) {
content = element.text();
} else {
content = element.html();
if (content) {
content = parseHtml(content);
}
}
if (opts.noTrim === false && content !== '') {
content = content.replace(/ /g, ' ');
content = content.trim();
}
ngModel.$setViewValue(content);
validate(content);
};
ngModel.$render = function () {
if ((opts.onlyText && opts.noLf) || opts.onlyNum) {
element.text(ngModel.$viewValue || '');
} else {
element.html(ngModel.$viewValue || '');
}
};
element.bind('blur keyup change', function (event) {
scope.$apply(read);
if (event.type === 'blur') {
scope.$apply(ngModel.$render);
}
});
element.bind('keydown', function (e) {
var cntrlKeys = [8, 37, 38, 39, 40, 46];
// comma, dot, 0-9
if (opts.onlyNum && cntrlKeys.indexOf(e.which) === -1 && e.which !== 188 && e.which !== 190 && !((e.which >= 48 && e.which <= 57) || (e.which >= 96 && e.which <= 105))) {
e.preventDefault();
return false;
}
if (opts.noLf) {
if (e.which === 13) {
e.preventDefault();
return false;
} else if (attrs.ngMaxlength && element.text().length >= attrs.ngMaxlength && cntrlKeys.indexOf(e.which) === -1) {
// !e.shiftKey && !e.altKey && !e.ctrlKey &&
e.preventDefault();
return false;
}
}
});
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, lf); // 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(/<[p>]+>/ig, lf); // replace p for newlines
html = html.replace(/<\/[p>]+>/gm, ''); // remove remaining p
html = html.replace(rxl, ''); // remove last newline
}
if (opts.onlyText) {
html = html.replace(/<\S[^><]*>/g, '');
}
return html;
}
}
};
}]);
})();