0

I've been trying to create a directive which I can arbitrarily add to an existing form (as attribute), which makes the form become a popover upon clicking on a nearby trigger link. I've got the directive to work once, but once I click the link again, the underlying data does not change and the buttons (eg. 'close') stop working.

A plunker can be found here: http://plnkr.co/edit/2Zyg1bLearELpofeoj2T?p=preview

Steps to reproduce: 1. Click on link, 2. Change text (note that link text changes as well), 3. Click close (ok doesn't do the right thing at current), 4. Click on link again, 5. Try to change text/click close, but nothing works...

I've read that a problem is that popovers in bootstrap are detached/attached to the DOM, but I don't know how I could resolve this issue anyway. I also would like to avoid third party libraries (such as angular-ui), as I'd like to avoid the overhead.

Any help is greatly appreciated.

Update Thanks to Vasaka's hint, I was able to progress a little further. The problem slightly changed in that the nested directive now does not seem to benefit from the $compile, i.e. I don't believe it is attached to the scope.

To reproduce the behaviour, click on the date (plunker link below), click on the date in the popover (date should increment) and close the popover. Repeating the steps again, you will notice that incrementing the date doesn't work any more. I tried to add $compile(element.contents())(scope) in an attempt to also compile the nested directive simple-date-picker, but this didn't resolve the issue.

Here's the updated plunker: http://plnkr.co/edit/2Zyg1bLearELpofeoj2T?p=preview

And the updated code:

<!DOCTYPE html>
<html>

  <head>
    <link data-require="bootstrap-css@*" data-semver="3.0.3" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" />
    <script data-require="[email protected]" data-semver="1.9.1" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script data-require="[email protected]" data-semver="2.3.2" src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
    <script data-require="[email protected]" data-semver="1.2.5" src="http://code.angularjs.org/1.2.5/angular.js"></script>

    <style>
    body {margin-top:40px; margin-left:40px;}
    </style>
    <script>
      var module = angular.module('module', []);

      module.directive('simpleDatePicker', function($compile) {
        return {
          restrict: 'E',
          scope: {
            date: '='
          },
          replace: true,
          template: '<div ng-click="date.setDate(date.getDate()+5)"> {{ date }} </div>',
        }
      });

      module.directive('myForm', function() {
        return {
          restrict: 'E',
          scope: {
            popover: '=?',
            value: '='
          },
          transclude: true,
          replace: true,
          template:
            '<div>' +
              '<a href="" ng-transclude></a>' +
              '<form ng-submit="submit($event)" ng-hide="popover && !formVisible" ng-attr-popover="{{ popover }}" class="form-inline">' +
                '<simple-date-picker date="value"></simple-date-picker>' +
                '<div ng-hide="!popover">' +
                  '<button type="submit" class="btn btn-primary">OK</button>' +
                  '<button type="button" class="btn" ng-click="formVisible=false">close</button>' +
                '</div>' +
                '<div class="editable-error help-block" ng-show="error">{{ error }}</div>' +
              '</form>' +
            '</div>',
          controller: function($scope, $element, $attrs) {
            $scope.formVisible = false;
            $scope.submit = function(evt) {
              $scope.formVisible = false;
            }
          }
      }});

      module.directive('popover', function($compile) {
        return {
          restrict: 'A',
          scope: false,
          compile: function compile(tElement, tAttrs, transclude) {
            return {
              pre: function preLink(scope, iElement, iAttrs, controller) {
              },
              post: function postLink(scope, iElement, iAttrs, controller) {
                var attrs = iAttrs;
                var element = iElement;

                // We assume that the trigger (i.e. the element the popover will be
                // positioned at is the previous child.
                var trigger = element.prev();
                var popup = element;

                // Connect scope to popover.
                trigger.on('shown', function() {
                  var tip = trigger.data('popover').tip();
                  $compile(tip)(scope);
                  scope.$digest();
                });

                trigger.popover({
                  html: true,
                  content: function() {
                    scope.$apply(function() {
                      scope.formVisible = true;
                    });
                    return popup;
                  },
                  container: 'body'
                });
                scope.$watch('formVisible', function(formVisible) {
                  if (!formVisible) {
                    trigger.popover('hide');
                  }
                });
                if (trigger.data('popover')) {
                  trigger.data('popover').tip().css('width', '500px');
                }
              }
            }
          }
        };
      });

      function MyCtrl($scope) {
          $scope.value = new Date(0);
      }

      angular.element(document).ready(function() {
        angular.bootstrap(document, ['module']);
    });

      </script>
  </head>

  <body ng-controller="MyCtrl">
    <my-form popover="true" value="value">
    {{ value }}
  </my-form>
  </body>

</html>
4
  • 2
    angular-ui already has a popover, take a look at that, i don't know what you mean by overhead, but those components are written natively in angular. Commented Jan 14, 2014 at 4:43
  • I'd like to avoid an additional library (angular-ui) which I referred to as overhead. Commented Jan 14, 2014 at 5:00
  • 1
    You may want to look into this popover implementation. The key thing here is compilation of popover's content against scope on popover's show by overriding native bootstrap functions. Commented Jan 14, 2014 at 6:41
  • @Vasaka Thanks, I'll take a look at their implementation. Commented Jan 14, 2014 at 12:04

1 Answer 1

1

I think I resolved the two issues. In case anybody is interested, I'll quickly summarise my findings:

1) As per Vasaka's suggestion, the tip of the popover needs to be bound to the scope ($compile(tip)(scope)).

2) The second problem was that the nested directive wasn't compiled by the $compile() call in (1). This is due to having set replace: true in the (nested) directive definition object of simple-date-picker. Since the original directive tag initially gets replaced, any subsequent $compile run won't recognise the simple date picker as Angular directive any more.

Final plunker (only difference is replace: false).

Sign up to request clarification or add additional context in comments.

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.