From 43c8664fae7e111165e85d48cb79c3d36504ff01 Mon Sep 17 00:00:00 2001 From: Aleks Selivanov Date: Thu, 26 Feb 2015 00:49:21 +0500 Subject: [PATCH 1/6] Add placeholder support --- angular-contenteditable.js | 160 +++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 62 deletions(-) diff --git a/angular-contenteditable.js b/angular-contenteditable.js index 49b59ee..73b7606 100644 --- a/angular-contenteditable.js +++ b/angular-contenteditable.js @@ -5,30 +5,34 @@ */ angular.module('contenteditable', []) - .directive('contenteditable', ['$timeout', function($timeout) { return { - restrict: 'A', - require: '?ngModel', - link: function(scope, element, attrs, ngModel) { - // don't do anything unless this is actually bound to a model - if (!ngModel) { - return - } + .directive('contenteditable', ['$timeout', function($timeout) { + return { + restrict: 'A', + require: '?ngModel', + link: function(scope, element, attrs, ngModel) { + // don't do anything unless this is actually bound to a model + if (!ngModel) { + return + } - // options - var opts = {} - angular.forEach([ - 'stripBr', - 'noLineBreaks', - 'selectNonEditable', - 'moveCaretToEndOnChange', - ], function(opt) { - var o = attrs[opt] - opts[opt] = o && o !== 'false' - }) + // options + var opts = {} + angular.forEach([ + 'stripBr', + 'noLineBreaks', + 'selectNonEditable', + 'moveCaretToEndOnChange', + 'placeholder' + ], function(opt) { + var o = attrs[opt] + opts[opt] = o && o !== 'false' + }) + if (opts.placeholder) { + opts.placeholder = attrs.placeholder || 'Empty' + } - // view -> model - element.bind('input', function(e) { - scope.$apply(function() { + // view -> model + function readViewValue() { var html, html2, rerender html = element.html() rerender = false @@ -46,53 +50,85 @@ angular.module('contenteditable', []) if (rerender) { ngModel.$render() } - if (html === '') { - // the cursor disappears if the contents is empty - // so we need to refocus - $timeout(function(){ - element[0].blur() - element[0].focus() - }) - } - }) - }) + return html; + } - // model -> view - var oldRender = ngModel.$render - ngModel.$render = function() { - var el, el2, range, sel - if (!!oldRender) { - oldRender() + element.bind('input', function(e) { + scope.$apply(function() { + var empty = '' === readViewValue() + if (empty) { + // the cursor disappears if the contents is empty + // so we need to keep focus + selectAll(element[0]) + } else if (opts.placeholder) { + element.removeClass('placeholder') + } + }) + }) + if (opts.placeholder) { + element.bind('blur', function (e) { + scope.$apply(function () { + if (readViewValue() === '') { + element.html(opts.placeholder) + element.addClass('placeholder') + } + }); + }) } - element.html(ngModel.$viewValue || '') - if (opts.moveCaretToEndOnChange) { - el = element[0] - range = document.createRange() - sel = window.getSelection() - if (el.childNodes.length > 0) { - el2 = el.childNodes[el.childNodes.length - 1] - range.setStartAfter(el2) - } else { - range.setStartAfter(el) + + // model -> view + var oldRender = ngModel.$render + ngModel.$render = function() { + var el, el2, range, sel + if (!!oldRender) { + oldRender() } - range.collapse(true) - sel.removeAllRanges() - sel.addRange(range) - } - } - if (opts.selectNonEditable) { - element.bind('click', function(e) { - var range, sel, target - target = e.toElement - if (target !== this && angular.element(target).attr('contenteditable') === 'false') { + element.html(ngModel.$viewValue || opts.placeholder || '') + if (opts.placeholder) { + element.toggleClass('placeholder', !ngModel.$viewValue); + } + + if (opts.moveCaretToEndOnChange) { + el = element[0] range = document.createRange() sel = window.getSelection() - range.setStartBefore(target) - range.setEndAfter(target) + if (el.childNodes.length > 0) { + el2 = el.childNodes[el.childNodes.length - 1] + range.setStartAfter(el2) + } else { + range.setStartAfter(el) + } + range.collapse(true) sel.removeAllRanges() sel.addRange(range) } - }) + } + if (opts.placeholder) { + element.bind('focus', function () { + if (!ngModel.$viewValue) { + element.html('') + selectAll(element[0]) + } + }) + } + + if (opts.selectNonEditable) { + element.bind('click', function(e) { + var range, sel, target + target = e.toElement + if (target !== this && angular.element(target).attr('contenteditable') === 'false') { + selectAll(target) + } + }) + } } } - }}]); + + function selectAll(node) { + var range = document.createRange() + range.selectNodeContents(node) + var sel = window.getSelection() + sel.removeAllRanges() + sel.addRange(range) + } + }]); From 1c3766deee67e548210f145e848fc8bc445fe51f Mon Sep 17 00:00:00 2001 From: Aleks Selivanov Date: Thu, 26 Feb 2015 00:52:17 +0500 Subject: [PATCH 2/6] Add placeholder test fixture --- test/fixtures/placeholder.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/fixtures/placeholder.html diff --git a/test/fixtures/placeholder.html b/test/fixtures/placeholder.html new file mode 100644 index 0000000..9511159 --- /dev/null +++ b/test/fixtures/placeholder.html @@ -0,0 +1,22 @@ + + + + + + + + + + +
+
+ + + From 4e8dcc0a8fdf2ef6ebdb2811ce78f5d90816d9ad Mon Sep 17 00:00:00 2001 From: Aleks Selivanov Date: Thu, 26 Feb 2015 00:52:42 +0500 Subject: [PATCH 3/6] Fix Angular bootstrap in some tests --- test/fixtures/no-line-breaks.html | 2 -- test/fixtures/select-non-editable.html | 2 -- test/fixtures/simple.html | 1 - test/fixtures/strip-br.html | 2 -- 4 files changed, 7 deletions(-) diff --git a/test/fixtures/no-line-breaks.html b/test/fixtures/no-line-breaks.html index d200c05..5d3edbe 100644 --- a/test/fixtures/no-line-breaks.html +++ b/test/fixtures/no-line-breaks.html @@ -14,8 +14,6 @@ window.scope = $scope }) .controller('Ctrl2', function($scope) {}) - -angular.bootstrap(document, ['simple']) diff --git a/test/fixtures/select-non-editable.html b/test/fixtures/select-non-editable.html index 78090ec..9e40f8a 100644 --- a/test/fixtures/select-non-editable.html +++ b/test/fixtures/select-non-editable.html @@ -13,8 +13,6 @@ $scope.model = "Initial stuff with bold and italic and non-editable stuff" }) .controller('Ctrl2', function($scope) {}) - -angular.bootstrap(document, ['simple']) diff --git a/test/fixtures/simple.html b/test/fixtures/simple.html index 1b3d0bf..43172b0 100644 --- a/test/fixtures/simple.html +++ b/test/fixtures/simple.html @@ -9,7 +9,6 @@ .controller('Ctrl', function($scope) { $scope.model = "Initial stuff with bold and italic yay" }) -angular.bootstrap(document, ['simple']) diff --git a/test/fixtures/strip-br.html b/test/fixtures/strip-br.html index 0081e15..0282f75 100644 --- a/test/fixtures/strip-br.html +++ b/test/fixtures/strip-br.html @@ -13,8 +13,6 @@ $scope.model = "Initial stuff with bold and italic yay" }) .controller('Ctrl2', function($scope) {}) - -angular.bootstrap(document, ['simple']) From c30b17d0a2ed40f079d0d6e6a1e9eb8a00a7b2e6 Mon Sep 17 00:00:00 2001 From: Aleks Selivanov Date: Thu, 26 Feb 2015 01:14:36 +0500 Subject: [PATCH 4/6] Fix placeholder re-render related bug --- angular-contenteditable.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/angular-contenteditable.js b/angular-contenteditable.js index 73b7606..108d7e4 100644 --- a/angular-contenteditable.js +++ b/angular-contenteditable.js @@ -48,7 +48,8 @@ angular.module('contenteditable', []) } ngModel.$setViewValue(html) if (rerender) { - ngModel.$render() + console.log('!!rerender'); + ngModel.$render(true) } return html; } @@ -78,15 +79,19 @@ angular.module('contenteditable', []) // model -> view var oldRender = ngModel.$render - ngModel.$render = function() { + ngModel.$render = function(ignorePlaceholder) { var el, el2, range, sel if (!!oldRender) { oldRender() } - element.html(ngModel.$viewValue || opts.placeholder || '') - if (opts.placeholder) { - element.toggleClass('placeholder', !ngModel.$viewValue); + var value = ngModel.$viewValue || ''; + if (!ignorePlaceholder && opts.placeholder) { + if (value === '') { + value = opts.placeholder; + } + element.toggleClass('placeholder', !value); } + element.html(value); if (opts.moveCaretToEndOnChange) { el = element[0] From a0c7ad2e43dc156f70affe264824e270b491b428 Mon Sep 17 00:00:00 2001 From: Aleks Selivanov Date: Thu, 26 Feb 2015 01:16:49 +0500 Subject: [PATCH 5/6] Remove logging --- angular-contenteditable.js | 1 - 1 file changed, 1 deletion(-) diff --git a/angular-contenteditable.js b/angular-contenteditable.js index 108d7e4..3f3ab7b 100644 --- a/angular-contenteditable.js +++ b/angular-contenteditable.js @@ -48,7 +48,6 @@ angular.module('contenteditable', []) } ngModel.$setViewValue(html) if (rerender) { - console.log('!!rerender'); ngModel.$render(true) } return html; From 04f3b15fc2b5faf48cbdb6c0dd7fa115b34e78b0 Mon Sep 17 00:00:00 2001 From: Aleks Selivanov Date: Thu, 26 Feb 2015 01:27:49 +0500 Subject: [PATCH 6/6] Fix styling issue --- angular-contenteditable.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/angular-contenteditable.js b/angular-contenteditable.js index 3f3ab7b..ff05f1b 100644 --- a/angular-contenteditable.js +++ b/angular-contenteditable.js @@ -85,10 +85,10 @@ angular.module('contenteditable', []) } var value = ngModel.$viewValue || ''; if (!ignorePlaceholder && opts.placeholder) { - if (value === '') { - value = opts.placeholder; - } - element.toggleClass('placeholder', !value); + element.toggleClass('placeholder', value === ''); + if (value === '') { + value = opts.placeholder; + } } element.html(value);