Library Cycle Dialog Items

=Cycle Dialog Items w/ Callback=
===Introduction===
It's typical for a script to need more than the maximum-allowed amount of items per dialog window.
This script allows for cycling through multiple pages of items and provides a way to customize everything, as well as a callback function that end users can edit without having to look through the entire script body if they do not desire.
==Script==
// Cycle Dialog Items by Hawkster Westmoreland
// May 23, 2011
// Allow a dialog to cycle through more than 12 items
// define a channel for listening
integer CHANNEL = -2476;
// define a list containing all the possible menu items
list ALL_MENU_ITEMS = ["0","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"];
// the maximum number of buttons that can be displayed in the dialog at one time
integer MAX_DIALOG_BUTTONS = 12;
// the dialog's message
string DIALOG_MESSAGE = "Pick a Number";
// define tokens for the Prev and Next operations
string PREV_BUTTON = "<< Prev";
string NEXT_BUTTON = "Next >>";
// this function is called when an actual menu item is selected that is
// neither the PREV_BUTTON nor NEXT_BUTTON items, so feel free to
// replace the body of this function with your own code
MenuItemSelectedCallback(integer channel, string name, key id, string message)
{
	llOwnerSay("You selected: " + message);
}
// ==============================================================
// You may not need to change anything below this section, but
// feel free to look through the code if you would like to modify
// the basic functionality.
// ==============================================================
// the number of menu items available (calculated in state_entry)
integer ITEMS_COUNT = 0;
// define a cycle number to keep track of which sublist to display
integer CYCLE_INDEX = 0;
// figure out which sublist cycle should be displayed
//
// items can be any list that has a bunch of items in it
// direction can be PREV_BUTTON, NEXT_BUTTON, or a blank String
list GetMenuCycle(list items, string direction)
{
	// the sublist that will be generated by the next few operations
	list sublist = [];
	// calculate the number of items available
	ITEMS_COUNT = llGetListLength(items);
	
	// check to see what the direction was (if one was specified)
	if(direction == PREV_BUTTON)
	{
		// display the previous cycle if the preconditions are met
		if(CYCLE_INDEX > 0)
		{
			// the index can be cycled backward safely
			CYCLE_INDEX--;
		}
	}
	else if(direction == NEXT_BUTTON)
	{
		// display the next cycle if the preconditions are met
		CYCLE_INDEX++;
	}
	// figure out which button cycle needs to be displayed
	if(CYCLE_INDEX == 0) // first cycle
	{
		// check the number of available items
		if(ITEMS_COUNT <= MAX_DIALOG_BUTTONS)
		{
			// the entire list can be displayed as one complete dialog
			sublist = llList2List(items, 0, ITEMS_COUNT - 1);
		}
		else
		{
			// grab the sublist from the beginning until MAX_DIALOG_BUTTONS - 2
			// to take into account the need for a NEXT_BUTTON item
			sublist = llList2List(items, 0, MAX_DIALOG_BUTTONS - 2);
			// append the NEXT_BUTTON item to the end of the cycle
			sublist += [NEXT_BUTTON];
		}
	}
	else // second...n cycle
	{
		// make sure we did not go over the list bounds
		integer start_index = 0;
		// (MAX_DIALOG_BUTTONS - 1) represents the first cycle with the
		// NEXT button
		//
		// ((CYCLE_INDEX - 1) * (MAX_DIALOG_BUTTONS - 2)) calculates
		// every cycle after the first (with NEXT and PREV buttons)
		start_index = (MAX_DIALOG_BUTTONS - 1) + ((CYCLE_INDEX - 1) * (MAX_DIALOG_BUTTONS - 2));
		// calculate how many items we'll have left after this cycle
		integer items_left = ITEMS_COUNT - start_index;
		// check to see if we'll have another cycle after this one
		if(items_left > MAX_DIALOG_BUTTONS - 2)
		{
			// we can fill another dialog with PREV and NEXT buttons, so
			// this is just another regular cycle (with MAX_DIALOG_BUTTONS - 3) to ensure that
			// the total number of items pulled from ALL_MENU_ITEMS is actually equal to
			// (MAX_DIALOG_BUTTONS - 2)
			sublist = llList2List(items, start_index, start_index + (MAX_DIALOG_BUTTONS - 3));
			// add the PREV button and NEXT button
			sublist = [PREV_BUTTON] + sublist + [NEXT_BUTTON];
		}
		else
		{
			// we can finish the list along with a PREV button, so this
			// is the final cycle for the list
			sublist = llList2List(items, start_index, ITEMS_COUNT - 1);
			// add the PREV button to the beginning of the cycle
			sublist = [PREV_BUTTON] + sublist;
		}
	}
	
	// return the generated sublist cycle
	return sublist;
}
default
{
	state_entry()
	{
		// listen on the specified channel
		llListen(CHANNEL, "", llGetOwner(), "");
	}
	changed(integer change)
	{
		// check to see if the object owner changed
		if(change & CHANGED_OWNER)
		{
			// reset the script so calls to llGetOwner()
			// will correctly reference the new owner
			llResetScript();
		}
	}
	listen(integer channel, string name, key id, string message)
	{
		if(message == NEXT_BUTTON)
		{
			// user clicked the NEXT_BUTTON option, so get the next
			// cycle of menu items
			llDialog(llGetOwner(), DIALOG_MESSAGE, GetMenuCycle(ALL_MENU_ITEMS, NEXT_BUTTON), CHANNEL);
		}
		else if(message == PREV_BUTTON)
		{
			// user clicked the PREV_BUTTON option, so get the previous
			// cycle of menu items
			llDialog(llGetOwner(), DIALOG_MESSAGE, GetMenuCycle(ALL_MENU_ITEMS, PREV_BUTTON), CHANNEL);
		}
		else
		{
			// user clicked an actual item, so do something with it
			MenuItemSelectedCallback(channel, name, id, message);
		}
	}
	
	touch_start(integer total_number)
	{
		// display the dialog if the owner touched me
		if(llDetectedKey(0) == llGetOwner())
		{
			// display the dialog with the current menu cycle
			llDialog(llGetOwner(), DIALOG_MESSAGE, GetMenuCycle(ALL_MENU_ITEMS, ""), CHANNEL);
		}
	}
}
==How It Works==
The state_entry() function sets up a listen on the specified channel and waits for interaction. When the object containing the script is touched, a dialog is displayed with the current sub-list cycle available. When the PREV or NEXT buttons are clicked, the sub-list cycle is re-calculated and sent back to the dialog for display. Finally, when an element is clicked that is neither the PREV nor NEXT buttons, the MenuItemSelectedCallback(integer, string, key, string) function is invoked to process user-defined code.