0

I have a directive that wraps a pure-JS library. I want to add another directive I have and combine the two.

Currently I have:

Directive

app.directive('voPiechart', ['$window', '$compile',
    function ($window, $compile) {
        return {
            restrict: 'E',
            scope: {
                isReady: '=',
                data: '=',
                clickEvent: '='
            },
            link: function ($scope, $elm) {
                var svg = d3.select('#' + $elm[0].id)
                    .append("svg")
                    .append("g");

                svg.append("g")
                    .attr("class", "slices");
                svg.append("g")
                    .attr("class", "labels");
                svg.append("g")
                    .attr("class", "lines");

                /* more directive logic */

                $scope.unselectSlice = function () {
                    $scope.clickEvent(null);
                }
            }
        };
    }
]);

HTML

<vo-piechart id="pie-chart" data="data" is-ready="!isBusy" click-event="sliceSelected" />

Now, I want to add a directive called outside-click that notifies me whenever I click outside an element. In this case - the SVG element that d3 creates.

I know adding directives to a directive should usually be done in the compile function, but in this case I can't since I need the link to continue work.

I've tried this:

svg.attr('on-outside-click', '{{unselectSlice()');
svg.attr('watch-outside-click', 'true');
$compile(svg)($scope);

It doesn't work and I seem to be missing something in understanding how directives work.

I looked at other similar questions but couldn't correlate them to my issue

--> A working plunkr <--

Thanks

AlexD
  • 4,062
  • 5
  • 38
  • 65
  • I was looking at how to do add a attr-restrict-directive dynamically as well, I didn't get an answer. I'm interested to see what answers you get. Best of luck. – Lightfooted May 19 '16 at 10:12
  • When you `$compile`, do it with `$scope` because it should know from where to get properties or methods. In this case `$compile(svg)($scope)`, though this might not be the issue. BTW I'm not seeing you provided info for 2nd directive. – Bettimms May 19 '16 at 10:21
  • @Lightfooted Do upvote the questions that you think is good/interesting so that it gets more attention (*hopefully*) – T J May 19 '16 at 10:26
  • @Bettimms thanks edited, though still doesn't work. – AlexD May 19 '16 at 10:31
  • I think you should use native d3 methods for that. It has a click event on its own. – Chris Hermut May 19 '16 at 12:29
  • Sure, but what I need is a click out event – AlexD May 19 '16 at 12:29

2 Answers2

0

I ended up registering for each slice click event AND for the entire svg click event and handling it myself (without the additional directive).

Here is a working fiddle: http://jsfiddle.net/Lvc0u55v/4116/

.on('click', function(item, index, alwayzZero) {
    if (selectedThis) {
        d3.select(selectedThis).transition().attr("d", arc);
        selectedThis = null;
        selectedSlice = null;
    }
})

I would still appreciate knowing the answer to the original question, though.

AlexD
  • 4,062
  • 5
  • 38
  • 65
0

What about this approach ( I didn't remove the rest of events, maybe they are not needed anymore)

var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', ['$scope', '$timeout', function($scope, $timeout) {
    $scope.data = [{
      label: "Neutral",
      value: 20,
      color: "#FF9E3B",
      from: "-0.3",
      to: "0.3",
      name: "-0.3-0.3"
    }, {
      label: "Negative",
      value: 40,
      color: "#FD2626",
      to: "-0.3",
      name: "*--0.3"
    }, {
      label: "Positive",
      value: 10,
      color: "#0DCF71",
      from: "0.3",
      name: "0.3-*"
    }];

    $scope.sliceSelected = function(data, index) {
      $timeout(function() {
        $scope.selectedSentiment = data != null ? $scope.data.find(function(item) {
          return item.label == data.label;
        }) : null;
      });
    }
    
    $scope.directiveRunTimes = 0;
    
    $scope.thisDirectiveWorks = function() {
       $scope.directiveRunTimes++;
    }
    
  }])
  .directive('onOutsideClick', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            var stopProp;
            attr.$observe('watchOutsideClick', function (value) {
                //console.log("observed: " + value);
                if (attr.onOutsideClick && attr.watchOutsideClick && scope.$eval(attr.watchOutsideClick)) {
                    $(document).bind('click', function(event) 
                    {
                      if(event.target.nodeName === "path") {
                        event.stopPropagation();
                        return false
                      }
                        console.log("Clciked Anywhere");
                        scope.$apply(function() {
                            scope.$eval(attr.onOutsideClick);
                        });
                    });
                    $(element).bind('click', function(event) {
                        //console.log("In-element click");
                        event.stopPropagation();
                    });
                } else {
                    $(document).unbind('click');
                    $(element).unbind('click');
                }
            });
        }
    }
})
  .directive('voPiechart', ['$window', '$compile',
    function($window, $compile) {
      return {
        restrict: 'E',
        scope: {
          isReady: '=',
          data: '=',
          clickEvent: '='
        },
        link: function($scope, $elm) {
          var container = $($elm[0]).parent();
          var data, width, height, radius, svg, pie, arc, outerArc, key, color;

          $scope.$watch('isReady', function(newVal) {
            if (newVal) {
              if (svg) {
                change(true);
              } else {
                setTimeout(function() {
                  createPiechart();
                }, 100);
              }
            }
          });

          $scope.$watch(function() {
            return $window.outerWidth;
          }, function(newVal, oldVal) {
            if (newVal !== oldVal) {
              createPiechart();
            }
          });

          function createPiechart() {
            duplicateData();

            if (svg) {
              svg.remove();
              $('#' + $elm[0].id).empty();
            }

            updateDimensions();

            /****************************** Important code here! ************************************/

            svg = d3.select('#' + $elm[0].id)
              .append("svg")
              .append("g");

            svg.attr('on-outside-click', '{{unselectSlice()');
            svg.attr('watch-outside-click', 'true');
            $compile(svg)($scope);

            /****************************** end Important code here! ************************************/ 

            svg.append("g")
              .attr("class", "slices");

            pie = d3.layout.pie()
              .sort(null)
              .value(function(d) {
                return d.value;
              });

            arc = d3.svg.arc()
              .outerRadius(radius * 0.8)
              .innerRadius(radius * 0.65);

            outerArc = d3.svg.arc()
              .innerRadius(radius * 0.9)
              .outerRadius(radius * 0.9);

            svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
            change(false);
          }

          function updateDimensions() {
            if (container.width() > 0) width = container.width();
            if (container.height() > 0) height = container.height();
            if (Math.min(width, height) / 2 > 0) radius = Math.min(width, height) / 2;
          }

          function getTotalValues() {
            return data.map(function(item) {
              return item.value;
            }).reduce(function(prev, current) {
              return prev + current;
            }, 0);
          }

          function duplicateData() {
            data = $scope.data.filter(function(item) {
              return item.value > 0;
            });

            if (data.length == 0) {
              data.push({
                label: '$$empty',
                value: 1,
                color: '#f7f8f8'
              })
            }
          }

          function change(shouldDuplicateData) {
            if (shouldDuplicateData) {
              duplicateData();
            }

            var totalValues = getTotalValues();

            key = function(d) {
              return d.data.label;
            };
            color = d3.scale.ordinal()
              .domain(data.map(function(item) {
                return item.label;
              }))
              .range(data.map(function(item) {
                return item.color;
              }));

            /* ------- PIE SLICES -------*/
            var slice = svg.select(".slices").selectAll("path.slice")
              .data(pie(data), key);

            //watch-outside-click="{{ !rangePicker.startDateCollapse || !rangePicker.endDateCollapse }}" o on-outside-click="rangePicker.startDateCollapse = true; rangePicker.endDateCollapse = true;"
            slice.enter()
              .insert("path")
              .style("fill", function(d) {
                return color(d.data.label);
              })
              .attr("class", "slice");

            slice.on("click", function(item, index, alwaysZero) {
              $scope.clickEvent(item.data, index);
            });

            slice
              .transition().duration(1000)
              .attrTween("d", function(d) {
                this._current = this._current || d;
                var interpolate = d3.interpolate(this._current, d);
                this._current = interpolate(0);
                return function(t) {
                  return arc(interpolate(t));
                };
              });

            slice.exit()
              .remove();

            if (data.length == 1 && data[0].label == '$$empty') {
              var text = svg.select(".labels").selectAll("text")
                .remove();
              var polyline = svg.select(".lines").selectAll("polyline")
                .remove();
              return;
            }

          }

          /****************************** Important code here! ************************************/

          $scope.unselectSlice = function() {
            console.log('It worked!');
            alert('workssss');
            $scope.clickEvent(null);
          }
          
          /****************************** end Important code here! ************************************/
        }
      };
    }
  ]);
.pie-chart-container {
        position: absolute;
        top: 5px;
        bottom: 5px;
        left: 5px;
        right: 5px;
}

.pie-chart-container svg {
            width: 100%;
            height: 100%;
        }

.pie-chart-container path.slice {
            stroke-width: 2px;
        }

.pie-chart-container polyline {
            opacity: .3;
            stroke: black;
            stroke-width: 2px;
            fill: none;
        }
    
<!DOCTYPE html>
<html>

<head>
  <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
  <script src="https://code.angularjs.org/1.4.7/angular.min.js"></script>
  <script src="http://d3js.org/d3.v3.js"></script>
  <link rel="stylesheet" href="style.css">
  <script src="script.js"></script>
</head>

<body ng-app="myApp">

  <div class="pie-chart-container" ng-controller="myCtrl">
    <button on-outside-click="thisDirectiveWorks()" watch-outside-click="true"> mooooooooooooooo {{directiveRunTimes}}</button>
    <div>{{selectedSentiment.label}}</div>
    <vo-piechart id="pie-chart" data="data" is-ready="!isBusy" click-event="sliceSelected"></vo-piechart>
  </div>
</body>

</html>
Bettimms
  • 671
  • 5
  • 12