|
|
Back in March, the HyperCard mailing list was discussing the possibility of a scripting contest - the slickest, fastest, crispest script to perform some given task. Jon Pugh came up with the challenge:
An index field which, when pasted onto a card, adds handlers to the background which allow it to get a name for each card (this handler is customized after pasting the field in, but can default to "line 1 of fld 1" or something) and do additions and subtractions from the index as cards are added and deleted, ideally completely automatically. Then it also needs to provide a way of building/rebuilding the index.
The criteria were: functionality, elegance, speed, brevity, clarity, originality, and the gut-reactions of the judges.
After arduous judging by the honorable judging panel of Jon Pugh (since it was his challenge), Catherine Kunicki (since the competition was her idea in the first place), and Jacqueline Landman Gay (whose name needs no parenthesized explanation), this space is proud to present the winning script, written by Jonathon Ashwell. You can either
download the stack (8K) with the winning field, or look at the script itself below.
-- AutoIndex, by Jonathan Ashwell
-- Westing Software, www.westinginc.com
--
-- To use:
--
-- 1. Copy and paste this text (the whole thing, including
-- comments) into the script of a new field (probably a
-- scrolling field, but that's up to you)
--
-- 2. Copy and paste that field into one or more backgrounds of a stack
--
-- 3. You're done.
--
--
-- Notes:
--
-- When pasted into the background layer of a stack, a field containing
-- these scripts will append newCard, deleteCard, and doMenu handlers to
-- the background script and do the following:
--
-- 1. Maintain a list of the cards in the same background as the field.
--
-- 2. List the cards by id's or names. The type of view is shown on the
-- first line of the list (List by..., in italics). Clicking on the
-- first line (List by ID or List by Name) toggles the list to the
-- other type. [Note: when listing by Name, unnamed cards are listed
-- as card id xxxx]. You can extend this to list *any* info. See comments
-- in the doListView function below for details.
--
-- 3. Lists of card names are updated on-the-fly when card names are
-- entered or edited.If you have a doMenu handler already in the
-- background, you will have to merge it with the doMenu handler
-- inserted by these scripts. I could circumvent this by assigning a
-- menuMsg to "Card Info..." and putting a handler in the stack script,
-- but I haven't because it's pretty intrusive.
--
-- 4. Clicking on the id or name of a card will take you to that card.
-- (This was not part of the original challenge, and the code in the
-- mouseUp handler can be commented out if desired.) The card name/id
-- does not stay hilited in the list (see comments below for reason).
-- This can be changed by commenting out one line.
--
-- 5. If the cards in the bg are reordered (by being sorted, for example), you can
-- update the list by clicking on the first line of the field (the List by... line)
--
-- 6. The scripts between the "-----------" dividers below are copied to the bg
-- script when this field is pasted into a stack.
-- It would be easy to implement the reverse (remove the AutoIndex scripts from
-- the bg script when the field is deleted by trapping for the deleteField message),
-- but I decided not to clutter up the scripts with this feature.
--
--7. No global variables were harmed in the making of these scripts.
------------------------------------------------------------------------
-- Slick Script entry AutoIndex, the background scripts
-- by Jonathan Ashwell
-- Westing Software, www.westinginc.com
on newCard
-- add this new card's name/id to the list
if there is a fld " Index " then -- make sure the field wasn't deleted from the stack
-- get the number of this cd (in this bg, *not* in the stack)
get number of this cd - number of cd 1 of this bg + 1
-- insert it into the Index field (in sequential order)
if "name" is in line 1 of fld " Index " then -- insert cd name
put return & short name of this cd after line it of fld " Index "
else -- insert cd id
put return & short id of this cd after line it of fld " Index "
end if
end if
pass newCard
end newCard
on deleteCard
-- remove the card id or name from the list
if the number of cds in this bg > 1 then
-- don't delete last item from fld if this is the last card in the bg
if there is a fld " Index " then
if "name" is in line 1 of fld " Index " then -- look for the name
get offset (return & short name of this cd & return, fld " Index ")
else -- look for the id
get offset (return & short id of this cd & return, fld " Index ")
end if
if it > 0 then
-- calculate the line number of the entry
get number of lines in char 1 to it of fld " Index "
delete line it + 1 of fld " Index "
end if
end if
end if
pass deleteCard
end deleteCard
on doMenu what
-- trap for "Card Info..." so we can detect when a card's name is changed
if what = "Card Info..." then -- Æ ? may need localization for non-English systems
if there is a fld " Index " then
if " name" is in line 1 of fld " Index " then
-- it is list is by name (don't bother to update on-the-fly if list is by id).
-- check for change in card name. If changed, update the list!
put short name of this cd into oldName
send "doMenu" && what to HyperCard -- let the user do whatever in the Card Info dialog box
if short name of this cd <> oldName then -- Name was changed. Update the list!
get offset (return & oldName & return, fld " Index ")
if it > 0 then
-- calculate the line number of the entry
get number of lines in char 1 to it of fld " Index "
-- replace the old name with the new one.
put short name of this cd into line it + 1 of fld " Index "
end if
end if
exit doMenu
end if
end if
end if
pass doMenu
end doMenu
-- end Slick Script AutoIndex background scripts
------------------------------------------------------------------------
on newField -- executed when the script is pasted into a stack
-- first, make sure we're in a bg. If not, offer to move there for the user.
-- second, set field properties
-- third, generate the list of cards by name
-- fourth, append the scripts listed above to the background script
if word 1 of name of me = "card" then -- Yikes, I was pasted into the card layer!
beep
answer "My scripts will not work in the card layer. Should I move myself to the background layer for you?" with "No" or "Yes"
if it = "Yes" then -- move to bg
doMenu "Clear Field" -- remove me
doMenu "Background"
doMenu "Paste Field" -- paste me again, into bg this time
doMenu "Background"
exit newField
-- another newField message has been generated, and will pass through this handler shortly
else -- remove myself
doMenu "Cut Field"
pass newField
end if
end if
set name of me to " Index " -- this should be changed (throughout)
-- in the unlikely event you already have a fld with this name
-- set my display properties. Edit to taste.
set the textFont of me to "geneva"
set the textSize of me to "10"
-- don't change these
set the lockText of me to "true"
set the dontWrap of me to "true"
set the autoSelect of me to "true"
set the sharedText of me to "true"
-- place "view by name" on first line and make list of card names
doListView "name"
-- copy the newCard, deleteCard, and doMenu scripts above to the background script
-- see if we're already installed in the bg script
if offset("-- Slick Script entry AutoIndex", script of this bg) = 0 then
put script of me into myScript
get offset("-- Slick Script entry AutoIndex, the background scripts", myScript)
put number of lines in char 1 to it of myScript into startLine
get offset("-- end Slick Script AutoIndex background scripts", myScript)
put number of lines in char 1 to it of myScript into endLine
put return & return & line startLine to endLine of myScript into slickScripts
get script of this bg
put slickScripts after it
if length of it > 29900 then
beep
answer "Background script is too long. Can't add AutoIndex scripts!" with "Bother!"
else
set script of this bg to it
end if
end if
pass newField
end newField
on mouseUp -- handle a click in the list
if word 2 of the selectedLine of me = 1 then
-- clicked on the List by... line
-- change the view (id vs. name)
if " Name" is in the selectedText of me then
doListView "id"
else
doListView "name"
end if
else -- go to the card clicked on.
-- Comment this part out if you *don't* want a mouse click in the
-- list to go to the selected card
put the scroll of me into saveScroll -- save scroll value of this list
get the selectedText of me
lock screen -- so we can set the scroll below without flicker
if " id" is in line 1 of fld " Index " then
-- it is id view. Go to the card of this id.
go cd id it
else
-- it is name view. Get the name and go to that cd.
-- (Note, if two cards in this bg have the same name, you will cycle between them.)
get the selectedText of me
if word 1 to 2 of it = "card id" then
go it
else
go cd it in this bg
end if
end if
-- Saving scroll above and restoring it here prevents the list from
-- "jumping" when going from cd to cd.
-- You may want to implement something similar in your closeCard and
-- openCard handlers.
set the scroll of name of me to saveScroll
-- HI decision here. I elect to *unhilite* the selected card name/id. I do this
-- because the selected card might *not* be the actual card the user is on
-- (because one can go from cd to cd many ways other than clicking on this list).
-- If you want the card name/id to stay hilited, comment out the next line.
select line 0 of me
unlock screen
end if
pass mouseUp
end mouseUp
on doListView listType -- generate the list of card id's or names
-- "listType" should be "id" or "name". Default is "name"
-- Note that you can list *any* info this way. For example, you could list
-- the contents of the field "foo" on each card. If you do so, you should also
-- modify the mouseUp handler so that it goes to the correct cd when you click
-- on it, such as:
-- get the selectedText of me
-- go cd where fld "foo" = it
-- Tip. Build the list in memory (the variable theIndex, here).
-- Only set up the field at the end. Much faster.
if there is a fld " Index " then
lock screen
if listType <> "id" then -- list by name
put " List by Name" & return into theIndex -- set first line
-- collect the card names here
repeat with i = 1 to number of cds in this bg
set cursor to busy -- bit bit of visual feedback
put short name of cd i of this bg after theIndex
put return after theIndex
-- if you combine these lines, it doesn't work! HC bug.
end repeat
else -- list by id
put " List by ID" & return into theIndex -- set first line
-- collect the card id's here
repeat with i = 1 to number of cds in this bg
set cursor to busy -- bit bit of visual feedback
put short id of cd i of this bg after theIndex
put return after theIndex
-- if you combine these lines, it doesn't work! HC bug.
end repeat
end if
put theIndex into fld " Index "
set the textstyle of line 1 of fld " Index " to italic
unlock screen
end if
end doListView
-- end of AutoIndex scripts
Congratulations!