Summary:
Interactive demo link (detailed but not extensively explained :D)
Due to length, the solution won't be fully copied here.
An overview and brief explanation of the approach will be provided.
(Full code available in the provided link, packaged as a module for easy usage.)
APPROACH
The solution involves the following key components:
A grid
directive
Displays data in a visually pleasing grid and maintains a Grid object representation of the data/element.
A gridContainer
directive
Acts as a container for multiple grids, ensuring only one grid is active at any given time.
Arrow-key events are handled at this level, with proper delegation to the active grid.
A Position
service
Used for creating and manipulating Position objects to track the active cell's position in a Grid.
A Grid
service
Responsible for creating Grid objects that can track active-cell position, offer utility functions for movement, cell selection, determining focus movement after boundaries, etc.
A GridContainer
service
Isolates grids, preventing navigation beyond container borders.
Also enables multiple grids with the same ID as long as each resides in a different container.
A DIRECTION
constant
Enumerates possible neighboring directions (North, South, East, West).
Customized CSS
Enhances grid appearance.
Functionality:
Define a <grid-container>
with nested <grid>
elements.
Specify essential data (IDs, sizes, data, positions, neighbors, etc).
Each <grid-container>
corresponds to a GridContainer
object, and each <grid>
to a Grid
object.
When the <grid-container>
is focused and an arrow key is pressed, the active Grid's method is invoked (e.g., moveUp/Down/Left/Right).
Grid calculates new position, adjusts internal representation.
If new position exceeds boundaries, searches for a neighbor in that direction or wraps around if no registered neighbor.
Clicking on a cell makes the parent Grid active with position shifted to the clicked cell.
Initiating and Template:
Example of 4-grid template:
<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-container>
Partial templates for grid
and gridContainer
directives:
grid-container.tmpl.html
<div tabindex="-1" ng-transclude=""></div>
grid.tmpl.html
<div class="grid"> ... (cell structure) ... </div>
Structure:
Outline of the code for brevity:
var app = angular.module('myApp', []);
... Controllers, directives, services, etc ...
app.constant('DIRECTION', {...});
app.factory('Grid', function (Position, DIRECTION) {
...
});
app.factory('GridContainer', function (Grid) {
...
});
app.factory('Position', function () {
...
});
Find the complete code and demo here.
Module: esGrid
is utilized, all components (controllers, directives, services) are es.
namespace structured for reusability.
To implement:
1. Include the IIFE under [MODULE: esGrid]
in a script.
2. Add esGrid
as a dependency to your module.
3. Utilize directives in view: <es-grid-container><es.grid ... ></es.grid>...<es-grid-container>
4. Customize the template: <es.grid ... template-url="some.url"></es.grid>
5. Consider CSS referencing (escape .
for special character): CSS: es\.grid { border: ... }
DISCLAIMER:
The solution may not fully support older browsers not in Angular 1.2.+
compatibility scope.
Works best with contemporary browsers; adaptable for older ones by revising functionalities like Array.forEach()
to angular.forEach()
, among other adjustments.