Отключение orderBy в AngularJS при редактировании списка

После применения orderBy в моем списке полей ввода, если я редактирую какое-либо поле, он начинает сортировку немедленно, и я теряю фокус. Я попытался выкопать код angular и обнаружил, что они применили что-то вроде $watch на атрибутах orderBy, поэтому всякий раз, когда значение меняет его, он сортирует список. Есть ли способ отключить orderBy при редактировании? Я не хочу позволять orderBy сортировать данные при редактировании текста. Любая помощь будет оценена

Вот мой плункер

Примечание. Я хочу использовать orderBy и не нуждаюсь ни в каких альтернативах, таких как сортировка списка из самого контроллера. Я просто хочу, чтобы orderBy сортировал список один раз на загрузке страницы, а затем оставался довольно.

Ответы

Ответ 1

Я использовал orderBy в списке, который можно было бы переупорядочить с помощью angular -sortable и столкнулся с аналогичной проблемой.

Мое решение состояло в том, чтобы выполнить заказ вручную, вызвав фильтр orderBy внутри контроллера, когда страница была инициализирована, а затем вызвала его впоследствии, когда это необходимо, например, в функцию обратного вызова после повторного упорядочения списка.

Ответ 2

Вы можете переопределить директиву, чтобы изменить момент обновления до момента переупорядочения. Вы также можете просто не использовать ng-model и полагаться на настраиваемую директиву.

Этот поток обсуждает переопределение директивы ввода для изменения обновления модели, которое должно быть вызвано событием tge blur. Взгляните на fiddle.

Несмотря на то, что вы можете переопределить директиву, вы не должны этого делать и лучшее решение, как описано и приведено в качестве примера @Liviu T. в ниже) будет создавать настраиваемую директиву, которая удаляет привязку привязки к событию и добавляет размытие. Вот директивный код, и вот Livuu 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 не являются пространствами имен, вам нужно будет удалить любое ранее добавленное событие.

Ответ 3

После @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=preview и fooobar.com/questions/350586/... для соответствующего исправления приоритета.

Ответ 4

Другой подход может заключаться в том, чтобы не начинать фокусироваться. Если ваша основная проблема заключается в том, что вы теряете фокус, то вместо отключения 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"/>

Я знаю, что это напрямую не отвечает на ваш вопрос, но может помочь решить основную проблему.

Ответ 5

Нет простого и простого способа выполнить то, что вы хотите, но есть некоторые варианты. Вам нужно будет работать без ng-модели и вместо этого использовать событие 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.

Но имейте в виду, что это нарушит двустороннюю привязку между моделью и представлением.

Ответ 6

Вы можете создать собственный фильтр и вызвать его только при необходимости. Пример, когда вы нажимаете "Grid header" для сортировки или после динамического добавления/удаления значений в массив или просто нажмите кнопку (Refresh Grid)

Вам нужна зависимость Inject 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>

Ответ 7

Вы можете заморозить текущее упорядочение во время редактирования. Скажем, ваш 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" ) и устанавливаете его в текущую позицию. Я нуль-pad это так, что позиция 0002 сопоставляется до 0011. Может быть, это не обязательно, не знаю.

Обычно вы хотите запомнить текущий порядок, здесь в переменных области "savedOrder" и "savedReverse".

И, наконец, вы скажете angular сортировать по этому новому свойству "pos", и voilà порядок таблицы заморожен, потому что это свойство просто не изменяется при редактировании.

Когда вы закончите редактирование, вы должны сделать обратное. Вы восстанавливаете старый порядок из переменных области "savedOrder" и "savedReverse":

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

Если порядок массива $scope.items имеет значение для вас, вам также придется отсортировать его снова до его первоначального заказа.

Ответ 8

Попробуйте использовать переменную области видимости для изменения порядка. В этом случае, когда вы собираетесь заказать, вы вызываете функцию в своем контроллере, которая меняет значение переменной порядка в поле, которое вы хотите заказать, и когда вы редактируете, вы reset его.

Пример:

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

Итак, здесь мы имеем переменную "порядок", чтобы сообщить ng-repeat, в каком поле должен быть упорядочен список. Кнопка вызывает функцию в контроллере, которая изменяет значение "порядок".

<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' - поле, которое вы хотите заказать. Если вы ничего не сделаете, у вас будет та же проблема, что и у вас, поэтому, после того, как вы присвоили правильное значение переменной порядка, вы идете

$scope.order = "";

Сбрасывает порядок. В результате заказы будут эффективными только при нажатии кнопки, а затем он не будет снова заказываться, если вы не нажмете кнопку еще раз. Это можно изменить, чтобы он заказывал снова, когда, например, вы закончили редактирование элемента, как вы хотели.