UNPKG

ng-content-editable

Version:

Use ngModel and validations with contenteditable HTML5 attribute

145 lines (120 loc) 6.06 kB
(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(/&nbsp;/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(/&nbsp;/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; } } }; }]); })();