操纵外部DOM(防止文本选择),同时避免从指令控制器

时间:2015-08-14 08:24:09

标签: angularjs dom angularjs-directive

场景...

我正在构建一个日期选择器指令,直到现在我设法在模板/控制器问题之间保持严格的分离。控制器构建一个充满日期对象的月份数组。该模板使用ng-repeat="day in days"呈现当前月份日期。

目前我正在构建一个由ng-mousedown="startSelection(date.dayId);"调用的区间选择方法。按下单击按钮时,模型中将一系列日期定义为选定日期,并通过css类在模板中突出显示。在鼠标按钮释放时,最后一次定义范围以获得最终日期选择。

这很好,但是有一个小问题需要解决。按下鼠标并startSelection执行此操作时,浏览器也会突出显示特定蓝色阴影中的文本。我想通过在按下鼠标时将no-select css类附加到body元素,然后在选择序列结束时将其删除来防止此行为。

我选择了body元素,因为如果光标将指令滑入app主体,则会从页面的任何位置选择文本。

.no-select { 
    -webkit-touch-callout:none; 
    -webkit-user-select:none; 
    -khtml-user-select:none; 
    -moz-user-select:none; 
    -ms-user-select:none; 
    user-select:none;
}

...和问题

这就是让我感到困扰的问题:由于我们不应该从控制器(尤其是外部元素)操纵DOM以便于单元测试,所以推荐的操作方法是什么?对于像这样的边缘案例,是否有任何指导方针/最佳做法?创建专门用于文本选择预防的服务是否过度?我想这最适合作为utils服务的方法。

指令控制器:

app.directive("caDatePicker", function () {
    return {
        ...
        controller: function ($scope) {
            ...
            $scope.startSelection = function (dayId) {
                angular.element(document.querySelector('body')).addClass('no-select');
                ...
            };
            ...
        }
    }
}

略有不同的情况: 如果我在指令元素<ca-date-picker></ca-date-picker>上添加一个类,如下面的代码示例中所示。这可以吗?这是否违反了控制器模板分离关注指南?

link: function (scope, element, attrs) {
    element.addClass("no-select");
    ...
}

1 个答案:

答案 0 :(得分:1)

你可以在控制器中设置一个布尔值,然后在你的指令中你可以看到这个模型属性的变化并在指令中进行你的dom操作:

controller: function ($scope) {
    $scope.dayId = "";
    $scope.selectionStarted = false;
    $scope.startSelection = function (dayId) {
       $scope.dayId = dayId;
       $scope.selectionStarted = true;

    };
    ...
}

然后在你的指令中:

$scope.$watch('selectionStarted', function(newValue, oldValue) {
          if(newValue) {
            angular.element(document.querySelector('body')).addClass('no-select');
          }
        });

基本上把这作为一个经验法则。 从不曾在控制器曾经中操纵你的dom。 您的控制器是控制视图行为的粘合剂,指令取决于控制器的状态$ scope变量是操纵dom的内容。

如果您要操作的元素超出了direcitve范围或控制器范围。但该控制器的属性是否应该应用dom操作。你可以这样做:

在你的模板中:

ng-class="noSelectBoolean ? 'no-select-css-class' : 'select-css-class'"
位于模板范围内的控制器中的

.controller(function($scope) {
     $scope.noSelectBoolean = false;
     $scope.$on('applyNoSelect', function(event, value) {
     $scope.noSelectBoolean = value;
     }
}

最后在您的控制器中决定是否应该应用它:

.controller(function($scope, $rootScope){
    $scope.noSelect  = function(value){
     $rootScope.$broadcast('applyNoSelect', value);
   }
}

更多深入解释,请查看我之前提交的答案:

Event broadcasting in angularJS