4

I created a little application using Angular to manage Todolists. Each list has a number of todos. Each todo has attributes name, value1 and value2.

Each list should be sorted automatically by Angular, so I used ng-repeat="todo in selectedList.todos | orderBy: todoOrderFilter":

  <ul class="list-group">
    <li class="list-group-item" ng-repeat="todo in selectedList.todos | orderBy: todoOrderFilter">

      <div>
        <span>{{todo.name}} (Value1: {{todo.value1}}, Value2 {{todo.value2}})</span>
        <button type="button" class="btn btn-warning btn-xs" ng-click="editTodo(todo)"><i class="icon-trash"></i> Edit</button>
        <button type="button" class="btn btn-danger btn-xs floatright" ng-click="deleteTodo(todo)"><i class="icon-trash"></i> Delete</button>
      </div>

    </li>
  </ul>

In my controller I defined my order filter like this:

$scope.todoOrderFilter = function (todo) {
    return todo.value1 * todo.value2;
};

This works well so far until I tried to make each row editable. To accomplish this, I added an additional <div> with input elements to edit the values inside each <li> and also added ng-hide="todo.editing" and ng-show="todo.editing" to be able to turn on/off edit mode by simply setting todo.editing=true or false;

Full HTML looks like this:

  <ul class="list-group">
    <li class="list-group-item" ng-repeat="todo in selectedList.todos | orderBy: todoOrderFilter">

      <div ng-hide="todo.editing">
        <span>{{todo.name}} (Value1: {{todo.value1}}, Value2 {{todo.value2}})</span>
        <button type="button" class="btn btn-warning btn-xs" ng-click="editTodo(todo)"><i class="icon-trash"></i> Edit</button>
        <button type="button" class="btn btn-danger btn-xs floatright" ng-click="deleteTodo(todo)"><i class="icon-trash"></i> Delete</button>
      </div>

      <div ng-show="todo.editing">
        <input id="todoname" ng-model="todo.name" ng-enter="updateTodo(todo)" type="text" class="form-control marginBottom" placeholder="Todo speichern" aria-describedby="basic-addon2"></input>
        Value1: <input ng-model="todo.value1" ng-enter="updateTodo(todo)" type="text" class="form-control marginBottom" placeholder="Value1" aria-describedby="basic-addon2"></input>
        Value2: <input ng-model="todo.value2" ng-enter="updateTodo(todo)" type="text" class="form-control marginBottom" placeholder="Value2" aria-describedby="basic-addon2"></input>
        <button type="button" class="btn btn-default" ng-click="updateTodo(todo)">Save</button>
        <button type="button" class="btn btn-danger" ng-click="cancelUpdateTodo(todo)">Cancel</button>
      </div>

    </li>
  </ul>

Edit button handler:

$scope.editTodo = function(todo) {
    todo.editing = true;
};

This kinda works but while I edit input fields for value1 or value2 my sort function is automatically triggered which causes the <li> elements to jump up and down which is really bad.

So what I basically want is that my auto sort filter is disabled while todo.editing=true.

So far I found these similar questions on SO but they weren't really helpful:


Question: How can I prevent Angular from resorting the todo list while todo.editing=true?

Community
  • 1
  • 1
Timo Ernst
  • 13,291
  • 19
  • 95
  • 153

2 Answers2

1

The solution was to edit a copy of the object instead of directly editing it. Then, replace the original object with the copy when the user finished editing.

Timo Ernst
  • 13,291
  • 19
  • 95
  • 153
0

I firmly believe in utilising code that has already been written for us by the Angular team, and building upon it. As such, I think this is a perfect scenario for decorating the built-in orderBy filter to accept a fourth argument (ignore).

I haven't tested this myself very thoroughly but it should do the trick;

app.config(function ($provide) {
  $provide.decorator('orderByFilter', function ($delegate) {
    // Store the last ordered state.
    var previousState;

    return function (arr, predicate, reverse, ignore) {
      // If ignore evaluates to a truthy value, return the previous state.
      if (!!ignore) {
        return previousState || arr;
      }

      // Apply the regular orderBy filter.
      var order = $delegate.apply(null, arguments);

      // Overwrite the previous state with the most recent order state.
      previousState = order;

      // Return the latest order state.
      return order;
    }
  });
});

Usage:

<div ng-repeat="d in data | orderBy:predicate:reverse:ignore">

<!-- in your case -->
<div ng-repeat="todo in selectedList.todos | orderBy:todoOrderFilter:false:todo.editing>

I hope all of that makes sense (maybe even just works™).

Kasper Lewau
  • 7,407
  • 1
  • 30
  • 40