Skip to content

Commit

Permalink
feat(taBind): Allow updates while focussed.
Browse files Browse the repository at this point in the history
Fixes #38

Updates while the editor is focussed will now allow overwriting of the content and caret will be put to the end of the content.
  • Loading branch information
SimeonC authored and SimeonC committed Feb 5, 2015
1 parent e1008df commit 452c7f0
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 23 deletions.
2 changes: 1 addition & 1 deletion lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ textAngular.directive("textAngular", [
scope.displayElements.forminput.val(ngModel.$viewValue);
// if the editors aren't focused they need to be updated, otherwise they are doing the updating
/* istanbul ignore else: don't care */
if(!scope._elementSelectTriggered && $document[0].activeElement !== scope.displayElements.html[0] && $document[0].activeElement !== scope.displayElements.text[0]){
if(!scope._elementSelectTriggered){
// catch model being null or undefined
scope.html = ngModel.$viewValue || '';
}
Expand Down
33 changes: 23 additions & 10 deletions lib/taBind.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
var _isInputFriendly = _isContentEditable || element[0].tagName.toLowerCase() === 'textarea' || element[0].tagName.toLowerCase() === 'input';
var _isReadonly = false;
var _focussed = false;
var _skipRender = false;
var _disableSanitizer = attrs.taUnsafeSanitizer || taOptions.disableSanitizer;
var _lastKey;
var BLOCKED_KEYS = /^(9|19|20|27|33|34|35|36|37|38|39|40|45|112|113|114|115|116|117|118|119|120|121|122|123|144|145)$/i;
Expand Down Expand Up @@ -150,6 +151,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
};

var _setViewValue = function(_val, triggerUndo){
_skipRender = true;
if(typeof triggerUndo === "undefined" || triggerUndo === null) triggerUndo = true && _isContentEditable; // if not contentEditable then the native undo/redo is fine
if(typeof _val === "undefined" || _val === null) _val = _compileHtml();
if(_blankTest(_val)){
Expand Down Expand Up @@ -530,6 +532,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
if(!_isReadonly){
_setViewValue();
}
_skipRender = true; // don't redo the whole thing, just check the placeholder logic
ngModel.$render();
});

Expand All @@ -546,7 +549,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'

element.on('focus', scope.events.focus = function(){
_focussed = true;
ngModel.$render();
element.removeClass('placeholder-text');
});

element.on('mouseup', scope.events.mouseup = function(){
Expand Down Expand Up @@ -634,20 +637,27 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
ngModel.$render = function(){
// catch model being null or undefined
var val = ngModel.$viewValue || '';

// if the editor isn't focused it needs to be updated, otherwise it's receiving user input
if($document[0].activeElement !== element[0]){
// Not focussed
if(!_skipRender){
/* istanbul ignore else: in other cases we don't care */
if(_isContentEditable && _focussed){
// element is focussed, test for placeholder
element.removeClass('placeholder-text');
element[0].blur();
$timeout(function(){
element[0].focus();
taSelection.setSelectionToElementEnd(element.children()[element.children().length - 1]);
}, 1);
}
if(_isContentEditable){
// WYSIWYG Mode
if(attrs.placeholder){
if(val === ''){
// blank
if(_focussed) element.removeClass('placeholder-text');
else element.addClass('placeholder-text');
_setInnerHTML(_defaultVal);
}else{
// not-blank
element.removeClass('placeholder-text');
_setInnerHTML(val);
}
}else{
Expand All @@ -667,13 +677,16 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
// only for input and textarea inputs
element.val(val);
}
}else{
/* istanbul ignore else: in other cases we don't care */
if(_isContentEditable){
// element is focussed, test for placeholder
}
if(_isContentEditable && attrs.placeholder){
if(val === ''){
if(_focussed) element.removeClass('placeholder-text');
else element.addClass('placeholder-text');
}else{
element.removeClass('placeholder-text');
}
}
_skipRender = false;
};

if(attrs.taReadonly){
Expand Down
40 changes: 28 additions & 12 deletions src/textAngular.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,9 @@ function($window, $document, taDOM){

range.selectNodeContents(el);
range.collapse(false);

if(el.childNodes && el.childNodes[el.childNodes.length - 1] && el.childNodes[el.childNodes.length - 1].nodeName === 'br'){
range.startOffset = range.endOffset = range.startOffset - 1;
}
rangy.getSelection().setSingleRange(range);
},
// from http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
Expand Down Expand Up @@ -760,6 +762,7 @@ function($window, $document, taDOM){
range.deleteContents();
}
}

range.insertNode(frag);
if(lastNode){
api.setSelectionToElementEnd(lastNode);
Expand Down Expand Up @@ -933,6 +936,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
var _isInputFriendly = _isContentEditable || element[0].tagName.toLowerCase() === 'textarea' || element[0].tagName.toLowerCase() === 'input';
var _isReadonly = false;
var _focussed = false;
var _skipRender = false;
var _disableSanitizer = attrs.taUnsafeSanitizer || taOptions.disableSanitizer;
var _lastKey;
var BLOCKED_KEYS = /^(9|19|20|27|33|34|35|36|37|38|39|40|45|112|113|114|115|116|117|118|119|120|121|122|123|144|145)$/i;
Expand Down Expand Up @@ -1039,6 +1043,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
};

var _setViewValue = function(_val, triggerUndo){
_skipRender = true;
if(typeof triggerUndo === "undefined" || triggerUndo === null) triggerUndo = true && _isContentEditable; // if not contentEditable then the native undo/redo is fine
if(typeof _val === "undefined" || _val === null) _val = _compileHtml();
if(_blankTest(_val)){
Expand Down Expand Up @@ -1419,6 +1424,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
if(!_isReadonly){
_setViewValue();
}
_skipRender = true; // don't redo the whole thing, just check the placeholder logic
ngModel.$render();
});

Expand All @@ -1435,7 +1441,7 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'

element.on('focus', scope.events.focus = function(){
_focussed = true;
ngModel.$render();
element.removeClass('placeholder-text');
});

element.on('mouseup', scope.events.mouseup = function(){
Expand Down Expand Up @@ -1523,20 +1529,27 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
ngModel.$render = function(){
// catch model being null or undefined
var val = ngModel.$viewValue || '';

// if the editor isn't focused it needs to be updated, otherwise it's receiving user input
if($document[0].activeElement !== element[0]){
// Not focussed
if(!_skipRender){
/* istanbul ignore else: in other cases we don't care */
if(_isContentEditable && _focussed){
// element is focussed, test for placeholder
element.removeClass('placeholder-text');
element[0].blur();
$timeout(function(){
element[0].focus();
taSelection.setSelectionToElementEnd(element.children()[element.children().length - 1]);
}, 1);
}
if(_isContentEditable){
// WYSIWYG Mode
if(attrs.placeholder){
if(val === ''){
// blank
if(_focussed) element.removeClass('placeholder-text');
else element.addClass('placeholder-text');
_setInnerHTML(_defaultVal);
}else{
// not-blank
element.removeClass('placeholder-text');
_setInnerHTML(val);
}
}else{
Expand All @@ -1556,13 +1569,16 @@ angular.module('textAngular.taBind', ['textAngular.factories', 'textAngular.DOM'
// only for input and textarea inputs
element.val(val);
}
}else{
/* istanbul ignore else: in other cases we don't care */
if(_isContentEditable){
// element is focussed, test for placeholder
}
if(_isContentEditable && attrs.placeholder){
if(val === ''){
if(_focussed) element.removeClass('placeholder-text');
else element.addClass('placeholder-text');
}else{
element.removeClass('placeholder-text');
}
}
_skipRender = false;
};

if(attrs.taReadonly){
Expand Down Expand Up @@ -2109,7 +2125,7 @@ textAngular.directive("textAngular", [
scope.displayElements.forminput.val(ngModel.$viewValue);
// if the editors aren't focused they need to be updated, otherwise they are doing the updating
/* istanbul ignore else: don't care */
if(!scope._elementSelectTriggered && $document[0].activeElement !== scope.displayElements.html[0] && $document[0].activeElement !== scope.displayElements.text[0]){
if(!scope._elementSelectTriggered){
// catch model being null or undefined
scope.html = ngModel.$viewValue || '';
}
Expand Down
10 changes: 10 additions & 0 deletions test/taBind/taBind.display.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ describe('taBind.display', function () {
$rootScope.$digest();
expect(element.hasClass('placeholder-text')).toBe(false);
});
it('should not add the placeholder text back if focussed and blank', function () {
element.triggerHandler('focus');
$rootScope.$digest();
$rootScope.html = '<p>Lorem Ipsum</p>';
$rootScope.$digest();
$rootScope.html = '';
$rootScope.$digest();
expect($window.getComputedStyle(element[0], ':before').getPropertyValue('display')).toBe("");
expect(element.html()).toEqual('<p><br></p>');
});
});
describe('as contenteditable div initially with content', function(){
var $rootScope, element, $window;
Expand Down
8 changes: 8 additions & 0 deletions test/taBind/taBind.spec.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 452c7f0

Please sign in to comment.