Angular.js: Support HTML5 contentEditable attribute

Created on 20 Aug 2011  ·  78Comments  ·  Source: angular/angular.js

We should do two-way binding even for other elements than inputs, if there is contentEditable attribute.

Lots of comments moderate not core broken expected use feature

Most helpful comment

Hi everyone,
binding to contenteditable is not something we are going to support in core:

  • handling contenteditable in a robust and comprehensive way is very complex
  • Official support would take away a lot of resources from the (small) team that is handling AngularJs 1.x nowadays.
  • However, even with a larger team, contenteditable would be a huge project that would demand constant attention
  • We would have to duplicate / include a lot of code that has already been written by others that handles the various browser differences and quirks.
  • "binding to contenteditable" is not a clearly delineated concept. Support for "simple" binding will grow bigger as browser differences need to be handled, and might raise demand for complete WYSIWYG / rich text editors

Fortunately, there are already projects that handle contenteditable and rich text editors, some that are wrapped to integrate with Angular, and some that are specifically written for it. I didn't have time to test all projects, I just want to highlight textAngulars taBind directive, which handles "simple" contenteditable two-way binding with ngModel. This is as far as I can say the scope of this issue: no fancy editor, but simply a way to have a robust contenteditable that handles different input techniques and browser differences. I've created a plunker that shows a simple contenteditable that has been enhanced with taBind: http://plnkr.co/edit/kKPfk0LCXWrMpZ1gkblb?p=preview (textAngular also provides a full editor if you need one).

All 78 comments

a stale piece of code from previous mailing list discussion

<!doctype html>
<html xmlns:ng="http://angularjs.org" xmlns:ngx="http://angularjs.org/extras" ng:controller="MyCtrl">
 <script ng:autobind src="http://code.angularjs.org/angular-0.9.10.js"></script>
  <script>
   function MyCtrl() {
     this.reset = function(){
       this.text = "Hello <b>World</b>!";
     };
     this.reset();
   }

   angular.widget("@ngx:contenteditable", function(expression, template){
     template.attr('contenteditable', 'true');
     return angular.extend(function($updateView, instance) {
       var scope = this,
           lastHtml = instance.html();
       instance.bind('blur keyup paste', function(){
         scope.$set(expression, lastHtml = instance.html());
         $updateView();
       });

       this.$watch(expression, function(value){
         if (value != lastHtml)
           instance.html(value);
       });

     }, {$inject:['$updateView']});
   })
  </script>

 <body ng:controller="MyCtrl">
  <h1>Editor</h1>
  <hr/>
  <div ngx:contenteditable="text" style="border: 1px solid black;"></div>
  [ <a href="" ng:click="reset()">reset</a> ]
  <hr/>
  <textarea rows="10" cols="80" name="text"></textarea>
 </body>
</html>

Hey all, so I was unable to get the provided example to work with 0.9.10, let alone with 1.0.0r6.

Any further guidance would be greatly appreciated! (As I'd much rather use contenteditable over a directive that replaces with for inline-edits) :D

The above code is outdated. Can you update it?

Yes, it would be great if we were able to use contenteditables with AngularJS.

I agree, need 2-way data binding for content editable

I did find a way around this by implementing my own directive (note JS is in CoffeeScript):

angular.module('NameOfYourApp').directive('contenteditable', ->
  return {
    restrict: 'A', 
    require: '?ngModel',
    link: (scope, element, attr, ngModel) ->
      return if not ngModel

      ngModel.$render = ->
        element.html(ngModel.$viewValue)

      element.bind 'blur', ->
        if ngModel.$viewValue isnt $.trim(element.html())
          scope.$apply(read)

      read = ->
        console.log("read()")
        ngModel.$setViewValue($.trim(element.html()))
  }
)

Edit:

And if you don't use CoffeeScript, here's the JS version:

angular.module('NameOfYourApp').directive('contenteditable', function() {
    return {
      restrict: 'A',
      require: '?ngModel',
      link: function(scope, element, attr, ngModel) {
        var read;
        if (!ngModel) {
          return;
        }
        ngModel.$render = function() {
          return element.html(ngModel.$viewValue);
        };
        element.bind('blur', function() {
          if (ngModel.$viewValue !== $.trim(element.html())) {
            return scope.$apply(read);
          }
        });
        return read = function() {
          console.log("read()");
          return ngModel.$setViewValue($.trim(element.html()));
        };
      }
    };
  });

Are you aware of this contentEditable demo? http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController

@mhevery Is there a reason for not including this directive in Angular?

I'm aware, though I should have pointed out that mine is slightly different and relies on jQuery's trim function.

@wizek you have to draw the line someplace.

On Tue, Aug 7, 2012 at 11:50 PM, Hengjie [email protected] wrote:

I'm aware, though I should have pointed out that mine is slightly
different and relies on jQuery's trim function.


Reply to this email directly or view it on GitHubhttps://github.com/angular/angular.js/issues/528#issuecomment-7576294.

@mhevery I wouldn't dispute that for a minute.

Though, may I ask what logic do you follow when deciding where you draw that line?

@mhevery If Angular included 'input, textfield, etc' on the basis of it being input fields in the HTML4 spec, then by that basis, we should include 'contenteditable' because it's part of the HTML5 spec's input.

I guess we just make a guess. We don't have any official rules. You are free to make your case, or better yet, send us a pull request. I think the demo is bit trivial since I think real content editable is more complicated, but maybe that is all that needs to be done.

Just dug onto this, and the mud I extracted is quite dirty actually...

If you try the previously mentioned example, you'll notice that with Chrome, any trailing space will result in a final "&nbsp;" displayed at the end of the bound textarea. The same goes if you input two subsequent spaces. The behavior is quite different in Firefox, but the same kind of issues did show up.

This outlines something important: in those contenteditable stuff, you can actually input any HTML code. Try to copy/paste some component of a webpage into that example, and profit.

Being is this situation is a conceptual mess, at least if as me you're just willing to use this as a fancy input box. So I decided to simplify things, and only keep the plain-text information. But if you want the actual binding not to shock the user, you have to remove the formatting from both the $viewValue and the contenteditable element itself.

And here we're inside an immense no man's land of browser specificities. Handling regular typing is almost fine, however when it comes down to copy/paste, there is no simple solution... I mean, neither Chrome nor Firefox have a "paste" event, but do paste the text with formatting, whereas something like KHTML will just refuse to paste anything...

I started something here, that is (almost) stable for Chrome, kind of works for Firefox and is untested for useless and generally incompatible browsers (but that did invent the contenteditable attribute \o/).

However, I actually discourage the use of contenteditable. Indeed, if you try to copy/paste some DOM including a

+1

+1

Please send a pull request with implementation, tests and docs update.

@IgorMinar: what about HTML pasting? I'm not sure it can be completely prevented, leading to accidental or malicious pasting of arbitrary HTML code, including

+1

+1

Hengjie's implementation works better than the two examples in the doc. It works on arbitrary element, e.g. td.
As for the two examples, using contenteditable and ng-model on td, td does not display the content.

+1

+1

+1

have you seen https://github.com/akatov/angular-contenteditable ? let's contribute to that, and get it into AngularUI..

+1

+1

2 years...

+1

:+1: x :100:

+1

+1, 2 years ago.

+1

That's how I handle pasting while deleting formatting :

document.getElementById("titlesection").addEventListener('paste', function(e){ 
  scope.$apply(function(){
    e.preventDefault();
    var text = e.clipboardData.getData("text/plain");
    document.execCommand("insertHTML", false, text.fulltrim());
  });
});

reedit: evaluating the text content of the editable div on keydown/keyup/keypress should probably work albeit the behaviour is different in some browsers. And changing the content of the editable element when focused can introduce some issues in the handling the caret position as it is reset each time. For example if implementing an autocorrect, it can be problematic.

Alternatively, I managed some 2 way binding with a mutation observer but it's deployed only in the latest browsers. And the issue if I wanted to change the text remains (i.e caret position is reset to the utmost left position). I had trid to play with the range/selection API but it is really too cumbersome to deal with the different way text nodes are handled.

Interesting, it's true that the clipboard API is getting standardized, but can it be considered to be supported widely enough being given the the security implications it has?

+1

One argument for inclusion into angular base: It seems to me that the angular input=text directive already solves many problems that will be encountered by every contenteditable directive regarding user input. Especially the use of the private services $sniffer / $browser to support browsers without oninput must be duplicated by a 3rd party component ($timeout can be used for $browser, but has overhead).

e: it seems like you can inject $browser and $sniffer. So private means 'use at your own risk'?

One argument against it: contenteditable is horribly inconsistent among browsers. See e.g. http://a-software-guy.com/2013/01/a-summary-of-markup-changes-in-contenteditable-divs-in-webkitfirefox-and-msie/ If it is included into angular, there will be a whole slew of issues that only deal with these inconsistencies and feature requests (rich text editor etc.). I'd rather have the core devs focus on more important things.

Though it's hard to make it consistent, I don't see it being a hard problem to solve, especially given that the problem has already been solved by the likes of Aloha-Editor

Where is?

Old hacks doesn't works with angular 1.2.3.

I've been using my version https://github.com/angular/angular.js/issues/528#issuecomment-7573166 on 1.2.x and it's fine. Let me know what doesn't work about it and I can update that script.

Of course Angular should not feature a rich text editor of its own. However, it would be a good idea if contenteditable played nice with ngModel.

I have found myself copying a lot of code from the ngInput directive, notably the code to support ngMaxlength. Could the ngInput directive be extended to include support for contenteditable?

Ping @Hengjie

Yeah, using the ngInput directive is a good start. There'd be other issues we'd worry about:

  • Sometimes an empty looking div actually has p or span or brs in there. I think I have some code somewhere (both backend Ruby and JS) that will clean up empty elements. This is so that we can compare one string against the other and ensure that we save it only if there're actual changes in content.

One of the ways that I check that is by calling isEmpty() with the HTML as the argument:

CoffeeScript:

isEmpty = (el) -> el = $('<div>').html(el) if typeof el is 'string' return $.trim($(el).text()) is ''

Javascript:

var isEmpty;
isEmpty = function(el) {
  if (typeof el === 'string') {
    el = $('<div>').html(el);
  }
  return $.trim($(el).text()) === '';
};

The above relies on jQuery. The code is similar to how Aloha does it

  • Security issues with binding content. I'm using Sanitize Gem to get around people injecting JS into the editable. There's ng.sanitize so that could also work (for pure JS apps)
  • Clicking on contenteditable will fire a focus event but if there are links inside the then you'll want to listen to the click event and stop it's propagation.
  • Deal with enter to save (if you want it to act like an input field).
  • Placeholders

@Hengjie I believe that many of the things you mentioned should probably _not_ be a part of the Angular core. They are a layer on top. Angular should focus on making its directives work with the spec, not patching browser inconsistencies.

In my opinion, an extension of ngInput to include contenteditable would just require feature parity with the other text inputs — support for ngModel and ngMaxlength, for instance.

Side note: If it's any interest to you, I'm ~3 weeks deep into a journey to write my own rich text editor (because TinyMCE et al do not provide enough power for me, and their architecture is years old). I'm currently thinking about how this would integrate with Angular — all that appears to be missing is fundamental support for contenteditable. (It's written to the spec so it doesn't work in IE (they haven't yet implemented the Selection API); that said it does look to patch browser inconsistencies between Chrome/Firefox/Safari. No docs yet either.)

+1

+1 - don't need a full editor, but a nice 2-way binding for text would be nice... 3 years on the open issue.

+1 This would be a great addition

+1

Has anyone looked into the caret position/text node inconsistencies between the different browsers ?

edit: looking at OliverJAsh response above (https://github.com/guardian/scribe), which goes more in depth than what I did myself.. you understand why the issue is still unresolved after 3years. It would be great if there was a quick fix but I also understand that it is not priority for the core team.

Scribe is in production at the Guardian and has been for quite some time, so it's very well tested. Next month there will be documentation so hopefully you can start to use it. I still believe contenteditable has a place in Angular, with model binding and text input attributes (ngMaxlength for example).

Pushed some docs: https://github.com/guardian/scribe

We’re using it in Angular. If you’re interested how I’ll dig out the code, just manually doing the model binding.

+1

@OliverJAsh I'd love to see more details on how you're using this with Angular

I’ve put up an example of using Scribe with Angular. The example is comprehensive and so includes multiple plugins.

https://github.com/guardian/scribe-angular-example

You can preview the example at http://guardian.github.io/scribe-angular-example/.

I’m not happy with the implementation, but it works. I would love to work with everyone here to improve the design and do all of this in the preferred Angular way.

Getting a 404 on https://github.com/guardian/scribe-angular-example

Maybe the repo is private?

@commandtab Try again :-)

This looks really great! I'll start banging on it soon. Thanks!

Looks pretty full featured. Oliver. The demo is awesome.

For those who just need something closer to ng-input element, I have tried to cook a little something :
My example has jquery as a dependency for the key/focus events but it should be fairly easy to do without it (by setting up angular watches on the focus property of a dom element for instance). I have also ignored the trimming of nbsp and trailing breaks etc.. it's a matter of finding the correct regular expression and when to apply it I think.
works in chrome apparently.. haven't tested it elsewhere.

It works with ngModel (it was actually documented in the recent docs).

http://jsfiddle.net/zCbjG/81/

You could have a look at what we've done in textAngular (Full disclosure - I manage the repo for the owner).
Not only is it a rich text editor but we have a directive called taBind (Specific Code Here) we use to bind contenteditables with. It's got a whole heap of browser specific fixes and I use it as a ng-bind-html replacement in several places.

Personally I don't think contenteditable is something the core angular library should support at this time due to it's bad support in browsers. It's getting better slowly but it's really dodgy at the moment, especially when it comes to lists and each browser adding random tags at different times. For example chrome in certain cases likes to add some of the following styles with a span around text when un-applying formatting: <span style="line-height: 1.428571429; color: inherit; line-height: 1.1;">.

For a really simplistic editor the ngModelController example above should suffice, you can ctrl-B for bold, ctrl-I for italics etc. Anything more complex than that requires you either use libraries like Rangy or start playing with execCommand as we've done in textAngular.

On the other hand, if you did make something like this a core part of Angular I'd be happy to help out with PR's etc as then I could possibly make textAngular lighter!!

I like the scribe module and their angular demo --- maybe it would be worth endorsing that rather than trying to support it natively.

+1

+1

:+1:

+1

+1

+1

+1

+1

+1

+1

:heavy_plus_sign: 1

+1

+1

:+1: for the solution from @atd86 http://jsfiddle.net/zCbjG/81/

👍

+1

Hi everyone,
binding to contenteditable is not something we are going to support in core:

  • handling contenteditable in a robust and comprehensive way is very complex
  • Official support would take away a lot of resources from the (small) team that is handling AngularJs 1.x nowadays.
  • However, even with a larger team, contenteditable would be a huge project that would demand constant attention
  • We would have to duplicate / include a lot of code that has already been written by others that handles the various browser differences and quirks.
  • "binding to contenteditable" is not a clearly delineated concept. Support for "simple" binding will grow bigger as browser differences need to be handled, and might raise demand for complete WYSIWYG / rich text editors

Fortunately, there are already projects that handle contenteditable and rich text editors, some that are wrapped to integrate with Angular, and some that are specifically written for it. I didn't have time to test all projects, I just want to highlight textAngulars taBind directive, which handles "simple" contenteditable two-way binding with ngModel. This is as far as I can say the scope of this issue: no fancy editor, but simply a way to have a robust contenteditable that handles different input techniques and browser differences. I've created a plunker that shows a simple contenteditable that has been enhanced with taBind: http://plnkr.co/edit/kKPfk0LCXWrMpZ1gkblb?p=preview (textAngular also provides a full editor if you need one).

Was this page helpful?
0 / 5 - 0 ratings