Skip to content
This repository has been archived by the owner on Jan 7, 2020. It is now read-only.

Placeholder support #43

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 103 additions & 63 deletions angular-contenteditable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -44,55 +48,91 @@ angular.module('contenteditable', [])
}
ngModel.$setViewValue(html)
if (rerender) {
ngModel.$render()
ngModel.$render(true)
}
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(ignorePlaceholder) {
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') {
var value = ngModel.$viewValue || '';
if (!ignorePlaceholder && opts.placeholder) {
element.toggleClass('placeholder', value === '');
if (value === '') {
value = opts.placeholder;
}
}
element.html(value);

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)
}
}]);
2 changes: 0 additions & 2 deletions test/fixtures/no-line-breaks.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
window.scope = $scope
})
.controller('Ctrl2', function($scope) {})

angular.bootstrap(document, ['simple'])
</script>
</head>
<body>
Expand Down
22 changes: 22 additions & 0 deletions test/fixtures/placeholder.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!doctype html>
<html ng-app="test">
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="../../angular-contenteditable.js"></script>
<script type="text/javascript">
angular.module('test', ['contenteditable'])
.controller('AppController', function($scope) {
$scope.input = '';
});
</script>
<style type="text/css">
[contenteditable].placeholder { color: lightgrey; }
</style>
</head>
<body ng-controller="AppController">
<div contenteditable ng-model="input" no-line-breaks="true" placeholder="Write something"></div>
<br />
<textarea ng-model="input"></textarea>
</body>
</html>
2 changes: 0 additions & 2 deletions test/fixtures/select-non-editable.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
$scope.model = "Initial stuff <b>with bold</b> <em>and italic</em> and <span contenteditable='false'>non-editable</span> stuff"
})
.controller('Ctrl2', function($scope) {})

angular.bootstrap(document, ['simple'])
</script>
</head>
<body>
Expand Down
1 change: 0 additions & 1 deletion test/fixtures/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
.controller('Ctrl', function($scope) {
$scope.model = "Initial stuff <b>with bold</b> <em>and italic</em> yay"
})
angular.bootstrap(document, ['simple'])
</script>
</head>
<body>
Expand Down
2 changes: 0 additions & 2 deletions test/fixtures/strip-br.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
$scope.model = "Initial stuff <b>with bold</b> <em>and italic</em> yay"
})
.controller('Ctrl2', function($scope) {})

angular.bootstrap(document, ['simple'])
</script>
</head>
<body>
Expand Down