AngularJS + AG-格:粘/记得选择与虚拟分页/无限滚动 [英] AngularJS + ag-grid: sticky/remembered selections with virtual paging/infinite scrolling

查看:2222
本文介绍了AngularJS + AG-格:粘/记得选择与虚拟分页/无限滚动的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在一个AngularJS应用程序中,我有一个 AG-电网中使用的虚拟分页/无限滚动从数据集过大,显示延迟加载行立刻。我打开了复选框选择在第一列,这样,用户应当能够选择任意特定应用的操作的各个行。

In an AngularJS application, I have an ag-grid that uses virtual paging/infinite scrolling to lazy-load rows from a dataset that is too large to show at once. I have turned on check-box selection in the first column, so that the user should be able to select individual rows for arbitrary application-specific actions.

在AngularJS应用程序使用 UI路由器来控制多个视图。因此,建立在href=\"http://www.ag-grid.com/angular-grid-virtual-paging/index.php\" rel=\"nofollow\">与排序和放大器虚拟呼叫例如,有关奥运获奖者构建的数据,从 AG-格的文件,我已经进一步扩大了codeA位。从 index.html的

The AngularJS application uses ui-router to control multiple views. So, building on the virtual-paging example with "sorting & filtering", with constructed data about Olympic winners, from the ag-grid documentation, I've further extended the code a bit. From index.html:

<body ng-controller="MainController" class="container">
  <div ui-view="contents"></div>
</body>

和以下 UI路由器规定:

myapp.config(function($stateProvider, $urlRouterProvider) {
  $urlRouterProvider.otherwise("example.page1")

  $stateProvider
    .state('example', {
      abstract: true,
      views: {
        contents: {
          template: '<div ui-view="example"></div>'
        }
      }
    })
    .state('example.page1', {
      url: '/page1',
      views: {
        example: {
          templateUrl: 'page1.html'
        }
      }
    })
    .state('example.page2', {
      url: '/page2',
      views: {
        example: {
          template: 'Go back to the <a ui-sref="example.page1">example grid</a>.'
        }
      }
    });
});

其中, page1.html 如下所示:

<div ng-controller="GridController">
  <div ag-grid="gridOptions" class="ag-fresh" style="height: 250px;"></div>
</div>
<div>
  <h3>Selected rows:</h3>
  <ul class="list-inline">
    <li ng-repeat="row in currentSelection track by row.id">
      <a ng-click="remove(row)">
        <div class="badge">#{{ row.id }}, {{ row.athlete }}</div>
      </a>
    </li>
  </ul>
</div>
<p>Go to <a ui-sref="example.page2">the other page</a>.</p>

我要完成什么:


  1. ,在 AG-格所做的选择是记得(粘)滚动(虚拟)页面的时候拿出来看,然后再返回,以便用户可以选择多个在单独的页面行。

  2. 那想起选项可用网格的外部,并且支持添加和删除选项(如预期由 NG-点击=删除(行) page1.html ,如上图所示)。

  3. 那应该选择从与 AG-格到另一个视图切换时,并再次被记住。

  4. (可选),从而使选择的记住用户的会话。

  1. That selections made in the ag-grid is remembered (sticky) when scrolling a (virtual) page out of view and back again, so that a user can select multiple rows on separate pages.
  2. That the remembered selections are available outside the grid, and support adding and removing selections (as intended by the ng-click="remove(row)" in page1.html, shown above).
  3. That the selections should be remembered when switching from the view with the ag-grid to another one, and back again.
  4. (Optional) That the selections are remembered for the user's session.

我怎样才能做到这一点?

How can I accomplish this?

推荐答案

我创建了一个的这个工作的例子可以实现。

I've created a working example of this can be implemented.

首先,我们将编写一个AngularJS服务, selectionService 来跟踪选择的方式:

First of all, we'll write a AngularJS service, selectionService to keep track of the selections:

function _emptyArray(array) {
  while (array.length) {
    array.pop();
  }
}

function _updateSharedArray(target, source) {
  _emptyArray(target);
  _.each(source, function _addActivity(activity) {
    target.push(activity);
  });
}

myapp.factory('selectionService', function ($rootScope, $window) {
  var _collections = {},
    _storage = $window.sessionStorage,
    _prefix = 'selectionService';

  angular.element($window).on('storage', _updateOnStorageChange);

  function _persistCollection(collection, data) {
    _storage.setItem(_prefix + ':' + collection, angular.toJson(data));
  }

  function _loadCollection(collection) {
    var item = _storage.getItem(_prefix + ':' + collection);
    return item !== null ? angular.fromJson(item) : item;
  }

  function _updateOnStorageChange(event) {
    var item = event.originalEvent.newValue;
    var keyParts = event.originalEvent.key.split(':');

    if (keyParts.length < 2 || keyParts[0] !== _prefix) {
      return;
    }
    var collection = keyParts[1];
    _updateSharedArray(_getCollection(collection), angular.fromJson(item));
    _broadcastUpdate(collection);
  }

  function _broadcastUpdate(collection) {
    $rootScope.$emit(_service.getUpdatedSignal(collection));
  }

  function _afterUpdate(collection, selected) {
    _persistCollection(collection, selected);
    _broadcastUpdate(collection);
  }

  function _getCollection(collection) {
    if (!_.has(_collections, collection)) {
      var data = _loadCollection(collection);
      // Holds reference to a shared array.  Only mutate, don't replace it.
      _collections[collection] = data !== null ? data : [];
    }

    return _collections[collection];
  }

  function _add(item, path, collection) {
    // Add `item` to `collection` where item will be identified by `path`.
    // For example, path could be 'id', 'row_id', 'data.athlete_id',
    // whatever fits the row data being added.
    var selected = _getCollection(collection);

    if (!_.any(selected, path, _.get(item, path))) {
      selected.push(item);
    }

    _afterUpdate(collection, selected);
  }

  function _remove(item, path, collection) {
    // Remove `item` from `collection`, where item is identified by `path`,
    // just like in _add().
    var selected = _getCollection(collection);

    _.remove(selected, path, _.get(item, path));

    _afterUpdate(collection, selected);
  }

  function _getUpdatedSignal(collection) {
    return 'selectionService:updated:' + collection;
  }

  function _updateInGridSelections(gridApi, path, collection) {
    var selectedInGrid = gridApi.getSelectedNodes(),
      currentlySelected = _getCollection(collection),
      gridPath = 'data.' + path;

    _.each(selectedInGrid, function (node) {
      if (!_.any(currentlySelected, path, _.get(node, gridPath))) {
        // The following suppressEvents=true flag is ignored for now, but a
        // fixing pull request is waiting at ag-grid GitHub.
        gridApi.deselectNode(node, true);
      }
    });

    var selectedIdsInGrid = _.pluck(selectedInGrid, gridPath),
      currentlySelectedIds = _.pluck(currentlySelected, path),
      missingIdsInGrid = _.difference(currentlySelectedIds, selectedIdsInGrid);

    if (missingIdsInGrid.length > 0) {
      // We're trying to avoid the following loop, since it seems horrible to
      // have to loop through all the nodes only to select some.  I wish there
      // was a way to select nodes/rows based on an id.
      var i;

      gridApi.forEachNode(function (node) {
        i = _.indexOf(missingIdsInGrid, _.get(node, gridPath));
        if (i >= 0) {
          // multi=true, suppressEvents=true:
          gridApi.selectNode(node, true, true);

          missingIdsInGrid.splice(i, 1);  // Reduce haystack.
          if (!missingIdsInGrid.length) {
            // I'd love for `forEachNode` to support breaking the loop here.
          }
        }
      });
    }
  }

  var _service = {
    getCollection: _getCollection,
    add: _add,
    remove: _remove,
    getUpdatedSignal: _getUpdatedSignal,
    updateInGridSelections: _updateInGridSelections
  };

  return _service;
});

selectionService 服务允许添加和删除单独的集合,由确定集合任意对象,你会发现一个名字适当。通过这种方式,相​​同的服务可以用于记忆在多个 AG-格实例的选择。每个对象将使用路径参数来确定。在路径用于使用检索的唯一标识符lodash的获得功能。

The selectionService service allows adding and removing arbitrary objects to separate collections, identified by collection, a name you find suitable. This way the same service can be used for remembering selections in multiple ag-grid instances. Each object will be identified using a path parameter. The path is used to retrieve the unique identifier using lodash's get function.

此外,该服务使用的sessionStorage 坚持用户的整个标签/浏览器会话中的选择。这可能是矫枉过正;我们可以只依靠服务来保持选择的赛道,因为它只会被实例化一次。这当然可以修改您的需求。

Furthermore, the service uses sessionStorage to persist the selections during the user's whole tab/browser session. This might be overkill; we could have just relied on the service to keep track of the selections since it will only get instantiated once. This can of course be modified to your needs.

然后有一个必须完成的 GridController 的变化。首先第一列所有 columnDefs 进入不得不稍微改变

Then there were the changes that had to be done to the GridController. First of all the columnDefs entry for the first column had to be changed slightly

  var columnDefs = [
    {
      headerName: "#",
      width: 60,
      field: 'id',  // <-- Now we use a generated row ID.
      checkboxSelection: true,
      suppressSorting: true,
      suppressMenu: true
    }, …

,其中,一旦数据已被从远程服务器检索产生新的,产生的行ID

where the new, generated row ID is generated once the data has been retrieved from the remote server

       // Add row ids.
       for (var i = 0; i < allOfTheData.length; i++) {
         var item = allOfTheData[i];

         item.id = 'm' + i;
       }

(即M中的ID刚刚列入,以确保我没有混淆的ID与 AG-用其他ID格

(The 'm' in the ID was included just to make sure I didn't confused that ID with other IDs used by ag-grid.)

接下来, gridOptions 必要的修改是添加

Next, the necessary changes to gridOptions were to add

{
  …,
  onRowSelected: rowSelected,
  onRowDeselected: rowDeselected,
  onBeforeFilterChanged: clearSelections,
  onBeforeSortChanged: clearSelections,
  …
}

被不同的处理程序是相当简单的,与之通信的 selectionService

  function rowSelected(event) {
    selectionService.add(event.node.data, 'id', 'page-1');
  }

  function rowDeselected(event) {
    selectionService.remove(event.node.data, 'id', 'page-1');
  }

  function clearSelections(event) {
    $scope.gridOptions.api.deselectAll();
  }

现在,在 GridController 需要处理由信号更新 selectionService

Now, the GridController needs to handle updates signalled by the selectionService too

  $scope.$on('$destroy',
             $rootScope.$on(selectionService.getUpdatedSignal('page-1'),
                            updateSelections));

  function updateSelections() {
    selectionService.updateInGridSelections($scope.gridOptions.api, 'id', 'page-1');
  }

通话 selectionService.updateInGridSelections 将更新有关电网的并网选择。这是写最繁琐的功能。例如,如果选择了外部添加(网格外的),那么我们将不得不执行 forEachNode 运行,即使我们知道所有必要的节点已经在网格中选择;有没有办法提前退出的循环。

calls selectionService.updateInGridSelections which will update the in-grid selections of the grid in question. That was the most cumbersome function to write. For example, if a selection has been added externally (outside the grid), then we'll have to perform a forEachNode run, even if we know all the necessary nodes have already been selected in-grid; there's no way to exit that loop early.

最后,另一个关键是分别以清除和前后重新应用的选择,当过滤器或排序顺序被改变时,或者当新的数据被从服务器(其仅在演示模拟的)检索。解决的办法是包括 params.successCallback 中的 GetRows的<后 updateSelections 通话/ code>处理程序

Finally, another crux was to clear and reapply the selections before and after, respectively, when the filters or sort orders are changed, or when new data is retrieved from the server (which is only simulated in the demo). The solution was to include a call to updateSelections after the params.successCallback inside the getRows handler

             params.successCallback(rowsThisPage, lastRow);
             updateSelections();

现在,该解决方案的实施过程中最令人费解的结果是, AG-格 API网格选项 onAfterFilterChanged onAfterSortChanged 不能用于重新应用的选择,因为他们(远程)数据加载完成之前触发。

Now, the most puzzling findings during the implementation of this solution was that the ag-grid API grid options onAfterFilterChanged and onAfterSortChanged couldn't be used for reapplying the selections because they trigger before the (remote) data has finished loading.

这篇关于AngularJS + AG-格:粘/记得选择与虚拟分页/无限滚动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆