House o' HyperCard
Scripts of the Month
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