Learning 14: Bead data, functions and visibility

This page describes the commands used to associate data and functionality with beads, together with commands for controlling their visibility and responsiveness.

 

PS.data ( x, y, data )

You've already seen the PS.data() command at work in previous demonstration code. It's time to understand exactly what it does and how useful it can be.

PS.data() lets you associate any kind of data with each individual bead in the grid. Once data has been associated with a bead, that data is subsequently passed through the data parameter of the four bead-related Perlenspiel event calls:

The required x and y parameters indicate the zero-based column and row position of the bead, respectively. An error occurs if either parameter is negative, or exceeds the dimensions of the grid. Multiple bead assignment is supported using PS.ALL.

The optional data parameter specifies the data value to be assigned to the specified bead(s). It can be any value (number, string, Boolean, function, array or object) that can be assigned to a JavaScript variable.

If data is PS.DEFAULT, a data value of zero (0) is applied to the bead. If data is PS.CURRENT or not supplied, the bead data is not changed.

// SAMPLE CALLS
// Associate PS.COLOR_RED with bead 0, 3

PS.data( 0, 3, PS.COLOR_RED );

// Associate a numeric array with bead 2, 10

PS.data( 2, 10, [ 42, 666, 3.14159 ] );

// Associate an object with multiple properties
// to bead 5, 6

var obj = {
 health : 92,
 stamina : 80,
 female : true
};

PS.data( 5, 6, obj );

By associating data with beads, PS.data() lets you create "classes" of beads, allowing you to make decisions and take action based on a bead's data rather than its position in the grid.

Demonstration

Suppose you want to create a 9x9 grid that plays particular sounds when particular colored beads are touched:

If you use the position of the beads to determine which sound to play, your code might look something like this:

PS.init = function( system, options ) {
 "use strict";

 PS.gridSize( 9, 9 );

 PS.color( 0, 0, PS.COLOR_RED );
 PS.color( 2, 3, PS.COLOR_GREEN );
 PS.color( 3, 6, PS.COLOR_BLUE );
 PS.color( 5, 8, PS.COLOR_BLACK );
};

PS.touch = function( x, y, data, options ) {
 "use strict";

 if ( ( x === 0 ) && ( y === 0 ) ) {
 PS.audioPlay( "fx_pop" );
 }
 else if ( ( x === 2 ) && ( y === 3 ) ){
 PS.audioPlay( "fx_boop" );
 }
 else if ( ( x === 3 ) && ( y === 6 ) ){
 PS.audioPlay( "fx_beep" );
 }
 else if ( ( x === 5 ) && ( y === 8 ) ){
 PS.audioPlay( "fx_hoot" );
 }
};

This works, but the code is verbose and brittle.

Here's one way to use PS.data() to produce the same result:

PS.init = function( system, options ) {
 "use strict";

 PS.gridSize( 9, 9 );

 PS.color( 0, 0, PS.COLOR_RED );
 PS.color( 2, 3, PS.COLOR_GREEN );
 PS.color( 3, 6, PS.COLOR_BLUE );
 PS.color( 5, 8, PS.COLOR_BLACK );

 PS.data( 0, 0, "fx_pop" );
 PS.data( 2, 3, "fx_boop" );
 PS.data( 3, 6, "fx_beep" );
 PS.data( 5, 8, "fx_hoot" );
};

PS.touch = function( x, y, data, options ) {
 "use strict";

 if ( data ) {
 PS.audioPlay( data );
 }
};

In this code, PS.touch() doesn't need to examine which bead has been touched. It only needs to check if any data has been assigned to the bead. If so, it assumes that data is the name of a sound file, and plays it.

Let's rewrite this to be more general and expandable:

[Run Demo]

PS.init = function( system, options ) {
 "use strict";
 var beadList, len, i, bead, x, y;

 PS.gridSize( 9, 9 );

 beadList = [
 [ 0, 0, PS.COLOR_RED, "fx_pop" ],
 [ 2, 3, PS.COLOR_GREEN, "fx_boop" ],
 [ 3, 6, PS.COLOR_BLUE, "fx_beep" ],
 [ 5, 8, PS.COLOR_BLACK, "fx_hoot" ]
 ];

 len = beadList.length;
 for ( i = 0; i < len; i += 1 ) {
 bead = beadList[ i ];
 x = bead[ 0 ];
 y = bead[ 1 ];
 PS.color( x, y, bead[ 2 ] );
 PS.data( x, y, bead[ 3 ] );
 }
};

PS.touch = function( x, y, data, options ) {
 "use strict";

 if ( data ) {
 PS.audioPlay( data );
 }
};

Instead of using hardwired PS.data() calls to initialize the beads, this code abstracts the bead positions, colors and sounds into an array called beadList. Each element of beadList is a sub-array containing four items: the x and y coordinates of a bead, its color, and the sound to play when it is touched.

Although slightly more complicated than the previous version, this code is very, very easy to change and maintain. No code has to be touched to modify bead positions, colors or sounds, only the data in beadList. You can easily add or remove any number of beads from beadList, because the initialization code checks the length of the list before looping over it. You could also implement additional bead properties, such as border width or a glyph, with only a line or two of extra code inside the loop. And PS.touch() never changes at all.

Because there is no limit to the type or amount of data that can be associated with a bead, PS.data() lets you create complex bead behavior that is entirely data-driven, without the need to hardwire anything.

Return value

PS.data() returns the data value currently assigned to the bead.

If no data has been assigned, the value zero (0) is returned.

PS.ERROR is returned if an error occurs.

 

PS.exec ( x, y, exec )

Just as PS.data() lets you associate data with beads, PS.exec() lets you associate functions with beads.

The required x and y parameters indicate the zero-based column and row position of the bead, respectively. An error occurs if either parameter is negative, or exceeds the dimensions of the grid. Multiple bead assignment is supported using PS.ALL.

The optional exec parameter specifies the bead function. It should be a reference to a valid JavaScript function that accepts the same three parameters, in the same order, as Perlenspiel's PS.touch() event handler:

  1. x : integer
  2. y : integer
  3. data : any value

Once a function is associated with a bead, it is subsequently called whenever that bead is clicked or touched, with the x and y parameters set to the zero-based coordinates of the bead, and the data parameter set to the bead's current data value. This call occurs immediately before the usual system call to PS.touch().

The function specified by exec doesn't need to return anything. Any returned values are ignored.

If PS.DEFAULT or null is specified for exec, any previous function assignment is removed from the bead. If PS.CURRENT is specified, or no exec is supplied, the bead function is not changed.

// SAMPLE CALLS
// Associate a function assigned to a variable
// with bead 4, 5

var myFunction = function( x, y, data ) {
 PS.debug( "Clicked " + x + ", " + y + "\n" );
};

PS.exec( 4, 5, myFunction );

// Associate an anonymous function with bead 2, 1

PS.exec( 2, 1, function( x, y, data ) {
 PS.debug( "Clicked " + x + ", " + y + "\n" );
} );

Demonstration

Suppose you want to create a 7x7 grid with the following behaviors:

If you use the position of the beads to determine which action to take, your code might look something like this:

PS.init = function( system, options ) {
 "use strict";

 PS.gridSize( 7, 7 );

 PS.color( 0, 2, PS.COLOR_BLACK );
 PS.color( 1, 4, PS.COLOR_RED );
 PS.color( 4, 5, PS.COLOR_GREEN );
 PS.color( 3, 6, PS.COLOR_BLUE );
};

PS.touch = function( x, y, data, options ) {
 "use strict";

 if ( ( x === 0 ) && ( y === 2 ) ) {
 PS.audioPlay( "fx_click" );
 }
 else if ( ( x === 1 ) && ( y === 4 ) ) {
 PS.color( x, y, PS.random( PS.COLOR_WHITE ) );
 }
 else if ( ( x === 4 ) && ( y === 5 ) ) {
 PS.glyph( x, y, PS.random( 9 ) + 47 );
 }
 else if ( ( x === 3 ) && ( y === 6 ) ) {
 PS.gridColor( PS.random( PS.COLOR_WHITE ) );
 }
};

This works, but the code is verbose and brittle.

Here's one way to use PS.exec() to produce the same result:

PS.init = function( system, options ) {
 "use strict";

 PS.gridSize( 7, 7 );

 PS.color( 0, 2, PS.COLOR_BLACK );
 PS.color( 1, 6, PS.COLOR_RED );
 PS.color( 4, 5, PS.COLOR_GREEN );
 PS.color( 5, 6, PS.COLOR_BLUE );

 PS.exec( 0, 2, function( x, y, data ) {
 PS.audioPlay( "fx_click" );
 } );

 PS.exec( 1, 4, function( x, y, data ) {
 PS.color( x, y, PS.random( PS.COLOR_WHITE ) );
 } );

 PS.exec( 4, 5, function( x, y, data ) {
 PS.glyph( x, y, PS.random( 9 ) + 47 );
 } );

 PS.exec( 3, 6, function( x, y, data ) {
 PS.gridColor( PS.random( PS.COLOR_WHITE ) );
 } );
};

PS.touch = function( x, y, data, options ) {
 "use strict";
};

In this code, PS.touch() doesn't need to do anything at all! All of the required functionality is assigned directly to the beads themselves in PS.init().

Let's rewrite this to be more general and expandable:

[Run Demo]

PS.init = function( system, options ) {
 "use strict";
 var beadList, len, i, bead, x, y;

 beadList = [
 [ 0, 2, PS.COLOR_BLACK,
 function( x, y, data ) {
 PS.audioPlay( "fx_click" );
 } ],
 [ 1, 4, PS.COLOR_RED,
 function( x, y, data ) {
 PS.color( x, y, PS.random( PS.COLOR_WHITE ) );
 } ],
 [ 4, 5, PS.COLOR_GREEN,
 function( x, y, data ) {
 PS.glyph( x, y, PS.random( 9 ) + 47 );
 } ],
 [ 3, 6, PS.COLOR_BLUE,
 function( x, y, data ) {
 PS.gridColor( PS.random( PS.COLOR_WHITE ) );
 } ]
 ];

 len = beadList.length;
 for ( i = 0; i < len; i += 1 ) {
 bead = beadList[ i ];
 x = bead[ 0 ];
 y = bead[ 1 ];
 PS.color( x, y, bead[ 2 ] );
 PS.exec( x, y, bead[ 3 ] );
 }
};

PS.touch = function( x, y, data, options ) {
 "use strict";
};

This version abstracts the bead positions, colors and functions into an array called beadList. Each element of beadList is a sub-array containing four items: the x and y coordinates of a bead, its color, and the function to call when it is touched.

Only beadList needs to be changed to modify the bead positions, colors or functions. You can easily add or remove any number of beads from beadList. Additional bead properties could be handled with only a line or two of extra code within the initialization loop.

PS.exec() simplifies the implementation of complex bead behaviors without the need to hardwire anything.

Return value

PS.exec() returns the function currently assigned to the bead.

If no function is assigned, the value null is returned.

PS.ERROR is returned if an error occurs.

 

PS.visible ( x, y, show )

The PS.visible() command lets you hide and/or show any bead in the grid.

The required x and y parameters indicate the zero-based row and column position of the bead, respectively. An error occurs if either parameter is negative, or exceeds the dimensions of the grid. Multiple bead assignment is supported using PS.ALL.

If the optional show parameter is false or the number zero (0), the specified bead becomes invisible:

If show is true or any nonzero number, the bead becomes visible again. If no visual attributes were changed while it was invisible, it assumes its previous state.

Calling PS.visible( x, y, true ) on a visible bead has no effect. Similarly, calling PS.visible( x , y, false ) on an invisible bead has no effect.

// SAMPLE CALLS
// Hide the bead at 3, 8

PS.visible( 3, 8, false );

// Show a previously hidden bead at 2, 3

PS.visible( 2, 3, true );

If show is PS.DEFAULT, the default visibility (true, visible) is assigned to the bead. If show is PS.CURRENT or not supplied, the visibility is not changed.

Usage notes

1. An invisible bead retains all of its attributes. Its state prior to being made invisible can determined by inspecting the return value of any bead command.

2. Invisible beads continue to produce the usual PS.touch(), PS.release(), PS.enter() and PS.exit() events.

3. If a function has been assigned to an invisible bead with a previous call to PS.exec(), that function is still executed when the bead is clicked or touched.

Demonstration

This demo creates an 16x16 grid of beads, colored to represent all of the 256 levels of gray available in a 24-bit color environment. Clicking or touching a bead toggles its visibility.

Notice that the entire bead, including its border, is affected. This is most obvious if you touch beads at the outside edges of the grid.

[Run Demo]

PS.init = function( system, options ) {
 "use strict";
 var level, x, y;

 PS.gridSize( 16, 16 );

 // Create 256-level grayscale grid

 level = 0;
 for ( y = 0; y < 16; y += 1 ) {
 for ( x = 0; x < 16; x += 1 ) {
 PS.color( x, y, level, level, level );
 level += 1
 }
 }

 PS.statusText( "PS.visible() Demo" ); };

PS.touch = function( x, y, data, options ) {
 "use strict";

 // Toggle visibility

 PS.visible( x, y, !PS.visible( x, y ) );
};

Return value

PS.visible() returns true if the specified bead is visible, else false.

PS.ERROR is returned if an error occurs.

 

PS.active ( x, y, active )

The PS.active() command lets you control the interactive responsiveness of each bead in the grid.

The required x and y parameters indicate the zero-based row and column position of the bead, respectively. An error occurs if either parameter is negative, or exceeds the dimensions of the grid. Multiple bead assignment is supported using PS.ALL.

If the optional active parameter is false or zero (0), the specified bead is immediately deactivated:

If active is true or any nonzero number, the bead is reactivated and assumes the behavior it exhibited before deactivation.

Calling PS.active( x, y, true ) on an active bead has no effect. Similarly, calling PS.active( x, y, false ) on a deactivated bead has no effect.

// SAMPLE CALLS
// Deactivate the bead at 8, 3

PS.active( 8, 3, false );

// Reactivate the bead at 10, 11

PS.active( 10, 11, true );

If active is PS.DEFAULT, the default activation status (true, active) is assigned to the bead. If active is PS.CURRENT or not supplied, the activation status is not changed.

Notes

1. Deactivating a bead has no visual effect. If you want to give an inactive bead a particular appearance, make the changes before calling PS.active().

2. A deactivated bead retains all of its attributes. Its state prior to deactivation can determined by inspecting the return value of any bead command.

Demonstration

This demo creates a 1x2 grid. The upper bead randomly changes its color, border, border width, glyph (a Roman numeral) and glyph color, and plays an obnoxious sound, when touched. Touching the lower bead toggles the activation state of the upper bead.

[Run Demo]

PS.init = function( system, options ) {
 "use strict";
 var exec;

 PS.gridSize( 1, 2 );

 // Function to modify top bead

 exec = function ( x, y, data ) {
 PS.color( x, y, PS.random( PS.COLOR_WHITE ) );
 PS.border( x, y, PS.random( 32 ) );
 PS.borderColor( x, y, PS.random( PS.COLOR_WHITE ) );
 PS.glyph( x, y, ( PS.random( 16 ) - 1 ) + 0x2160 );
 PS.glyphColor( x, y, PS.random( PS.COLOR_WHITE ) );
 PS.audioPlay( "fx_squawk" );
 };
 PS.exec( 0, 0, exec ); // assign to top bead
 exec( 0, 0 ); // call it once to initialize

 PS.glyph( 0, 1, 0x263C ); // Start with sun
};

PS.touch = function( x, y, data, options ) {
 "use strict";
 var status;

 if ( y > 0 ) { // touching lower bead
 status = !PS.active( 0, 0 );
 PS.active( 0, 0, status );

 // Update status glyph

 if ( status ) {
 PS.glyph( x, y, 0x263C ); // sun
 }
 else {
 PS.glyph( x, y, 0x263D ); // moon
 }
 PS.audioPlay( "fx_click" );
 }
};

Return value

PS.active() returns true if the specified bead is active, else false.

PS.ERROR is returned if an error occurs.

 

Terms to know

Next: Faders