16

After applying orderBy in my list of input boxes if i edit any field it starts sorting immediately and i loose focus. I tried to dug in the angular code and found out they have applied something like $watch on the orderBy's attributes therefore whenever the value changes it sorts the list.Is there any way to disable orderBy while editing? I dont want to let the orderBy sort data while editing text. Any help will be appreciated

Here is my plunker

Note: I want to use orderBy & don't need any alternative like sorting the list from any controller itself. I just want the orderBy to sort the list once on page load and then remain quite.

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
I_Debug_Everything
  • 3,776
  • 4
  • 36
  • 48
  • 1
    When the server sends my data I have it include an additional column 'sortBy', which has the field(s) I want the sort to go by, then ng-repeat uses that for the orderBy. This field isn't in the edit, so doesn't change until I get row(s) back from the server again. I.e. When I "save" a record, the returning data is fetched from the database, including the sortBy, which replaces the same row in $scope, thus updating the sort on the page only after the edit is completed. – Dev Null Oct 21 '16 at 16:24

8 Answers8

11

I was using orderBy on a list that could be re-ordered using angular-sortable, and ran into a similar issue.

My solution was to perform the ordering manually, by calling the orderBy filter inside the controller when the page was initialised, and then calling it subsequently when necessary, for example in a callback function after re-ordering the list.

shauneba
  • 2,122
  • 2
  • 22
  • 32
  • 3
    There is an example in the official documentation : https://docs.angularjs.org/api/ng/filter/orderBy . Search for `var orderBy = $filter('orderBy');`. Don't forget to inject `$filter`. – Maxence Oct 10 '14 at 13:07
8

You could override the directive to change the moment of the update to the moment you wish the reordering. You could also just not use ng-model and rely on a custom directive.

This thread discuss overriding the input directive to change the model update to be triggered by tge blur event. Take a look at the fiddle.

Although you might override the directive, you shouldn't do this, and the best solution, as explained and exemplified by @Liviu T. in the comments below would be to create a custom directive that removes the event keyup binding and adds a blur one. Here is directive code, and here is Liviu's 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/>

Unfortunately, as Angular events are not namespaces, you will have to remove any previously added event.

Community
  • 1
  • 1
Caio Cunha
  • 23,326
  • 6
  • 78
  • 74
  • 2
    I would suggest you don't override but add a new directive that enables that behavior only where you need it http://plnkr.co/edit/yoUmEUMMzecNlbKmtgDq?p=preview – Liviu T. Mar 08 '13 at 12:07
  • good solution but as suggested in comments we shouldn't override already existing one. as well as i dont want to break any binding with view even for a keydown event – I_Debug_Everything Mar 08 '13 at 12:36
  • I agree, and updated the question to accomodate @LiviuT. solution. Unfortunately, the only way to avoid the `keydown` update, is to remove events already bound to the element. The good point of Liviu's solution is that you will not lose to other moments where the default directive is used. – Caio Cunha Mar 08 '13 at 13:14
3

A different approach may be to not loose focus to begin with. If your main problem is that your loosing focus, then instead of disabling orderBy, add this directive to your input:

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;
            });

        }
    };
}])

Then just:

<input keep-focus ng-model="item.name"/>

I know this does not directly answer you question, but it might help solve the underlying problem.

Shawn Dotey
  • 616
  • 8
  • 11
  • This was helpful and does improve my situation, thanks. However, if there is a checkbox in a sort col and you check it then the item shoots off to it's proper order. Any ideas how to improve that? – Norbert Norbertson Oct 18 '17 at 13:33
2

Following @CaioToOn works but it breaks in Angular 1.2.4 (possibly the whole 1.2.X branch). To get it to work you also need to declare the priority of the custom directive to be 1 (the input directive is 0). The directive then becomes:

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());
            });         
        });
    }
};

});

See http://plnkr.co/edit/rOIH7W5oRrijv46f4d9R?p=preview and https://stackoverflow.com/a/19961284/1196057 for related priority fix.

Community
  • 1
  • 1
  • this is good but I have a check box in the grid and when a user checks the item then the row shoots off to it's new sorted location. Not sure what I can do about it other than turn order by off for that. – Norbert Norbertson Oct 18 '17 at 14:13
0

There's no easy or straightforward way to accomplish what you want, but there are some options. You would have to work without ng-model, and instead use the blur event:

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.

But be aware that this would break the two-way binding between the model and the view.

Stewie
  • 60,366
  • 20
  • 146
  • 113
  • i do have binding with other elements in my page and i guess those changes wont be reflected until i blur the input box. right? wouldn't it be nicer if we can just nullify/edit the orderBy in such a way that it wont see any changes until i reload the page or apply sorting via any button. your solution is good no doubt but i don't want hack with ng-model. hope you understand – I_Debug_Everything Mar 08 '13 at 12:35
  • `orderBy` filter is actually not watching for changes. It's the ng-repeat directive that watches your `items` array and reiterates it as soon as your ng-model is changed. So you may either go the way I suggested or you may create your custom ng-repeat directive, which would not watch for changes, but instead be triggered by some other (explicit) way. – Stewie Mar 08 '13 at 12:51
0

You can create custom filter and call that only when necessary. Example when you click on 'Grid header' for sorting or after dynamically adding/removing values to array, or simply click of a button(Refresh Grid)

You need to dependency Inject Angular filter and sort filter

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);
   }
 }

and call in HTML something like:

<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>
Mahesh
  • 3,727
  • 1
  • 39
  • 49
0

You can freeze the current ordering while you are editing. Say your html looks like this:

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

In your controller you write:

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;
};

Before the user starts editing, you first sort the current items in exactly the same order that they currently appear in the page. You do that by calling the orderBy $filter() with the current sorting parameters.

Then you go over your - now sorted - items, and add an arbitrary property (here "pos") and set it to the current position. I zero-pad it so that position 0002 collates before 0011. Maybe that is not necessary, no idea.

You normally want to remember the current ordering, here in the scope variables "savedOrder" and "savedReverse".

And finally you tell angular to sort by that new property "pos" and voilà the table order is frozen, because that property simply does not change while editing.

When you are done editing, you have to do the opposite. You restore the old ordering from the scope variables "savedOrder" and "savedReverse":

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

If the order of the $scope.items array matters for you, you would also have to sort it again to its original ordering.

Guido Flohr
  • 1,871
  • 15
  • 28
0

Try using a scope variable to change the order. In this case, when you are going to order, you call a function in your controller that changes the variable order value to the field you want to order by, and when you are editing, you reset it.

Example:

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

So here we have the variable "order" to tell the ng-repeat by which field the list must be ordered. A button calls a function in the controller that changes the "order" value.

<button type="button" id="orderName" click="changeOrder('item.name')" >Name order </button>

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

And then, in the changeOrder function

$scope.order = param;

Where 'param' is the field you want to order by. If you don't do anything else, you are going to have the same problem you had, so then, after you have assigned the correct value to the order variable you go

$scope.order = "";

Which resets the ordering. The result is that the ordering is just going to be effective when the button is pressed and then it wont order again unless you press the button again. This can be changed so it orders again when, for example, you have finished editing an item, as you wanted.