22

My goal is to create an editable directive that allows a user to edit HTML of any element to which the attribute is attached (see Plunker: http://plnkr.co/edit/nIrr9Lu0PZN2PdnhQOC6)

This almost works except I can't get the original raw HTML of the transcluded content to initialize the text area. I can get the text of it from clone.text(), but that's missing the HTML tags like <H1>, <div>, etc. so clicking apply with no edits is not idempotent.

The method clone.html() throws an error, Cannot read property 'childNodes' of undefined

app.directive("editable", function($rootScope) {
  return {
    restrict: "A",
    templateUrl: "mytemplate.html",
    transclude: true,
    scope: {
      content: "=editContent"
    },

    controller: function($scope, $element, $compile, $transclude, $sce) {

      // Initialize the text area with the original transcluded HTML...
      $transclude(function(clone, scope) {

        // This almost works but strips out tags like <h1>, <div>, etc.
        // $scope.editContent = clone.text().trim();

        // this works much better per @Emmentaler, tho contains expanded HTML
        var html = ""; 
        for (var i=0; i<clone.length; i++) {
            html += clone[i].outerHTML||'';}
        });
        $scope.editContent = html;

      $scope.onEdit = function() {
        // HACK? Using jQuery to place compiled content 
        $(".editable-output",$element).html(
          // compiling is necessary to render nested directives
          $compile($scope.editContent)($rootScope)
        );
      }

      $scope.showEditor = false;

      $scope.toggleEditor = function() {
        $scope.showEditor = !$scope.showEditor;
      }         
    }
  }
});

(This question is essentially a wholesale rewrite of the question and code after an earlier attempt to frame the question, Get original transcluded content in Angular directive)

Community
  • 1
  • 1
prototype
  • 7,249
  • 15
  • 60
  • 94
  • 2
    `clone` is a collection of elements. Have you been able to inspect it in a debugger? – Nathaniel Johnson Nov 20 '13 at 17:22
  • Aha! Iterating over them and appending outerHTML is much closer: `var text = ""; for (var i=0; i` it shows `

    Clock

    {{time}}

    `. In this example, the clock doesn't transclude content, so the net effect is the same. I wonder if it's possible to get the original HTML?
    – prototype Nov 20 '13 at 17:48
  • 1
    I suspected it might be the case. Good deal. The original HTML might be sitting in the $element object in the outer scope. Transclusion is not my strong suit. – Nathaniel Johnson Nov 20 '13 at 17:57
  • why aren't you wrapping original content in an element with a class that you can use to find it and keep it isolated in one container? – charlietfl Nov 20 '13 at 18:34
  • 1
    if it was me would just add textarea/editor on demand like with doubleclcik. Append when needed. – charlietfl Nov 20 '13 at 18:45
  • @user645715 Did you able to figure how to get the 'original transcluded markup'? – manikanta Jun 14 '14 at 21:56
  • Sorry, no, despite a fair effort. I then implemented the properties editor using JSON, which mapped directly to jQuery "data-" properties. (And because the project had lots of nested views, ended up porting it to Backbone.js for simple control over how/when it was rendered) – prototype Jun 16 '14 at 03:18

1 Answers1

3

The $element.innerHTML should contain the original HTML. I am showing that it contains

  <div class="editable">
  <span class="glyphicon glyphicon-edit" ng-click="toggleEditor()"></span>

    <div class="editable-input" ng-show="showEditor">
       <b><p>Enter well-formed HTML content:</p></b>
       <p>E.g.<code>&lt;h1&gt;Hello&lt;/h1&gt;&lt;p&gt;some text&lt;/p&gt;&lt;clock&gt;&lt;/clock&gt;</code></p>
       <textarea ng-model="editContent"></textarea>
       <button class="btn btn-primary" ng-click="onEdit()">apply</button>
    </div>

    <div class="editable-output" ng-transclude=""></div>
  </div>
shoebox639
  • 2,312
  • 15
  • 14
Nathaniel Johnson
  • 4,731
  • 1
  • 42
  • 69
  • 3
    Very helpful. That contains the template text. Your answer did spark me to add the method `compile(element, attrs)` which has the transcluded HTML in its `innerHTML`. But it's after Angular expanded it into new DOM elements using the template. That'll break if any sub-elements have `replace:true` or transclude content themselves. I suspect there's no way to get Angular to give the HTML from before Angular processed it, and thus the starting content would need to be bootstrapped not via transclusion but passed say via an attribute or ajax. – prototype Nov 20 '13 at 18:27
  • This gets into the guts of the digest cycle. By setting the priority arbitrarily high, you might be able to ensure that it is evaluated first. – Nathaniel Johnson Nov 20 '13 at 18:51
  • How to get the uncompiled transclude content of an Angular directive if I using `replace:true` with template. it's mean `element` not contain the raw orginal content beacuse `element`is template element not orginal element – pery mimon Sep 07 '15 at 14:07