Подтвердить что ты не робот

AngularJS orderby с пустым полем

Я заказываю свои данные и исправляю все исправления, кроме некоторых полей, пустых или не имеющих значения. При заказе эти пустые поля появляются в первую очередь. Например, при заказе номеров мы получим огромный пустой список до получения значений "0".

Я делаю это так:

ng-click="predicate = 'name'; reverse=!reverse"

и

ng-repeat="name in names | orderBy:predicate:reverse"

JSFiddle: http://jsfiddle.net/JZuCX/1/

Есть ли простой элегантный способ исправить это? Я хочу, чтобы пустые поля были последними, несмотря ни на что.

4b9b3361

Ответ 1

Я бы написал фильтр, который берет элементы с пустым именем из упорядоченного массива и помещает их в конец:

<li ng-repeat="item in (items|orderBy:'name'|emptyToEnd:'name')">{{item.name}}</li>

Код может выглядеть так:

.filter("emptyToEnd", function () {
    return function (array, key) {
        if(!angular.isArray(array)) return;
        var present = array.filter(function (item) {
            return item[key];
        });
        var empty = array.filter(function (item) {
            return !item[key]
        });
        return present.concat(empty);
    };
});

Рабочий пример.

Кстати, ваша скрипка не содержит никакого соответствующего кода. Вы использовали неправильную ссылку?

Обновление 2: Ваша скрипка с моим фильтром.

Ответ 2

Как насчет этого для сортировки строк:

item in (items|orderBy:['!name', 'name'])

Преимущество (кроме краткости) заключается в сортировке null и undefined с пустыми строками.

В моем случае я хотел, чтобы пустые и нулевые и неопределенные вместе были сверху (значения null и undefined по умолчанию сортируются в нижней части), поэтому я использовал:

item in (items|orderBy:['!!name', 'name'])

Ответ 3

Вниз здесь!: D

Это решение расширяет нормальную функциональность фильтра angularJs orderBy, чтобы принять третий аргумент, определяющий, следует ли инвертировать нормальную сортировку нулевых и undefined значений. Он отслеживает имена свойств, которые он передал (не только один), и не перебирает элементы в секунду, как это делают некоторые другие решения. Он используется следующим образом:

<li ng-repeat="item in (items|orderBy:'name':false:true)">{{item.name}}</li>

Я нашел кучу нитей, некоторые из них не были непосредственно о orderBy, и скомпилировали их методы, а также пару моих собственных:

angular.module('lib')
.config(['$provide', function ($provide) {
    $provide.decorator('orderByFilter', ['$delegate', '$parse', function ($delegate, $parse) {
        return function () {
            var predicates = arguments[1];
            var invertEmpties = arguments[3];
            if (angular.isDefined(invertEmpties)) {
                if (!angular.isArray(predicates)) {
                    predicates = [predicates];
                }
                var newPredicates = [];
                angular.forEach(predicates, function (predicate) {
                    if (angular.isString(predicate)) {
                        var trimmed = predicate;
                        if (trimmed.charAt(0) == '-') {
                            trimmed = trimmed.slice(1);
                        }
                        var keyFn = $parse(trimmed);
                        newPredicates.push(function (item) {
                            var value = keyFn(item);
                            return (angular.isDefined(value) && value != null) == invertEmpties;
                        })
                    }
                    newPredicates.push(predicate);
                });
                predicates = newPredicates;
            }
            return $delegate(arguments[0], predicates, arguments[2]);
        }
    }])
}]);

Чтобы использовать этот код дословно, укажите "lib" как зависимость для вашего приложения.

Кредиты на:

Ответ 4

В дополнение к решению Klaster_1 добавьте дополнительный параметр, чтобы сделать фильтр более общим:

http://jsfiddle.net/Zukzuk/JZuCX/27/

Реализация

<tr ng-repeat="name in (names | orderBy:predicate:reverse | orderEmpty:'name':'toBottom')">

Фильтр

.filter('orderEmpty', function () {
    return function (array, key, type) {
        var present, empty, result;

        if(!angular.isArray(array)) return;

        present = array.filter(function (item) {
            return item[key];
        });

        empty = array.filter(function (item) {
            return !item[key]
        });

        switch(type) {
            case 'toBottom':
                result = present.concat(empty);
                break;
            case 'toTop':
                result = empty.concat(present);
                break;

                // ... etc, etc ...

            default:
                result = array;
                break;
        }
        return result;
    };
});

Thnx Klaster_1!

Ответ 5

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

ng-repeat="name in names | orderBy:predicate"

Внутри вашего контроллера:

$scope.predicate = function(name) {
    return name === '' ? 'zzzzzzz' : !name; 
    /* The 'zzzzzz' forces the empty names to the end, 
      I can't think of a simpler way at the moment. */
}

Ответ 6

@Klaster_1 действительно что-то делал, но как только мне понадобилось вложенное значение, фильтр переставал работать. Кроме того, если бы я был обратным порядком, я все равно хотел, чтобы мои значения null отображались до 0. Я добавил $parse, чтобы позаботиться о вложенных ключах и добавил обратный параметр, чтобы я знал, когда положить нулевые значения вверху.

.filter("emptyToEnd", function ($parse) {
    return function (array, key, reverse) {
        if(!angular.isArray(array)) return;
        var keyFn = $parse(key);
        var present = [];
        var empty = [];

        angular.forEach(array, function(item){
          var val = keyFn(item);
          if(angular.isUndefined(val) || val === null) {
            empty.push(item);
          } else {
            present.push(item);
          }
        });

        if (reverse) {
          return present.concat(empty);
        } else {
          return empty.concat(present);
        }
    };
});

Ответ 7

Я не знаю, почему другой ответ предлагает помещать записи нулевого значения внизу. Если я хочу сортировать нормально, значит, в порядке ASC все нуль сверху и в порядке DESC все нули идут вниз, я попробовал другие ответы здесь, но не смог помочь мне изменить код, чтобы преобразовать значение null в '' в моем массиве, и теперь он работает так гладко:

$scope.convertNullToBlank = function (array) {
  for (var i = 0; i < array.length; i++) {
     if (array[i].col1 === null)
       array[i].col1 = '';

     if (array[i].col2 === null)
        array[i].col2 = '';
  }
  return array;
}

Ответ 8

Сортировка и обратная сортировка с использованием столбца сортировки переменных и сохранение undefined внизу, даже ниже отрицательных значений

Мне нравится элегантность ответа Шона выше! Мне нужно было предоставить моим пользователям возможность выбирать столбец для сортировки и выбор направления сортировки, но при этом требуется, чтобы undefined упал на дно, даже если есть отрицательные числа.

Ключевое понимание Шона, фиксирующего отрицательные числа, есть!!. Используйте префикс "!" +, Если вы выполняете сортировку в прямом направлении и префикс "!!" +, если вы делаете обратную сортировку.

Ниже приведен фрагмент ниже. Кстати, я поставил переменные, которые устанавливают предикат (выбор свойства для сортировки) и обратный внутри объекта ( "d" ), так что мы не получаем странных проблем с областью. Вам может не понадобиться "d" в вашей среде.

Кроме того, вы, вероятно, захотите использовать что-то лучше, чем мои дрянные кнопки в нижней части страницы, чтобы управлять вашим предикатом и направлением. Однако это позволяет легко читать ключевые части кода.

function mainController($scope) {
  $scope.userArray = [
  { name: "Don", age: 20 },
  { name: "Bob", age: 30, height: 170 },
  { name: "Abe", age: 40, height: 160 },
  { name: "Zoe", age: 70 },
  {              age: 70, height: 155 },
  { name: "Shorty",age:45,height: -200},
  { name: "TwinkleInEye", age: -1, height: 152 }
  ]

  $scope.d = {}; // Create an object into which info can be stored and not trashed by Angular tendency to add scopes
  $scope.d.predicate = "name"; // This string is the name of the property on which to sort
  $scope.d.reverse = false; // True means reverse the sort order
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<body ng-app="" ng-controller="mainController">
  <div ng-repeat="user in (userArray | orderBy: (d.reverse ?['!!'+d.predicate,d.predicate]:['!'+d.predicate,d.predicate]) : d.reverse)">

    Name {{ user.name }} : Age {{ user.age }} : Height {{ user.height }}
  </div>

  <br/>

  <button ng-click="d.predicate='name';">Name</button>
  <button ng-click="d.predicate='age';">Age</button>
  <button ng-click="d.predicate='height';">Height</button> Currently: {{d.predicate}}
 <br/> Leave undefined at bottom, but otherwise: 
  <button ng-click="d.reverse= !d.reverse;">Reverse</button> Currently: {{d.reverse}}

</body>

Ответ 9

Я создал суть с альтернативным фильтром, основанным на предыдущих решениях: https://gist.github.com/360disrupt/1432ee1cd1685a0baf8967dc70ae14b1

Фильтр расширяет существующий фильтр angular:

angular.module 'tsd.orderByEmptyLast', []
  .filter 'orderByEmptyLast', ($filter) ->
    return (list, predicate, reverse)->
      orderedList = $filter('orderBy')(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
      return orderedList

В более новых версиях angular вам может потребоваться включить orderByFilter вместо использования $filter

angular.module 'tsd.orderByEmptyLast', ['orderByFilter']
  .filter 'orderByEmptyLast', () ->
    return (list, predicate, reverse)->
      orderedList = orderByFilter(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
      return orderedList