10

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

5
  • How about ng-transclude? (docs.angularjs.org/api/ng.directive:ngTransclude) Commented Jan 6, 2014 at 15:21
  • Can you use transclude with no template? Commented Jan 6, 2014 at 15:26
  • Nope, you need at least an inline one (I think this should do it template: '<h1 ng-transclude></h1>') Commented Jan 6, 2014 at 15:28
  • 1
    Transclude sounds like what I am after but can't seem to get it to work. Could you add an answer and "fill in the blanks" in the above question?? Thanks Commented Jan 6, 2014 at 15:50
  • The reason you are doing this is because you want to have markdown along with other custom elements? I am also trying something similar where I need to have radio button control and textbox along with the content. I am thinking to follow the similar structure. is it recommended? Commented Aug 4, 2015 at 16:31

4 Answers 4

8

Here you go!

Working Demo

app.directive('markdown', function() {
  return {
    restrict: 'E',
    transclude: true,
    compile: function(elem) {
      elem.replaceWith(Markdowner.transform(elem.html()));
    }
  }
});
Sign up to request clarification or add additional context in comments.

2 Comments

Turns out that this approach is deprecated in angular 1.2 though. ;(
What part is deprecated? Would this be an acceptable solution based on yours? plnkr.co/edit/44nAgaudUPCkd2Cw3hdp?p=preview
6

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.

2 Comments

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
Good thinking, it's a lot more useful with bindings.
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

2 Comments

I've edited the question and added comments to the code. Would this handle <markdown>{{heading}}</markdown> ?
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.
0

On your link function, AngularJS already parsed your HTML and replaced the contents by your template (which in your case is missing, since you have "replace" set to true).

You can grab the inner html content from the $element, which is a jQuery (jQLite) element.

var contents = $element.innerHTML;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.