2

尝试让 3d 派对 jQuery 库使用两个非常简单的自定义指令:

无法让 ng-click 工作,并且不确定如何从链接函数中的重复元素中获取数据。

当您单击幻灯片时,其名称和隐藏数据应附加在其下方的列表中。

jsfiddle

angular.module('sm', [])
.directive('selector', function () {
return {
    restrict: "E",
    template: '<div class="swiper-wrapper">' +
        '<div class="swiper-slide" ng-repeat="slide in slides">' +
        '<h1 ng-click="selected(slide)">{{ slide.name }}</h1>' +
        '</div></div>',
    replace: true,
    controller: ['$scope', '$timeout', function ($scope, $timeout) {

        $scope.slides = [{
            name: 'one',
            hidden: 'kittens'
        }, {
            name: 'two',
            hidden: 'puppies'
        }, {
            name: 'three',
            hidden: 'bacon'
        }];
        $timeout(function () { // important!
            $.swiper.init();
        });

        // ng-click never fired due to the jQuery slider plugin
        $scope.selected = function (data) {
            console.log('ng-click called $scope.selected');
            $scope.$broadcast('slideSelected', data);
        };
    }],
    link: function linkFn(scope, lElement, attrs) {

        lElement.on('click', function (el) {
            console.log('lElement on click called');
            // how do I get access to the clicked element's data?
            scope.$broadcast('slideSelected', el);
            $
        })
    }
}
})
      .directive('selected', function () {
        return {
            restrict: "E",
            template: '<ul>' +
                '<li ng-repeat="selection in selected">{{ selection }}</li>' +
                '</ul>',
            replace: true,
            controller: ['$scope', function ($scope) {
                var selected = ['Add to me', 'please'];
                $scope.selected = selected;
                $scope.$on('slideSelected', function (data) {
                    $scope.$apply(function () {
                        selected.push(selected);
                    })
                });
            }],
        }
    })
        .controller('MyCtrl', function ($scope) {});

    $.swiper = {
        init: function () {
            var mySwiper = $('.swiper-container').swiper({
                mode: 'horizontal',
                loop: true
            });
        }
    };
4

2 回答 2

4

这里有几点需要注意:

1. 如果您没有以子指令应该能够访问require它并访问其控制器的方式创建指令,您可以考虑只使用链接函数而不是控制器。可以将$timeout依赖项移至指令工厂函数。

2. 你的指令共享一个范围;由于指令没有被告知创建新的或隔离的范围,它们各自的scope.selected属性(一个中的函数和另一个中的值)相互覆盖。

隔离范围解决了这个问题,但是你不能这样做,scope.$broadcast因为范围不再连接。你的选择是

  1. 在父范围内广播事件:scope.$parent.$broadcast
  2. $rootScope在(所有范围的最终父级)上广播事件
  3. 使用共享服务而不是事件广播(这可能是我会做的)

3. 如果您查看文档Scope#$on,您会看到侦听器函数的第一个参数是触发的事件;第二参数将是您发送到$broadcast函数中的自定义数据。

4. 在 Angular 的 1.1.x 版本中,如果不添加一个子句来告诉 Angular 它应该使用哪些数据来确定数据是否真的重复,就不能在ng-repeat属性中拥有相同的数据。track by在这里我们使用$index

<li ng-repeat="selection in selected track by $index">{{ selection }}</li>

解决这些问题让我们得到这个代码:http: //jsfiddle.net/BinaryMuse/hCdJA/;问题是ng-click仍然被 jQuery 插件吃掉了。在 Angular 中使用第三方 jQuery 插件时,这类问题并不少见,答案通常是编写一个指令来包装插件的功能。


经过一番努力,我有一组指令将 Swiper 的功能(至少是我们关心的部分;Swiper 在 API 方面具有相当广泛的表面积,所以我没有涵盖所有内容)以可重用的形式方式。我很难获得setDatagetData正常工作(我怀疑这是插件中的一个错误),因此最终通过常规data()调用和外部对象来存储回调来破解我的方式。

在我们进入代码之前,你可以在这里看到一个工作演示:http: //jsfiddle.net/BinaryMuse/UruNG/

这是最终的 HTML:

<div ng-app="sm">
  <div ng-controller="MyCtrl">
    <swiper>
      <slide ng-repeat="slide in slides" ng-click="select(slide)">
        <h1>{{slide.name}}</h1>
      </slide>
    </swiper>
    <ul>
      <li ng-repeat="item in items track by $index">{{item | json}}</li>
    </ul>
  </div>
</div>

我已经分离了swiperandslide元素以使它们可重用和可组合;该slide指令使用该require属性获取父指令定义的控制器,swiper以访问它公开的函数。

这是使它工作的JavaScript:

angular.module('sm', [])
.directive('swiper', function($timeout) {
  return {
    restrict: 'EA',
    template: "<div class='swiper-container'>" +
      "<div class='swiper-wrapper'></div>" +
      "<div style='display: none' ng-transclude></div>" +
      "</div>",
    replace: true,
    transclude: true,
    // We use a controller here so the slide directive
    // can require it and call `addSlide`.
    controller: function($element) {
      var newSlides = [];
      var mySwiper = null;
      var slideCount = 0;
      var callbacks = {};

      // Attached directly to the controller so other directives
      // have access to it.
      this.addSlide = function(html, callback) {
        if (mySwiper) {
          var newSlide = mySwiper.createSlide(html.html());
          // Hackily save off the callback based on
          // a unique ID since getData() for
          // swiper.clickedSlide doesn't appear to work
          // when using setData() on newSlide.
          newSlide.data('slideNumber', ++slideCount);
          mySwiper.appendSlide(newSlide);
          callbacks[slideCount] = callback;
          mySwiper.swipeTo(0, 0, false);
        } else {
          // mySwiper hasn't been initialized yet; save
          // the slide off in an array so we can add it later.
          newSlides.push({html: html, callback: callback});
        }
      };

      $timeout(function() {
        mySwiper = $element.swiper({
          mode: 'horizontal',
          loop: true,
          onSlideClick: function(swiper) {
            // Look up the callback we saved off and call it.
            var clicked = swiper.clickedSlide;
            var slideNumber = clicked.data('slideNumber');
            var callback = callbacks[slideNumber];
            if (callback) callback();
          }
        });

        // Now that mySwiper has been initialized, iterate
        // over any calls to `addSlide` that happened
        // before we were ready and add them to the swiper.
        for (var i = 0; i < newSlides.length; i++) {
          var slide = newSlides[i];
          this.addSlide(slide.html, slide.callback);
        }
      }.bind(this));
    }
  }
})
.directive('slide', function() {
  return {
    restrict: 'EA',
    // Look for a parent `swiper` element and get its controller 
    require: '^swiper',
    template: "<div class='swiper-slide' ng-transclude></div>",
    replace: true,
    transclude: true,
    link: function(scope, elem, attrs, swiper) {
      swiper.addSlide(elem, function() {
        scope.$apply(attrs.ngClick);
      });
    }
  }
})
.controller('MyCtrl', function ($scope) {
  $scope.slides = [{
    name: 'one',
    hidden: 'kittens'
  }, {
    name: 'two',
    hidden: 'puppies'
  }, {
    name: 'three',
    hidden: 'bacon'
  }];

  $scope.items = ["Add to me", "please"];

  $scope.select = function(slide) {
    $scope.items.push(slide);
  };
});

您可以看到我们已经设法将所有 Swiper 特定的功能保留在指令中,而我们循环的数据 ( slides) 和触发的回调 ( select) 附加到控制器范围,它们更有意义 (因为它们是特定于应用程序的数据)。

同样,可以在这里找到工作演示:http: //jsfiddle.net/BinaryMuse/UruNG/

于 2013-07-17T19:00:29.980 回答
1

我注意到几件事:

1)您根本不需要链接功能。当您使用模板时,Angular 负责编译模板内的指令。此外,您的链接函数绑定到选择器元素,而不是单独绑定到每个 li,因此您无法确定单击了哪个数据对象。

2)您的指令使用继承的范围,但都将不同的东西分配给相同的属性名称。在选择器指令中,您将分配$scope.selected为一个函数。在 selected 指令中,您将分配$scope.selected一个值数组。这些干扰是因为它们使用相同的范围。我能够通过将第一个更改为 just 来修复它$scope.select = function(data)...

3)您编写了事件处理程序来查找数据作为第一个参数。事件是第一个参数,之后的任何参数都绑定到广播事件时传递的参数,所以它会更好$scope.$on('slideSelected', function(event, data)...

4)您的事件处理程序不需要应用范围,因为这将自动发生,而只需更新模型。

更新的小提琴在这里

于 2013-07-17T17:35:38.610 回答