0

This is a slightly long one.

How can I have child directives that are aware of all of their ancestors, not just their immediate parent?

The reason I'm asking is that I want to have a Raphael paper directive that provides a reference to a Raphael paper to all its children. Also, I'm trying to have a "rl-shape" directive that can group different shapes together so they can be translated, transformed, etc. together all at once. I'd also like this "rl-shape" directive to be able to appear inside of itself, allowing for arbitrary trees of shapes.

There might be a completely different, better way to do this. If so, please correct me.

Here's the code I have so far:

<!doctype html>
<html xmlns:ng="http://angularjs.org" ng-app="myApp">
  <head>
    <title>Test</title>

    <script src="js/underscore.js"></script>
    <script src="js/raphael-min.js"></script>
  </head>

  <body>
    <rl-paper>
      <rl-shape name="myShape">
        <rl-circle name="inner" cx="0" cy="0" r="50"></rl-circle>
        <rl-circle name="outer" cx="0" cy="0" r="100"></rl-circle>
      </rl-shape>
    </rl-paper>

    <p>
      <button ng-click="myShape.translate(0, -10)">Move shape up</button>
      <button ng-click="myShape.translate(0, 10)">Move shape down</button>
    </p>

    <script src="js/angular.min.js"></script>
    <script>
      var myApp = angular.module("myApp", []);

      function Shape(children) {
        this.translate = function(dx, dy) {
          _.each(children, function(c) { c.translate(dx, dy); });
        };
      }

      myApp.directive("rlPaper", function() {
        return {
          restrict: "E",
          controller: function($element) {
            this.paper = new Raphael($element[0], 220, 220);
            this.paper.setViewBox(-110, -110, 220, 220);
          }
        };
      });

      myApp.directive("rlShape", function () {
        return {
          restrict: "E",
          require: ["^rlPaper"],
          controller: function($scope, $element, $attrs, $transclude) {
            this.children = [];
          },
          link: function(scope, element, attrs, ctrls) {
            // How can the link function of the rlShape directive access its
            // own controller?  If I could figure that out, I could do
            // something like the following:
            var shapeCtrl = undefined;  // Don't know how to get this

            var shape = Shape(shapeCtrl.children);
            scope[attrs.name] = shape;
          }
        };
      });

      myApp.directive("rlCircle", function() {
        return {
          restrict: "E",
          require: ["^rlPaper", "?^rlShape"],
          link: function(scope, element, attrs, ctrls) {
            var paperCtrl = ctrls[0];
            var shapeCtrl = ctrls[1];

            var circle = paperCtrl.paper.circle(attrs.cx, attrs.cy, attrs.r);
            scope[attrs.name] = circle;

            if ( shapeCtrl ) {
              shapeCtrl.children.push(circle);
            }
          }
        };
      });
    </script>
  </body>
</html>
David Sanders
  • 4,069
  • 1
  • 24
  • 38

1 Answers1

3

(You have quite a few questions in your question. You'll get better, and likely faster, answers, if you post separate questions for each, with a minimalistic fiddle or plunker demonstrating the problem/question.)

How can I have child directives that are aware of all of their ancestors, not just their immediate parent?

If a directive does not create an isolate scope (in the sample code you provided, none of your directives are doing this), then the directive has access to all ancestors' $scopes via normal JavaScript prototypal inheritance.

So if you define your data on your directives' $scopes, the child directives will be able to access that data directly:

E.g., if you have this code in a parent directive's controller function:

$scope.paper = new Raphael($element[0], 220, 220);

Then child directives can access it (in their controller or link functions):

var paper = $scope.paper;  // prototypal inheritance will find it

I want to have a Raphael paper directive that provides a reference to a Raphael paper to all its children.

So, in the rlPaper directive, define a "Raphael paper" object on that directive's $scope (not on this). Do this in the directive's controller function, not the link function, because the link function will run too late. (Fiddle showing when directive's controller and link functions run, with two nested directives.)

How can the link function of the rlShape directive access its own controller?

There is a way to do this, but I think the better way is to put your data and functions on the directive's $scope, which is shared by the directive's link and controller functions.

In the rlCircle directive, you don't have to require other parent controllers if you instead put your data onto the $scope objects.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Mark, thanks a lot for your thoughtful reply. You're right that I should have split my post up into smaller questions. I apologize for that. The fiddle you provided clarified a lot of things for me. I actually managed to answer a lot of my own questions since posting this and I had adopted an approach like what you're suggesting, putting the data on the scope instead of the controller object. Anyhow, thanks again for taking the time to decipher all my questions and answer them! – David Sanders Apr 04 '13 at 16:57