在编辑列表时禁用AngularJS中的orderBy

16

在我的输入框列表中应用orderBy后,如果我编辑任何字段,它会立即开始排序并且我就失去了焦点。我尝试查看angular代码并发现他们在orderBy的属性上应用了类似于$watch的东西,因此每当该值更改时,它都会对列表进行排序。有没有办法在编辑时禁用orderBy?我不想让orderBy在编辑文本时对数据进行排序。任何帮助将不胜感激。

这是我的plunker

注意:我想使用orderBy,并且不需要任何替代方法,例如从任何控制器本身对列表进行排序。我只想在页面加载时使用orderBy对列表进行一次排序,然后保持静止。


1
当服务器发送我的数据时,我会让它包含一个额外的列“sortBy”,其中包含我想要排序的字段,然后ng-repeat使用它进行排序。这个字段不在编辑中,因此直到我再次从服务器获取行数据之前都不会更改。也就是说,当我“保存”记录时,返回的数据将从数据库中获取,包括sortBy,它将替换$scope中的同一行,从而在编辑完成后仅更新页面上的排序。 - Dev Null
8个回答

11
我在使用可通过angular-sortable重新排序的列表上使用orderBy,并遇到类似的问题。
我的解决办法是在控制器中初始化页面时手动调用orderBy过滤器进行排序,然后在必要时随后调用它,例如在重新排序列表后的回调函数中。

3
官方文档中有一个例子:https://docs.angularjs.org/api/ng/filter/orderBy。搜索`var orderBy = $filter('orderBy');。不要忘记注入$filter`。 - Maxence

8
您可以重写指令以更改更新的时机,以实现您想要的重新排序时机。您也可以不使用ng-model并依赖于自定义指令此主题讨论了覆盖输入指令以更改模型更新被触发的blur事件。请查看fiddle
尽管您可能会覆盖该指令,但不应这样做。正如@Liviu T.下面的评论中所解释和示范的那样,最好的解决方案是创建一个自定义指令,以删除事件keyup绑定并添加模糊事件。这是指令代码,这是Liviu的plunker
app.directive('modelChangeBlur', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
            link: function(scope, elm, attr, ngModelCtrl) {
            if (attr.type === 'radio' || attr.type === 'checkbox') return;

            elm.unbind('input').unbind('keydown').unbind('change');
            elm.bind('blur', function() {
                scope.$apply(function() {
                    ngModelCtrl.$setViewValue(elm.val());
                });         
            });
        }
    };
});

<input type="text" ng-model="variable" model-change-blur/>

不幸的是,由于Angular事件不是命名空间,您必须删除先前添加的任何事件。


2
我建议您不要覆盖,而是添加一个新的指令,只在需要时启用该行为。http://plnkr.co/edit/yoUmEUMMzecNlbKmtgDq?p=preview - Liviu T.
好的解决方案,但正如评论中建议的那样,我们不应该覆盖已经存在的解决方案。同时,即使是对于一个按键事件,我也不想破坏与视图的任何绑定。 - I_Debug_Everything
我同意,并更新了问题以适应@LiviuT的解决方案。不幸的是,避免keydown更新的唯一方法是删除已绑定到元素的事件。 Liviu解决方案的好处是您不会输给其他使用默认指令的时刻。 - Caio Cunha

3
一种不同的方法是一开始就不要失去焦点。如果你的主要问题是注意力不集中,那么不要禁用orderBy指令,而是将该指令添加到你的输入框中:
app.directive("keepFocus", ['$timeout', function ($timeout) {
    /*
    Intended use:
        <input keep-focus ng-model='someModel.value'></input>
    */
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function ($scope, $element, attrs, ngModel) {

            ngModel.$parsers.unshift(function (value) {
                $timeout(function () {
                    $element[0].focus();
                });
                return value;
            });

        }
    };
}])

那么,只需:
<input keep-focus ng-model="item.name"/>

我知道这并不直接回答您的问题,但它可能有助于解决潜在的问题。

这很有帮助并且改善了我的情况,谢谢。但是,如果有一个复选框在排序列中,您勾选它,那么该项目会快速跳转到其正确的顺序。您有什么想法来改进这个问题吗? - Norbert Norbertson

2

以下是 @CaioToOn 的代码,但它在 Angular 1.2.4 中出现了问题(可能是整个 1.2.X 分支)。为使其正常工作,您还需要声明自定义指令的优先级为 1(输入指令为 0)。

指令如下:

app.directive('modelChangeBlur', function() {
return {
    restrict: 'A',
    priority: 1,
    require: 'ngModel',
        link: function(scope, elm, attr, ngModelCtrl) {
        if (attr.type === 'radio' || attr.type === 'checkbox') return;

        elm.unbind('input').unbind('keydown').unbind('change');
        elm.bind('blur', function() {
            scope.$apply(function() {
                ngModelCtrl.$setViewValue(elm.val());
            });         
        });
    }
};

});

请参考http://plnkr.co/edit/rOIH7W5oRrijv46f4d9R?p=previewhttps://stackoverflow.com/a/19961284/1196057进行相关的优先级修复。


这很好,但我在网格中有一个复选框,当用户勾选该项时,该行就会飞到它的新排序位置。不确定除了关闭排序功能之外我能做什么。 - Norbert Norbertson

0

您可以创建自定义过滤器,并在必要时进行调用。例如,当您单击“网格标题”以进行排序或在动态添加/删除数组值后,或者仅单击按钮(刷新网格)时。

您需要依赖注入Angular的过滤器排序过滤器

angular
   .module('MyModule')
   .controller('MyController', ['filterFilter', '$filter', MyContFunc])

     function ExpenseSubmitter(funcAngularFilter, funcAngularFilterOrderBy) {
       oCont = this;
       oCont.ArrayOfData = [{
         name: 'RackBar',
         age: 24
       }, {
         name: 'BamaO',
         age: 48
       }];
       oCont.sortOnColumn = 'age';
       oCont.orderBy = false;
       var SearchObj = {
         name: 'Bama'
       };

       oCont.RefreshGrid = function() {
         oCont.ArrayOfData = funcAngularFilter(oCont.ArrayOfData, SearchObj);
         oCont.ArrayOfData = funcAngularFilterOrderBy('orderBy')(oCont.ArrayOfData, oCont.sortOnColumn, oCont.orderBy);
   }
 }

并在HTML中调用类似于:

<table>
  <thead>
    <tr>
      <th ng-click="oCont.sortOnColumn = 'age'; oCont.RefreshGrid()">Age</th>
      <th ng-click="oCont.sortOnColumn = 'name'; oCont.RefreshGrid()">Name</th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="val in oCont.ArrayOfData">
      <td>{{val.age}}</td>
      <td>{{val.name}}</td>
    </tr>
  </tbody>
</table>

0

在编辑时,您可以冻结当前的排序。假设您的HTML看起来像这样:

<tbody ng-repeat="item in items | orderBy:orderBy:reverse">
    <tr ng-click="startEdit()">
      <td>{{item.name}}</td>
    </tr>
</tbody>

在你的控制器中,你需要编写以下代码:
var savedOrderBy, savedReverse;
$scope.startEdit() = function() {
    $scope.items = $filter('orderBy')($scope.items, $scope.orderby, $scope.reverse);

    for (var i = 0; i < $scope.items.length; ++i) {
        if (i < 9999) { 
            $scope.items[i]['pos'] = ("000" + i).slice(-4); 
        }
    }

    savedOrderBy = $scope.orderBy;
    savedReverse = $scope.reverse;
    $scope.orderBy = 'pos';
    $scope.reverse = false;
};

在用户开始编辑之前,您首先按照它们当前在页面中出现的顺序精确地对当前项目进行排序。通过使用当前排序参数调用orderBy $filter()来实现。

然后,您遍历已排序的项目,并添加一个任意属性(这里是“pos”),并将其设置为当前位置。我使用零填充它,以便位置0002在0011之前排序。也许这不是必要的,我不知道。

通常情况下,您希望记住当前排序方式,在作用域变量“savedOrder”和“savedReverse”中保存。

最后,您告诉Angular按照该新属性“pos”进行排序,voilà表格顺序被冻结,因为该属性在编辑时根本不会改变。

当您完成编辑后,您需要执行相反的操作。从作用域变量“savedOrder”和“savedReverse”中恢复旧的排序方式:

$scope.endEdit = function() {
    $scope.orderBy = savedOrderBy;
    $scope.reverse = reverse;
};

如果$scope.items数组的顺序对您很重要,那么您还需要将其排序回原始顺序。

0

想要实现您想要的功能并没有简单或直接的方法,但有一些选择。您必须在没有ng-model的情况下工作,而是使用blur事件:

JS:

var app = angular.module('myapp', []);

app.controller('MainCtrl', function($scope) {
  $scope.items = [
    { name: 'foo', id: 1, eligible: true },
    { name: 'bar', id: 2, eligible: false },
    { name: 'test', id: 3, eligible: true }
  ];

  $scope.onBlur = function($event, item){
    // Here you find the target node in your 'items' array and update it with new value
    _($scope.items).findWhere({id: item.id}).name = $event.currentTarget.value;
  };
});

app.directive('ngBlur', function($parse) {
  return function ( scope, element, attr ) {
    var fn = $parse(attr.ngBlur);
    element.bind( 'blur', function ( event, arg ) {
      scope.$apply( function(){
        fn(scope, {
          $event : event,
          arg: arg
        });
      });
    });
  };
});

HTML:

<div ng-controller="MainCtrl">
  <div ng-repeat="item in items | orderBy:'name'" >
    <input value="{{item.name}}" ng-blur="onBlur($event, item)"/>
  </div>
</div>

Plunker

但请注意,这会破坏模型和视图之间的双向绑定。


我在页面中与其他元素绑定,我猜这些更改在我模糊输入框之前不会反映出来。对吧?如果我们可以以这样的方式使orderBy无效/编辑,直到我重新加载页面或通过任何按钮应用排序,那不是更好吗?你的解决方案无疑很好,但我不想使用ng-model进行黑客攻击。希望你能理解。 - I_Debug_Everything
orderBy 过滤器实际上并没有监听变化,它是 ng-repeat 指令监听你的items 数组,并在 ng-model 更改后立即重新迭代它。因此,你可以按照我提出的建议去做,或者你可以创建自定义的 ng-repeat 指令,该指令不会监听变化,而是通过某种其他(显式)方式来触发。 - Stewie

0

尝试使用作用域变量来更改顺序。在这种情况下,当您要排序时,调用控制器中的函数来更改变量顺序值为您想要按其排序的字段,而在编辑时则将其重置。

例如:

<li ng-repeat="item in filtered = (items | filter:search) |  orderBy:order:rever" >

所以在这里我们有一个变量“order”,告诉ng-repeat按照哪个字段对列表进行排序。一个按钮调用控制器中的函数来改变“order”值。
<button type="button" id="orderName" click="changeOrder('item.name')" >Name order </button>

<button type="button" id="orderDate" click="changeOrder('item.date')" >Date order </button>`

然后,在changeOrder函数中

$scope.order = param;

其中'param'是您想按其排序的字段。 如果您什么也不做,您将遇到与之前相同的问题,因此在将正确的值分配给“order”变量后,您继续进行

$scope.order = "";

这将重置排序。结果是,排序只有在按下按钮时才会生效,除非再次按下按钮,否则不会重新排序。 可以更改此设置,例如,在编辑项目完成后,它会再次排序,就像您想要的那样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接