在AngularJS中跟踪网格中的位置

时间:2014-08-04 14:54:50

标签: javascript angularjs

我有一个问题:

我想在AngularJS的网格中跟踪一个位置,该位置可以使用箭头键进行更改,例如10x10网格。我首先想到我会在一个指令中这样做,所以我有以下代码:

angular.module('mymodule').directive('grid', function() {

    return {

        restrict : 'A',

        compile : function($target, $attrs) {

            var rows = $attrs.rows || 10;
            var cols = $attrs.cols || 10;

            for(var y = 0; y < rows; y++) {
                var row = angular.element('<div>');
                row.addClass('row');
                for(var x = 0; x < cols; x++) {
                    var col = angular.element('<div>');
                    col.addClass('item');
                    row.append(col);
                }
                $target.append(row);
            }

            return function($scope, $elem, $attrs) {
                // Maintain the position here
            }
        }

    }

});

这会创建一个漂亮的网格,但是我怀疑在哪里实际放置管理位置的代码。我还希望能够保持两个网格之间的位置,所以我认为需要在存储网格的地方创建像PositionManager这样的东西?

您对我的模块中的位置有何想法?

2 个答案:

答案 0 :(得分:1)

<强> TL; DR
Working demo (但它更长,没有大量记录:D)


这是一个很长的解决方案,所以我不会在这里重现所有代码 我将提供代码的概述和方法的简短描述 (完整的代码可以在上面的链接中找到(也作为一个模块捆绑在一起,以便于重复使用)。)


<强>概览

该方法使用以下组件:

  1. A grid指令
    在漂亮的网格中显示数据并维护数据/元素的Grid对象表示。

  2. A gridContainer指令
    顾名思义,这个元素包含网格。它将网格组合在一起, 这样,容器中一次只能激活一个网格(&#34;活动&#34; 表示响应箭头键事件)。
    键事件侦听器在此元素上注册(然后箭头按下被委托给活动网格);

  3. Position服务
    这用于创建新的Position对象来保存和操纵Grid中活动单元格的位置。

  4. Grid服务
    这用于创建能够跟踪其活动单元位置并提供实用程序功能的新Grid对象 移动,选择一个单元格,确定&#34;焦点&#34;应该在活动离开边界后继续等。

  5. GridContainer服务
    这用于&#34;隔离&#34;网格,即防止导航离开容器 它还允许在页面上具有多个具有相同ID的网格。只要每个容器位于不同的容器中,所有容器都按预期工作。

  6. DIRECTION常数
    基本上是一个&#34;枚举&#34;可能的邻近方向(北,南,东,西)。

  7. 一些CSS
    为了使我们的网格显示出良好的和#34; griddy&#34;。

  8. 以下是它的工作原理(或多或少):

    1. 您定义的<grid-container>元素包含一个或多个<grid>元素 您指定所有必要的数据(ID,大小,数据,位置,&#34;邻居&#34;等) 每个<grid-container>元素都有一个GridContainer对象进行备份,每个<grid>元素都有Grid个对象支持它。
      (为grid指令提供了默认模板,但用户可以使用template-url属性指定其他模板。)

    2. 一旦<grid-container>具有键盘焦点并按下箭头键,它就会调用活动网格上的相应方法 (例如moveUp / Down / Left / Right)。

    3. 网格计算新位置并更改其内部位置表示 如果新的位置超出了自身的边界,它会在相应的方向上寻找邻居 如果在该方向上没有注册的邻居,它将包围&#34; (例如,如果你在顶部并按&#34;向上&#34;它从底部开始)。

    4. 当您单击一个单元格时,父网格将自动激活,其位置将更改为单击的单元格。

    5. 简单就是馅饼,对吧!


      视图

      这是示例4网格视图的样子:

      <grid-container>
          <grid grid-id="grid-1" data="data1"
                width="{{data1[0].length}}" height="{{data1.length}}" x="0" y="0"
                neighbor-e="grid-2" neighbor-s="grid-3">
          </grid>
          <grid grid-id="grid-2" data="data2"
                width="{{data2[0].length}}" height="{{data2.length}}"
                neighbor-w="grid-1" neighbor-s="grid-4">
          </grid>
          <grid grid-id="grid-3" data="data3"
                width="{{data3[0].length}}" height="{{data3.length}}"
                neighbor-n="grid-1" neighbor-e="grid-4">
          </grid>
          <grid grid-id="grid-4" data="data4"
                width="{{data4[0].length}}" height="{{data4.length}}"
                neighbor-n="grid-2" neighbor-w="grid-3">
          </grid>
      </grid-container>
      

      以下是作为gridgridContainer指令模板的部分内容:

      <强>网格container.tmpl.html

      <div tabindex="-1" ng-transclude=""></div>
      

      <强> grid.tmpl.html

      <div class="grid">
          <div class="row" ng-repeat="row in data" ng-init="rowIdx=$index">
              <div class="cell" ng-class="{active:isActive(colIdx,rowIdx)}"
                      ng-repeat="cell in row" ng-init="colIdx=$index">
                  {{rowIdx+1}}.{{colIdx+1}}:<br />cell-{{cell.text}}
              </div>
          </div>
      </div>
      

      代码

      以下是代码的概要。为简洁起见,我删除了实际的详细信息,并保留了代码组织的高级功能,例如:对象的属性和方法,指令定义对象的属性,指令控制器/链接函数中的注释等:

      var app = angular.module('myApp', []);
      
      app.controller('mainCtrl', function ($scope) {
          var data = [
              [{text:  1}, {text:  2}, {text:  3}, {text:  4}, {text:  5}],
              [{text:  6}, {text:  7}, {text:  8}, {text:  9}, {text: 10}],
              [{text: 11}, {text: 12}, {text: 13}, {text: 14}, {text: 15}],
              [{text: 16}, {text: 17}, {text: 18}, {text: 19}, {text: 20}],
              [{text: 21}, {text: 22}, {text: 23}, {text: 24}, {text: 25}]
          ];
      
          $scope.data1 = $scope.data2 = $scope.data3 = $scope.data4 = data;
      });
      
      app.directive('gridContainer', function (GridContainer) {
          return {
              restrict: 'E',
              templateUrl: 'partials/grid-container.tmpl.html',
              transclude: true,
              scope: {},
              controller: function gridContainerCtrl($element, $scope) {
                  // Create a new GridContainer and maintain a reference to the currently active Grid,
                  // so key-events change the position in that Grid
      
                  // Delegate key-events to the active Grid
      
                  // Deregister the event-listener upon removing the element
              }
          };
      });
      
          templateUrl: function (tElem, tAttrs) {
              var templateUrl = tAttrs.templateUrl;
      
              if (!templateUrl) {
                  if (!$templateCache.get(defaultTmplKey)) {
                      $templateCache.put(defaultTmplKey, defaultTmplStr);
                  }
                  templateUrl = defaultTmplKey;
              }
      
              return templateUrl;
          },
      app.directive('grid', function ($templateCache, DIRECTION) {
          ...
      
          return {
              restrict: 'E',
              require: '^gridContainer',
              templateUrl: function (tElem, tAttrs) {
                  // Return `tAttrs.templateUrl` if it is defined.
                  // If not, put the default template into the `$templateCache`
                  // and return the key.
              },
              scope: {
                  data:   '=',
                  gridId: '@',
                  width:  '@',
                  height: '@',
                  x:      '@',
                  y:      '@',
                  neighborN: '@',
                  neighborS: '@',
                  neighborE: '@',
                  neighborW: '@'
              },
              link: function gridPostLink(scope, elem, attrs, ctrl) {
                  // Initialize a Grid
      
                  // Utility function to check if a cell is the active cell
      
                  // Upon clicking on a cell, set the current Grid as active
                  // and change its position to the clicked cell
      
                  // Deregister the event-listener upon removing the element
      
                  // If the position is initialized [i.e. both !== -1],
                  // set this Grid as the active grid
              }
          };
      });
      
      app.constant('DIRECTION', {...});
      
      app.factory('Grid', function (Position, DIRECTION) {
          ...
      
          function Grid(siblingGrids, id, rows, cols, x, y) {
              this._id           = ...;
              this._rows         = ...;
              this._cols         = ...;
              this._position     = new Position(...);
              this._neighbors    = ...;
              this._siblingGrids = ...;
      
              ...
          }
      
          Grid.prototype.getRows       = function () {...};
          Grid.prototype.getColumns    = function () {...};
      
          Grid.prototype.getX          = function () {...};
          Grid.prototype.getY          = function () {...};
          Grid.prototype.setXY         = function (x, y) {...};
      
          Grid.prototype.getNeighbor   = function (dir) {...};
          Grid.prototype.setNeighbor   = function (dir, neighborID) {...};
      
          Grid.prototype.indexToXY     = function (idx) {...};
          Grid.prototype.xyToIndex     = function (x, y) {...};
      
          Grid.prototype.moveUp        = function () {...};
          Grid.prototype.moveDown      = function () {...};
          Grid.prototype.moveLeft      = function () {...};
          Grid.prototype.moveRight     = function () {...};
      
          Grid.prototype._findNeighbor = function (direction) {...};
      
          return Grid;
      });
      
      app.factory('GridContainer', function (Grid) {
          function GridContainer() {
              this._grids = ...;
          }
      
          GridContainer.prototype.newGrid = function (id, rows, cols, x, y) {...};
      
          return GridContainer;
      });
      
      app.factory('Position', function () {
          function Position(x, y) {
              this._x = ...;
              this._y = ...;
          }
      
          Position.prototype.getX  = function () {...};
          Position.prototype.getY  = function () {...};
          Position.prototype.setXY = function (x, y) {...};
      
          return Position;
      });
      

      可以找到完整的代码和工作演示 here

      <子> 它捆绑为一个模块(esGrid),所有组件(控制器/指令/服务等)都是es.名称间隔(出于可重复使用的原因)。
      要在您的应用中使用它:

      <子> 1。在脚本中包含[MODULE: esGrid]下的IIFE 2。esGrid添加为您的模块的依赖项 3。使用视图中的指令:
         <es-grid-container><es.grid ... ></es.grid>...<es-grid-container>
      4。定义您自己的模板,如下所示:<es.grid ... template-url="some.url"></es.grid>
      5. 如果您想引用CSS中的元素(或任何类似CSS的选择器),请不要忘记转义.,因为它是一个特殊字符:{{ 1}}


      <子> 的声明:
      为了使建议的解决方案与Angular CSS: es\.grid { border: ... }支持的所有浏览器兼容,我们并未小心谨慎。 (是的,我正在看你的老IE。)
      它与现代浏览器兼容,并且可以轻松修改以适用于旧版浏览器 (例如,将1.2.+替换为Array.forEach()和类似的东西)。

答案 1 :(得分:0)

您应该使用编译功能的情况非常罕见。你应该能够使用ng-repeat完成同样的事情。例如:

<div ng-repeat="row in ctrl.range(rows)">
    <span 
            ng-repeat="col in ctrl.range(cols)" 
            ng-class="{selected: ctrl.curRow === row && ctrl.curCol === col}">
        cell
    </span>
</div>

你可以通过无数种方式来做这件事,但这里有一个可以帮助你入门的实例:http://jsfiddle.net/robianmcd/9otpqbg8/1/

如果单击网格,则可以使用箭头键在其中导航