Extended Sample – Colors

Now that you know all about mscript, let’s write a program with it!  The program will be a web page that can be used to change its background color.  Here’s what the finished product looks like:



So, we need a “Current Color” header, a little form for setting the color, another little form adding a new color, and a button for starting over.

Now that we know the user interface, the next order of business is the code to make the page work.

First off, we’ll use a database table for storing the list of colors.  Let’s call this table “colors”, very creative.  

We break up the program into separate functions, one per different way of getting or changing data.  

Here’s our first function, for adding a color to the database:

f addColor (color)
	* msdb.define("colors", color)
}

This function takes the color to add a parameter named color, and uses a * statement to call the msdb.define function to create a row in the colors database value with a key of the color parameter.  That’s a mouthful!  So msdb.define, that’s a function for adding or updating data in a database table.  The first parameter is the name of the database table.  The second parameter is the key value for the row in the database table.  We use a * statement because we don’t use the return value from the msdb.define function call.  We put this all in an addColor function so that the rest of the program doesn’t need to know about our database table at all.

Our next function is for setting the background color for the page.
  
f setBgColor (color)
	* msdb.define("currentColor", 0, index("color", color))
}

We use a separate database table called “currentColor” to store the current color, again very creative.  Like addColor, the setBgColor function takes a color parameter, and uses a * statement to call the msdb.define function.  In this case, we’re cheating a little by using a single-row table to store one value, the color.  So the key is 0 (zero), and whenever we call the msdb.define function the value for the color column in the one-row table is changed to have the new value.

Next we need a function for getting the current background color from our one-row database table:

f getBgColor ()
	$ color = msdb.selectValue("SELECT color FROM currentColor”)
	? color = null

		& color = "black"
	}
	<- color
}

Here we use msdb.selectValue function to get the value for the color column of the one-row currentColor table.  If no color has been set yet, msdb.selectValue returns the special value null.  We check for null and use a default color if needed.

Now we’re ready to start piecing things together.  We need a function for returning the list of colors:

f getColors ()
	$ colors = msdb.selectList("SELECT value FROM colors")
	? colors.length() = 0
		& colors = list("black", "blue", "green", "red")
		@ color : colors
			* msdb.define("colors", color)
		}
	}
	& colors = colors.sorted()
	-> colors
}

The msdb.selectList function is used to try to getting colors from the database.  When the program is first run, the colors table does not exist, so if we get no colors back from the msdb.selectList function call, we create a default list of colors, then use a @ statement to walk through that default list, using a * statement to call msdb.define to add colors to the colors database table.  Once we’ve got a good list of colors, we get a sorted copy and return the list.

Now that we can add colors, work with background colors, and get the color list, we know we need to be able to start over:

f resetColors ()
	* msdb.drop("colors")
	* msdb.drop("currentColor")
}

Here we use * statements to call the msdb.drop function to drop our two database tables.  This removes all record of these tables from the database.  Be careful with msdb.drop.  Getting user approval is a must, as will see soon.

Now we move to the user interface.  We’ve been going “bottom-up” building on basic single-purpose functions that we know we’ll need…or we later figured out we needed.

So here is the main function.  In other programming languages, a function named main is the one that is automatically called as the top-level function that defines the behavior of the program.  mscript	does not assume this, we have to call our main function, like so:
f main ()
	! Output the current list of colors
	! for troubleshooting
	! This output only appears in raw output
	! not web output
	>> <!--
	* printColors()
	>> -->
	
	! Unpack the inputs from the user's 
	! form submission
	$ command = input("command")
	$ theColor = input("color")
	
	! Handle user commands
	? command = "Reset Colors"
		* resetColors()
	? command = "Add Color"
		* addColor(theColor)
	}
	
	! Finalize the color
	? theColor = ""
		& theColor = getBgColor()
	<>
		* setBgColor(theColor)
	}
	
	! Output the BODY tag with the colors
	! and output the forms
	<{ body index("bgcolor", theColor, "text", "white")
		> "Current Color: " + theColor
		>> <br>
		>> <br>
		* renderColorPickerForms(theColor)
	}
}

! Call our main function to run the show
* main()

Here we’ve added lots of descriptive comments using ! statements describing how the program works.  So I don’t have much to say here.  It’s not common to have this many comments, or this descriptive.  Most code, like what we’ve seen up to now, is self-descriptive.  But main is where things really happen, and you’re new to this, so hopefully the comments help.

One thing to note is the input() function.  This, like the group of msdb functions, is an extension to the core mscript language.  This function reads values posted from <input> tags by <form> tags.  The values are read off what’s called the query string, the part of the web address (aka, URL) after the ?.

We’ve already explored more of the functions that main() calls, but there is some good code left to see.  Let’s start with printColors() used for debugging:

f printColors ()
	@ color : getColors()
		> color
	}
}

Nice and simple.

We dedicate separate function for outputting the UI interface for choosing a function.  This uses the <SELECT> HTML tag, not to be confused with the SELECT database query statement:

f renderColorsList (theColor)
	<{ select index("name", "color")
		@ color : getColors()
			$ optionValues = index("inner", color)
			? color = theColor
				* optionValues.add("selected", 1)
			}
			< option optionValues
		}
	}
}

The function takes the current color as the parameter, and uses a special index for setting the selected <OPTION> HTML tag to have a selected HTML attribute.

Finally, we have a function for outputting the UI for all the three forms on the page:

f renderColorPickerForms (theColor)
	<{ form
		* renderColorsList(theColor)
		< input index("name", "command", "type", "submit", "value", "Set Color")
	}
	
	<{ form
		< input index("type", "text", "name", "color", "value", "new-color")
		< input index("name", "command", "type", "submit", "value", "Add Color")
	}
	
	<{ form
		$ settings = index()
		* settings.add("name", "command")
		* settings.add("type", "submit")
		* settings.add("value", "Reset Colors")
		* settings.add("onclick", "return confirm('Are you sure?')")
		< input settings 
	}
}

The first <form> tag is for picking a background color.  It calls the renderColorsList function to output the list.  Using a separate function for listing the colors keeps the renderColorPickerForms simple and clean.

The second <form> has one <input> tag for the user to enter a new color of their choosing.

The third <form> is for resetting the database.  There are lots of settings for this tag, so an index variable is created and added to on separate lines with * statements so that the one line for the <INPUT> does not get too long or hard to understand.  Note that the user is prompted with whether they are sure they want to reset the database.


Here is the final complete sample code:

! Handle form submissions and render the page.
f main ()
	! Output the current list of colors for troubleshooting
	! This output only appears in the raw output, not the web output
	>> <!--
	* printColors()
	>> -->
	
	! Unpack the inputs from the user's 
	! form submission
	$ command = input("command")
	$ theColor = input("color")
	
	! Handle user commands
	? command = "Reset Colors"
		* resetColors()
	? command = "Add Color"
		* addColor(theColor)
	}
	
	! Finalize the color
	? theColor = ""
		& theColor = getBgColor()
	<>
		* setBgColor(theColor)
	}
	
	! Output the BODY tag with the colors
	! and output the forms
	<{ body index("bgcolor", theColor, "text", "white")
		> "Current Color: " + theColor
		>> <br>
		>> <br>
		* renderColorPickerForms(theColor)
	}
}

! Call our main function to run the show
* main()

! Output the forms for setting the background color, adding a color, and resetting the database.
f renderColorPickerForms (theColor)
	<{ form
		* renderColorsList(theColor)
		< input index("name", "command", "type", "submit", "value", "Set Color")
	}
	
	<{ form
		< input index("type", "text", "name", "color", "value", "")
		< input index("name", "command", "type", "submit", "value", "Add Color")
	}
	
	<{ form
		$ settings = index()
		* settings.add("name", "command")
		* settings.add("type", "submit")
		* settings.add("value", "Reset Colors")
		* settings.add("onclick", "return confirm('Are you sure?')")
		< input settings 
	}
}

! Output the <select><option>... HTML output.
f renderColorsList (theColor)
	<{ select index("name", "color")
		@ color : getColors()
			$ optionValues = index("inner", color)
			? color = theColor
				* optionValues.add("selected", 1)
			}
			< option optionValues
		}
	}
}

! Set the background color for the page.
! Use 0 for the key in this one row database table.
f setBgColor (color)
	* msdb.define("currentColor", 0, index("color", color))
}

! Get the background color for the page.
! If the database has no data, go with black.
f getBgColor ()
	$ color = msdb.selectValue("SELECT color FROM currentColor")
	? color = null
		& color = "black"
	}
	<- color
}

! Get all the colors in the database.  Failing that, start with a few.
f getColors ()
	$ colors = msdb.selectList("SELECT value FROM colors")
	? colors.length() = 0
		& colors = list("black", "blue", "green", "red")
		@ color : colors
			* msdb.define("colors", color)
		}
	}
	& colors = colors.sorted()
	-> colors
}

! Add a color to the database
f addColor (color)
	? isMatch(color, "^[a-zA-Z]+$")
		* msdb.define("colors", color)
	<>
		> "Invalid color: " + color + " - must be one word, all letters"
		> ""
	}
}

! Start over by resetting the database tables.
f resetColors ()
	* msdb.drop("colors")
	* msdb.drop("currentColor")
}

! Print the colors in the database.  Used for debugging.
f printColors ()
	@ color : getColors()
		> color
	}
}
%d bloggers like this: