AngularJS directive to parse and replace custom element contents

22,005

Solution 1

Here you go!

Working Demo

app.directive('markdown', function() {
  return {
    restrict: 'E',
    transclude: true,
    compile: function(elem) {
      elem.replaceWith(Markdowner.transform(elem.html()));
    }
  }
});

Solution 2

ngTransclude is specifically designed for this.

myModule.directive('heading', function() {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: true,
        template: '<h1 ng-transclude></h1>'
    };
}

Then use it like this:

<heading><span>{{foo}}</span></heading>

Here's a working fiddle (angular 1.2.7).

Also, I'm guessing you need some sort of markdown integration. Here's a version using transclude so that you end up with a div container.

This one skips the whole transclude behavior and I'm guessing it's closer to what you're after.

Solution 3

You can get and set the compiled contents of the element in the link function using:

element.html() //get
element.html("blah") //set

Here is a sample based on Sergiu's sample below that processes the bindings contained within the html using scope.$eval(), before calling the markdown converter:

http://jsfiddle.net/edeustace/4rE85/1/

angular.module('transclude', [])
 .directive('markdown', function() {

  var regex = /\{\{(.*?)\}\}/;

  var converter = new Showdown.converter();

  return {
    restrict: 'E',
    replace: true,
    scope: true,
    link: function (scope, element) {

      var processTemplate = function(text){
        var results = text.match(regex);
        if(!results){
          return text;
        } else {
          var value = scope.$eval(results[1]);
          var replaceKey = new RegExp("{{" + results[1] + "}}","g");
            text = text.replace(replaceKey, value);
            return processTemplate(text);
        }
     };
     var text = element.text();
     var processed = processTemplate(text);
     var markdownText = converter.makeHtml(processed);
     element.html(markdownText);
    }

  };
});

which will work with:

<markdown>
# Bar {{foo}} {{foo}}
# {{bing}}
</markdown>

Or you can bind it to an attribute that you can then use in your directive:

app.directive('markdownWithBinding', function () {

  var converter = new Showdown.converter();

  return {
    restrict: 'E',
    scope: {
      'md' : '@'
    },
    link: function  ($scope, $element, $attrs) {

      $scope.$watch('md', function(newMd){

        var markdownText = converter.makeHtml(newMd);
        element.html(markdownText);

      });
    }
  }
});

Used like so:

<markdown-with-binding md="Hello {{name}}"></markdown-with-binding> 
<!-- outputs Hello World!!! -->

Old Answer

This will happen in link() which is for linking the scope to the element. For structural changes where no scope is required you may be better off making your changes in the compile function:

app.directive('markdown', function () {

var link = function ($scope, $element, $attrs) {};
return {
    restrict: 'E',
    replace: true,
  compile: function($element, $attrs, $transclude){

    if($element.html() == "#Hello"){
      $element.html("<h1>Hello</h1>");
    }
    return link;
  },
}

});

Here's a great tutorial on components: http://www.youtube.com/watch?v=A6wq16Ow5Ec

Share:
22,005
Greg
Author by

Greg

Blaze UI - Open Source Modular UI Toolkit

Updated on July 09, 2022

Comments

  • Greg
    Greg almost 2 years

    I would like to create a simple markdown directive that accepts some content within the element, parses it and replaces it with html.

    So this:

    <markdown>#Heading</markdown>
    

    or this (where $scope.heading = '#Heading';)

    <markdown>{{heading}}</markdown>
    

    Becomes this:

    <h1>Heading</h1>
    

    My directive so far (obviously not complete!):

    .directive('markdown', function () {
        return {
            restrict: 'E',
            replace: true,
            link: function ($scope, $element, $attrs) {
                // Grab contents
                    var contents = /* How do I do this? */
    
                    var newContents = Markdowner.transform(contents);
    
                    // Replace <markdown> element with newContents
                    /* How do I do this? */
            }
        }
    })
    

    I'm unsure of how to grab the contents of the directive? Would I need to compile it?!

    Parsing Markdown is just an example

  • Greg
    Greg over 10 years
    I've edited the question and added comments to the code. Would this handle <markdown>{{heading}}</markdown> ?
  • ed.
    ed. over 10 years
    added update about binding - binding to the markup contents as you have it here can't be done out of the box, but I'm sure is achievable.
  • ed.
    ed. over 10 years
    I think Sergiu is on the right track (although the last sample doesn't work with bindings) - I've forked his last sample and added a function to process the bindings. jsfiddle.net/edeustace/4rE85/1
  • jessegavin
    jessegavin over 10 years
    Turns out that this approach is deprecated in angular 1.2 though. ;(
  • Sergiu Paraschiv
    Sergiu Paraschiv over 10 years
    Good thinking, it's a lot more useful with bindings.
  • Greg
    Greg over 10 years
    What part is deprecated? Would this be an acceptable solution based on yours? plnkr.co/edit/44nAgaudUPCkd2Cw3hdp?p=preview
  • jessegavin
    jessegavin over 10 years
    Yep. Updating my answer. Thanks.