From 715d3d1ee856c961c697217f16c68bca74ef6d92 Mon Sep 17 00:00:00 2001
From: Giovanni Silva <giovanni@atende.info>
Date: Fri, 10 Jan 2014 21:45:46 -0200
Subject: [PATCH] feat(directives): Add support for contenteditable with
 ng-model

---
 lib/directive/module.dart         |  1 +
 lib/directive/ng_model.dart       | 20 ++++++++++++++++++++
 test/directive/ng_model_spec.dart | 27 ++++++++++++++++++++++++++-
 3 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/lib/directive/module.dart b/lib/directive/module.dart
index 4ae39ab14..1190e923e 100644
--- a/lib/directive/module.dart
+++ b/lib/directive/module.dart
@@ -58,6 +58,7 @@ class NgDirectiveModule extends Module {
     value(TextAreaDirective, null);
     value(InputSelectDirective, null);
     value(OptionValueDirective, null);
+    value(ContentEditableDirective, null);
     value(NgModel, null);
     value(NgSwitchDirective, null);
     value(NgSwitchWhenDirective, null);
diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart
index 00eea02a7..2f6406fc8 100644
--- a/lib/directive/ng_model.dart
+++ b/lib/directive/ng_model.dart
@@ -325,3 +325,23 @@ class InputRadioDirective {
     });
   }
 }
+
+/**
+ * Usage (span could be replaced with any element which supports text content, such as `p`):
+ *
+ *     <span contenteditable= ng-model="name">
+ *
+ * This creates a two way databinding between the expression specified in
+ * ng-model and the html element in the DOM.  If the ng-model value is
+ * `null`, it is treated as equivalent to the empty string for rendering
+ * purposes.
+ */
+@NgDirective(selector: '[contenteditable][ng-model]')
+class ContentEditableDirective extends _InputTextlikeDirective {
+  ContentEditableDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
+      super(inputElement, ngModel, scope);
+
+  // The implementation is identical to _InputTextlikeDirective but use innerHtml instead of value
+  get typedValue => (inputElement as dynamic).innerHtml;
+  set typedValue(String value) => (inputElement as dynamic).innerHtml = (value == null) ? '' : value;
+}
diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart
index 1f63ba402..f2bded141 100644
--- a/test/directive/ng_model_spec.dart
+++ b/test/directive/ng_model_spec.dart
@@ -44,7 +44,6 @@ describe('ng-model', () {
       var input = probe.directive(InputTextDirective);
       input.processValue();
       expect(_.rootScope.model).toEqual('def');
-
     }));
 
     it('should write to input only if value is different', inject(() {
@@ -438,5 +437,31 @@ describe('ng-model', () {
       expect(blueBtn.checked).toBe(false);
     }));
   });
+  
+  describe('contenteditable', () {
+    it('should update content from model', inject(() {
+      _.compile('<p contenteditable ng-model="model">');
+      _.rootScope.$digest();
+
+      expect((_.rootElement as dom.Element).text).toEqual('');
+
+      _.rootScope.$apply('model = "misko"');
+      expect((_.rootElement as dom.Element).text).toEqual('misko');
+    }));
+
+    it('should update model from the input value', inject(() {
+      _.compile('<p contenteditable ng-model="model">');
+      Element element = _.rootElement;
+
+      element.innerHtml = 'abc';
+      _.triggerEvent(element, 'change');
+      expect(_.rootScope.model).toEqual('abc');
+
+      element.innerHtml = 'def';
+      var input = ngInjector(element).get(ContentEditableDirective);
+      input.processValue();
+      expect(_.rootScope.model).toEqual('def');
+    }));
+  });
 
 });