JavaScript example of a grid module

I made a canvas example a while back on how to go about making a grid, but that post was more so on drawing one, not having something that is a state object, and methods that act on that state object. So then in this JavaScript example post today I thought I would go about writing about a simple javaScript grid module.
There are many basic features that a grid module should have, such as a public method that can be used to get a cell by way of a canvas pixel position for example. There are also more advanced features such as path detection that maybe should be a part of a grid module, or maybe a part of an optional module that can be used on top of a grid module. In any case for this example I am going to just be sticking with the very basics of this sort of thing. However do not let that fool you, even when it comes to the very basics of a grid module there is still a fare about of ground to cover.

1 - The grid module

So then the first thing I would like to get to is the current state of the grid module that I have made for this post. When it comes to making a choice as to what kind of pattern I should go with when it comes to making a javaScript module I went with an IIFE.

1.1 - The create method

The main basic method of interest is the create method of this grid module that will create and return a new grid object. So right off the bat there is the question of how to go about structuring a grid object as there are so many different ways of going about doing that sort of thing in javaScript. For example many javaScript developers might like to create a grid as an array of arrays, however others present company included like to use a linear array and use expressions to get and set values of cells. This alone can prove to end up being a situation that can get a developer stuck on something that is trivial, however I often think it is basic to just make a decision and move forward when it comes to things like this. Also I can alter on make additional create methods that can be used to create my standard grid object from other kinds of gird object formats if I end up in a situation in which I need to.

So then the start of my grid module is the beginnings of an IIFE, and the create method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(function (api) {
// create a grid object
api.create = function (opt) {
opt = opt || {};
var grid = {
cellSelected: null, // selected cell ref
cells: []
};
grid.cellSize = opt.cellSize === undefined ? 32 : opt.cellSize;
grid.w = opt.w === undefined ? 8 : opt.w;
grid.h = opt.h === undefined ? 8 : opt.h;
grid.xOffset = opt.xOffset === undefined ? 0 : opt.xOffset;
grid.yOffset = opt.yOffset === undefined ? 0 : opt.yOffset;
var i = 0,
cell,
len = grid.w * grid.h;
while (i < len) {
cell = {
i: i, // store index for this cell
cellX: i % grid.w, // grid index pos values as uppercase X, and Y
cellY: Math.floor(i / grid.w),
data: {} // user data object
};
// cell pixel pos values as lowercase x, and y
cell.x = grid.xOffset + cell.cellX * grid.cellSize;
cell.y = grid.yOffset + cell.cellY * grid.cellSize;
grid.cells.push(cell);
i += 1;
}
return grid;
};

1.2 - The get cell by pixel position method

A must have method for a grid module is a method where I pass a canvas relative pixel position, and what is returned is a cell that is at that position. There are a number of ways to go about making this kind of method, one way might be to loop over all of the cells and using bounding box collision detection. However It might be better to use some examples that just involves some quick expressions.

1
2
3
4
5
6
7
8
9
10
// get a cell by the given pixel position
api.getCellByPixlePos = function (grid, x, y) {
var cellX = Math.floor( (x - grid.xOffset) / grid.cellSize ),
cellY = Math.floor( (y - grid.yOffset) / grid.cellSize ),
cell;
if(cellX >= 0 && cellY >= 0 && cellX < grid.w && cellY < grid.h){
return grid.cells[cellY * grid.w + cellX];
}
return null;
};

1.3 - Selected check method

I have a selected cell feature for this grid module so far. There is also the idea of selecting more than one cell also though. So this is a method that I might revise at some point.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// selected cell check
api.selectedCheck = function (grid, x, y, onSelect, onUnselect) {
var cell = api.getCellByPixlePos(grid, x, y);
if (cell) {
if (cell === grid.cellSelected) {
onUnselect(cell, grid, x, y);
grid.cellSelected = null;
} else {
if (grid.cellSelected) {
onUnselect(grid.cellSelected, grid, x, y);
}
grid.cellSelected = cell;
onSelect(cell, grid, x, y);
}
} else {
if (grid.cellSelected) {
onUnselect(grid.cellSelected, grid, x, y);
grid.cellSelected = null;
}
}
};
}
(this['gridMod'] = {}))

2 - The utils module for this over all example

I often have a general utility module when it comes to making an over all javaScript project of some kind. This module serves as just a general dumping ground for methods that I might use in one or more additional modules, and I can not think of any other place to put them. The state of this kind of module will differ a little from one project to the next, but I have wrote a post in which I am going over a version of this kind of module that has many of the usual suspect methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
var utils = {};
// bounding box
utils.boundingBox = function (x1, y1, w1, h1, x2, y2, w2, h2) {
return !(
(y1 + h1) < y2 ||
y1 > (y2 + h2) ||
(x1 + w1) < x2 ||
x1 > (x2 + w2));
};
// create a canvas element
utils.createCanvas = function (opt) {
opt = opt || {};
opt.container = opt.container || document.getElementById('canvas-app') || document.body;
opt.canvas = document.createElement('canvas');
opt.ctx = opt.canvas.getContext('2d');
// assign the 'canvas_example' className
opt.canvas.className = 'canvas_example';
// set native width
opt.canvas.width = opt.width === undefined ? 320 : opt.width;
opt.canvas.height = opt.height === undefined ? 240 : opt.height;
// translate by 0.5, 0.5
opt.ctx.translate(0.5, 0.5);
// disable default action for onselectstart
opt.canvas.onselectstart = function () {
return false;
}
// append canvas to container
opt.container.appendChild(opt.canvas);
return opt;
};
// get a canvas relative position that is adjusted for scale
utils.getCanvasRelative = function (e) {
var canvas = e.target,
bx = canvas.getBoundingClientRect(),
pos = {
x: (e.changedTouches ? e.changedTouches[0].clientX : e.clientX) - bx.left,
y: (e.changedTouches ? e.changedTouches[0].clientY : e.clientY) - bx.top,
bx: bx
};
// adjust for native canvas matrix size
pos.x = Math.floor((pos.x / canvas.scrollWidth) * canvas.width);
pos.y = Math.floor((pos.y / canvas.scrollHeight) * canvas.height);
return pos;
};
// create and return a canvas pointer event handler
utils.canvasPointerEventHandler = function (state, events) {
return function (e) {
var pos = utils.getCanvasRelative(e),
handler = null;
e.preventDefault();
if (e.type === 'mousedown' || e.type === 'touchstart') {
handler = events['pointerStart'];
}
if (e.type === 'mousemove' || e.type === 'touchmove') {
handler = events['pointerMove'];
}
if (e.type === 'mouseup' || e.type === 'touchend') {
handler = events['pointerEnd'];
}
if (handler) {
handler.call(e, e, pos, state);
}
};
};
// attach canvas pointer events
utils.canvasPointerEvents = function (canvas, state, events) {
var handler = utils.canvasPointerEventHandler(state, events),
options = {
passive: false
}
canvas.addEventListener('mousedown', handler, options);
canvas.addEventListener('mousemove', handler, options);
canvas.addEventListener('mouseup', handler, options);
canvas.addEventListener('touchstart', handler, options);
canvas.addEventListener('touchmove', handler, options);
canvas.addEventListener('touchend', handler, options);
};

3 - The draw file for the example

I have just a few draw methods that I will be using for this example. For now I just want a method to draw a simple solid background, and of course a method to draw the current state of the grid.

1
2
3
4
5
6
7
8
9
10
11
12
13
var draw = {};
// draw a background
draw.background = function (sm, ctx, canvas) {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
// draw the given grid object
draw.grid = function (grid, ctx, canvas) {
grid.cells.forEach(function (cell) {
ctx.fillStyle = cell.data.fillStyle || 'white';
ctx.fillRect(cell.x, cell.y, grid.cellSize, grid.cellSize);
});
};

4 - The main JavaScript file of this example

I then also have a main javaScript file that will make used of my grid module, as well as all the other JavaScript files that I have covered in this post. In this file I have what is the beginnings of a state machine, but this this example I will only need one state objects so I am not going to do anything to advanced when it comes to that sort of thing here. Still when it comes to making a full project of some kind a state machine is an important part of a project that might prove to be a little involved. I have mad a canvas example in which I am getting more into the topic of state machines, and I have a lot of other examples of this sort of thing. Like that of a grid module a state machine is another examples of something that I find myself recreating from the ground up a lot.

In the main.js file of an example I will often have a main app loop, that will make use of the request animation frame method as a way to have such a loop. There are a few other options when it comes to amking a main app loop, but when working with canvas request animation frame might be the best option.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
var sm = {
secs: 0,
fps: 30,
lt: new Date(),
canvasObj: utils.createCanvas({
width: 640,
height: 480
}),
grid: gridMod.create({
xOffset: 32,
yOffset: 32
}),
currentState: 'game',
states: {}
};
var onSelected = function(cell, grid, x, y){
cell.data.fillStyle = 'red';
};
var onUnselected = function(cell, grid, x, y){
cell.data.fillStyle = 'white';
};
// game state
sm.states.game = {
update: function (sm, secs) {
},
draw: function (sm, ctx, canvas) {
draw.background(sm, ctx, canvas);
draw.grid(sm.grid, ctx, canvas);
},
events: {
pointerStart: function (e, pos, sm) {
gridMod.selectedCheck(sm.grid, pos.x, pos.y, onSelected, onUnselected);
},
pointerMove: function (e, pos, sm) {},
pointerEnd: function (e, pos, sm) {}
}
};
utils.canvasPointerEvents(sm.canvasObj.canvas, sm, {
pointerStart: function (e, pos, sm) {
var state = sm.states[sm.currentState];
var handler = state.events['pointerStart'];
if (handler) {
handler.call(e, e, pos, sm);
}
}
});
var loop = function () {
var now = new Date(),
secs = (now - sm.lt) / 1000,
state = sm.states[sm.currentState];
requestAnimationFrame(loop);
if (secs >= 1 / sm.fps) {
state.update(sm, secs);
state.draw(sm, sm.canvasObj.ctx, sm.canvasObj.canvas);
sm.lt = now;
}
};
loop();

5 - Conclusion

That will be it for now when it comes to this grid module, however this might prove to be one of my many javaScript posts in which I will be coming back to do some editing now and then. There are a lot of other features that I would want to add to this kind of module, but the features will differ a little from one project to the next. Still this is the kind of thing that I have grown tied to making all over again each time I start a new vanilla javaScript project that calls for a module such as this. So it would be good to get a solid module for this sort of thing together and be done with this once and for all.