2

I'm not sure the title is even correct. What I'm doing is a little over my head as I'm fairly new to Angular.

I'm trying to have a directive run which comes from a bound value. The directive returns HTML. My problem is trying to get that HTML to display. I think I'm missing the concept of transclude (as I'm not using it), I'm all screwed up.

HERE IS A PLUNK: http://plnkr.co/edit/fRhpsS97IGbTWtDPCMlI?p=preview

I have data in my scope that looks like this:

$scope.data = [{
        details: 'IUt euismod tellus, at posuere sem. <code lang="java">int foo = 21;</code> Nullam mattis ac elit in gravida.'
      }, 
      {
        details: 'BHA BHA BHA e sem. <code lang="java">int wutWut = 56;</code> Nullam mattis ac elit in gravida. Sed nec ipsum sed urna.'
      }
    ];

I'm displaying the data like this:

<pre ng-repeat="item in data">
  {{item.details}}
</pre>

That all works fine, but the problem is that I need to have the <code> tags (from item.details) processed as a directive. Said directive should read the lang attribute and properly highlight the contents of the element with the Sunlight Syntax Highlighter which is included.

So here's the directive for <code>

.directive('code', ['$filter',
    function($filter) {
      return {
        restrict: 'E',
        replace: true,
        scope: {
          lang: '@'
        },
        controller: ['$scope','$element',
          function($scope, $element) {
            // default lang
            if (typeof $scope.lang === 'undefined')
              $scope.lang = 'java';

            $scope.src = $element.html();
          }
        ],
        template: '<span>{{src | codeHighlight:lang}}</span>'
      };
    }
  ]);

which basically just passes the the bits to the codeHighlight filter. The reason I pass it to a filter is because there's another point in my application where the highlighting method is done without a <code> tag so it works well for what I'm doing (minimal code repeat). I'm thinking I'm supposed to use the transclude option and the $filter service but I'm not 100% sure how to get the value between the tags.

Here's my codeHighlight filter. This does the highlighting using the Sunlight lib. So it returns HTML as a string. When I use it directly I usually do something like this: ng-bind-html="foo.bar | codeHighlight | safeHtml"

.filter('codeHighlight', function() {

    return function(code, lang) {
      // Default lang
      if (typeof lang === 'undefined')
        lang = 'java';

      // Grab a highlighter
      var highlighter = new Sunlight.Highlighter();
      var context = highlighter.highlight(code, lang);
      var el = angular.element(context.getNodes());
      var html = el.wrap('<p/>').parent().html();

      // Convert nbsp's to actual spaces (so line breaks work better)
      html = html.replace(/&nbsp;/gi, ' ');

      return html;
    };

  })

Here's the safeHtml filter just for sanity, but doesn't really apply to my question

.filter('safeHtml', ['$sce',function($sce){
    return function(html){
      return $sce.trustAsHtml(html);
    };
  }])

So all that would work, except the original data.details is never processed because Angular doesn't process (What's the right word? I know it's not process) the values spit to the screen (for obvious reasons)

So I modify the output and create a directive to recompile the output value. I use the compile attribute because I don't understand how to properly pull the linked? data from between the tags.

<pre ng-repeat="item in data" compile="item.details"></pre>

And here's the compile directive

.directive('compile', ['$compile',
    function($compile) {
      return {
        restrict: 'A',
        link: function(scope, element, attrs) {
          scope.$watch(attrs.compile, function(html) {
            element.html(html);
            $compile(element.contents())(scope);
          });
        }
      }
    }
  ])

So now everything works!!! neato! except my html is displayed as a string because Angular sanitizes output. Unfortunately the only way I know how to print raw HTML to the page is with the ng-bind-html directive which I can't do because I'm using my stupid compile directive.

I thought maybe in the watch function I could append element.html(element.html()) after the $compile function, but that just returns the raw template.

Obviously I'm stepping into a realm that I don't completely understand.

My dream would be to have the pre tag setup like in the first example:

<pre ng-repeat="item in data" compile="item.details">
  {{item.details}}
</pre>

as it kinda looks proper, but I'm begging here not choosing. If anyone could help me even a little I would be extremely grateful. I'm completely stuck and I don't even know what to Google anymore.

Skinner927
  • 953
  • 2
  • 13
  • 25
  • have you tried `element.replaceWith`? [see this question](http://stackoverflow.com/questions/14357945/how-to-make-angularjs-compile-the-code-generated-by-directive) – Joseph Yaduvanshi Apr 05 '14 at 01:20

2 Answers2

0

Wow you really missed SCE here.
Import the $sce dependency, which stands for "Strict Contextual Escaping." This means you can tell Angular.js to echo HTML instead of sanitizing it.
For example, $scope.bacon = $sce.trustAs('html', '<b>bacon!</b>'), then <div ng-bind-html="bacon"></div> will result in bacon!

Zoey Mertes
  • 3,139
  • 19
  • 23
-1

Awesome piece of code, all you need is just disable SCE for the string to be processed.

angular.module('moduleName').config(function($sceProvider) {
    $sceProvider.enabled(false);
})