Places the last chosen item in predef position in choose from list

Hello.
EditThis is work in progress, the code below which is a demo should work properly!
Grab it and play with it, and please tell me what you think. The documentation is a bit out of sync with the
result. I’ll post below when I am finished with the documentation..
If you have comments, please send me messages or email me; I intend to delete the whole thread when I’m finished, and post it as a “clean” post here at code exchange when I’m finished.

Abstract

smartList enhances the capability of your choose from list dialogs behind the scenes.

[b]This script object helps you to:[color=green]

¢Lookup a value in any corresponding table based on the original value or position.
¢Rearrange the elements in the displayed list where this is feasible, and keep the relations to other tables intact.
¢Keeping relations between lists intact when items are inserted or deleted at random positions.
[/color][/b]

Introduction to smartList

What I have tried to here is to write a small but powerfull list manager tool, implemented as a script object especially designed with the choose from list dialog in mind though it has other usages too.

This is for the dialogs for choosing items where you are most likely to select the same item, next time or have some other kind of heuristic and where it is gainful to have the list reflect the chronological order of the items that where chosen, or have the list rearranged in some other way. You can f.ex have your last chosen item marked as default. (optional and very easy anyway).

This is the way I generally prefer to have “list dialogs” react upon choices. Especially those I use infrequently where the dialog retains a run history of the script, which I find useful.

You can also use it to have the last item you chose pop up in some default position where it is standing by so that it can be selected upon the next invocation. In addition, you could have the last chosen element(s) show up in or around the middle position of the choose from list dialog. If that is what suits your needs.

This script object was made to make it easier to use lists in combination with choose from list dialogs in your scripts. This has often been a hindrance to me as some scripts I have wanted to make in say five minute becomes more daunting when I have had to consider the list handling in addition to the main tasks of the script. Tasks that were really simple in it’s nature became undoable within time contstraints by this.

Facts
The main job which the smartList lets you factor out, and implement in your script as prebuilt functionality is:

¢Keeping relations between two or more  1 to 1 related lists where corresponding elements
 is in the exact same position: [b]whether elements are repositioned, the lists grow or shrink[/b].
¢ Moving a single or dynamic sized collection of list elements to a new position in a list, and
 synchronize the related lists to keep the relations intact.

Specifics

Capability provided for your script:

¢ You can rearrange a displayed list, without regard to its relations with other lists:
  either chronologically ascending or descending or by some other means.
¢ It can add either descending or ascending order of your users choices, which can make 
  choosing from a list more intuitive given the task at hand, dependent upon the likelihood
  of doing a same next choice. It can also be used to provide a command history.
¢ There is also a centering mode, which I personally like when there are more elements in
  the list, than visible on the screen, so that it is equally long way to both ends of the lists,
  making the traversal of the list seem smaller.

Constraints and assumptions about your lists

.¢ Your list needs to consist of unique elements only. This script object will not reflect well
   upon duplicate elements in a list and will produce an improper result.

¢  The lists aren't lexically sorted or must retain a lexically sorted order.

¢ If you use it to maintain relations among lists, then the relations must be consistent.
   Meaning: [b]the same number of elements must be in each list, that's your responsibility.[/b]
  The handler [b]updateList[/b]  takes care of retaining the order of the elements in the lists
  so that the relations aren't broken. [b]You must supply the extra necessary call per list to
  keep everything in sync[/b].

¢ If your lists grow or shrink, then you must update the [b]listProperty[/b] object to reflect
  the changes. [b]This must be done on a per deletion or insertion of a single element![/b].
  This is done with the [b]updateListProperties[/b] handler. [b]It is your responsibility to
  check that the list is not empty[/b] And you must of course [b]use the list you last selected
  something from in order to update it properly[/b]!

¢ If you are to change your positioning scheme (absolute, relative or centering, then you
  [b]must create a new listProperty object[/b]. What ever was selected from before is lost.
  It is up to you to make calls to make the last selection reappear, which is easily done with
  the [b]updateList[/b] handler.

¢ You must use the included [b]getDefaults[/b] handler for getting default items from your list
  to feed the [b]choose from list command[/b] with.

Installation and usage

Copy the script object into your code and peek at the examples. Next, copy in the one or two handler calls or boilerplate codes into your original script then adjust the parameter names, and off you go. it is totally transparent to your own logic once it is installed, it manages itself.

That’s all.

Example walkthrough:

I have an unsorted list of items, and I use to have element nr 1 of the list as the default item, since that gives me a chronological list over what I have done:

My list contains in the following order: Apples Pears Bananas

They show up like this:

Apples (displayed as the default item)

Pears

Bananas

  • I choose Bananas from the dialog; the list shows up like this on the next invocation:

Bananas (Is displayed as the default item)

Apples

Pears

This functionality was provided by this code in the script:

	set fruitProperties to smartList's scoMakeListPropertyObject(1,  fruitList)

	set chosenFruit to choose from list fruitList default items smartList's getDefaults(false, true, fruitProperties) with multiple selections allowed and empty selection allowed

    smartList's updateList( chosenFruit, fruitList, fruitProperties, true)

** – The return value chosenFruit is tell that this the isOrigList in case corresponding lists also need update.

    -- this is done with identical calls, but with some differences in the supplied parameters.

This code has proven itself to work whether a choice consists of single or multiple items (choices).

Summary:

All you have to do, is to copy the script object into your code:

Create a property object for your list(s) via a handler call which takes care of everything.

Then you must add the handler getDefaults from smartList to your choose dialog if you wish to leverage that handlers capabilites to allways return the right things.

At last, after you have made your choice you must add a call to the handler updateList. That’s all there is to it. After you have done so, you can forget all about it. There is no hindrance or rework of your existing source or logic if this implementation level is suitable for your needs. Everything is taken care of behind the scenes. If you are having relations between lists, which you must retain, ( keep corresponding elements in the same positions) then you have to add some more invocations of the previous handler call and change some parameters.

Are there any constraints or assumptions I should know about?

First of all: This doesn’t work well with sorted lists. If your list isn’t sorted, then this will work very well for you. For a solution for sorted lists, you would only retain the last chosen elements as the default items of the next run.

See “Sorted Lists” at the end of the post.

What did you say my responsibilities were?

Example usage 1) Don’ let this frighten you, but get the script object from below and play with it.

If you want more functionality as your script or app is dependent upon a relation by position with items in another list, then you just need to call the same handler and provide it with the result of the first hander call, in order to make the relation intact. The same goes for a third list and so on.

Example usage 2 “ A full demo retaining relations between two lists.


--  Though Nigel Garvey and Emmanuel Levy has unknowingly contributed to this by providing handlers.
-- The Idea and implementation and any faults is totally mine. © McUsr 2010 and put in the Public Domain.
-- The usually guarrantees about nothing what so ever applies, use it at your own risk.
-- The code is however carefully tested, as the demo below will prove. Read the documentation. It is all
-- hidden somewhere in there.
-- If you want to use this code for some commercial project, let me know about it. -And we will have a talk.
” You are not allowed to post this code elsewhere, but may of course refer to the post at macscripter.net.
” macscripter.net/edit.php?id=130214
(*
TERMS OF USE. 
This applies only to posting code, as long as you don't post it, you are welcome to do
whatever you want to do with it without any further permission. 
Except for the following: Selling the code as is, or removing copyright statmentents and the embedded link in the code (without the http:// part) from the code. 

You must also state what you eventually have done with the original source. This obviously doesn't matter if you  distribure AppleScript as read only. I do not require you to embed any properties helding copyright notice for the code.

Credit for having contributed to your product would in all cases be nice!

If you use this code as part of script of yours you are of course welcome to post that code with my code in it here at macscripter.net. If you then wish to post your code elsewhere after having uploaded it to MacScripter.net please email me and ask for permission.

The ideal situation is however that you then refer to your code by a link to MacScripter.net
The sole reason for this, is that it is so much better for all of us to have a centralized codebase which are updated, than having to roam the net to find any usable snippets. Which some of us probabaly originated in the first hand.

I'm picky about this. If I find you to have published parts of my code on any other site without previous permission, I'll do what I can to have the site remove the code, ban you, and sue you under the jurisdiction of AGDER LAGMANNSRETT of Norway. Those are the terms you silently agree too by using this code. 

The above paragraphs are also valid if you violate any of my terms.

If you use this or have advantage of this code in a professional setting, where professional setting means that you use this code to earn money by keeping yourself more productive. Or if you as an employee share the resulting script with other coworkers, enhancing the productivity of your company, then a modest donation to MacScripter.net would be appreciated.
*)
-- variables which just is for the demo.
set scriptTitle to "smartListDemo"
set iterNumber to 0
set theTitle to "Choose Items from a Static List"




set origTheThemesList to {"1 System Properties", "2 Basic Language Lore", "3 File I/O", "4 Dates", "5 Advanced Language Lore", "6 Text Manipulation", "7 Application - Script Models"}
set origRelatedLIst to {"chapter 1", "chapter 2", "chapter 3", "chapter 4", "chapter 5", "chapter 6", "chapter 7"}


copy origTheThemesList to theThemesList
copy origRelatedLIst to relatedLIst
set defaultPos to 1 -- This is an important variable, holding the default position or positioning method.
set scoThemesListProperties to smartList's scoMakeListPropertyObject(defaultPos, theThemesList)

set listChanged to false
-- 								listChanged is a variable I use to keep track of the changed status of the list.
--								If the list is changed, then we must update its properties.
--								You would want a variable like this signal that the list properties must be updated.

repeat
	
	set iterNumber to iterNumber + 1
	if iterNumber is 1 then
		showInfoAbout(defaultPos)
	end if
	set theResult to choose from list theThemesList default items smartList's getDefaults(false, true, scoThemesListProperties) with title theTitle with multiple selections allowed and empty selection allowed
	--	set theResult to {1, 2, 3}
	if theResult is false then exit repeat -- or theResult is {} then exit repeat
	set aRes to smartList's updateList(theResult, theThemesList, scoThemesListProperties, true)
	smartList's updateList(aRes, relatedLIst, scoThemesListProperties, false)
	set theResult to choose from list relatedLIst default items smartList's getDefaults(false, true, scoThemesListProperties) with title theTitle with multiple selections allowed and empty selection allowed
	if theResult is false then exit repeat -- or theResult is {} then exit repeat
	
	set aRes to smartList's updateList(theResult, relatedLIst, scoThemesListProperties, true) -- last updated which original were true, uses this to update with.
	set aRes to smartList's updateList(aRes, theThemesList, scoThemesListProperties, false) -- last updated which original were true, uses this to update with.
	
	
	
	if not (iterNumber is 1 or iterNumber is less than 3) then
		if iterNumber is 3 then
			display dialog "Deletions begins"
			set theTitle to "Choose Items from a Shrinking List"
			display dialog "Deletes first element."
			set theThemesList to rest of theThemesList
			set relatedLIst to rest of relatedLIst
			set listChanged to true
		else if iterNumber is 4 then
			display dialog "Deletes last element"
			set theThemesList to items 1 thru -2 of theThemesList
			set relatedLIst to items 1 thru -2 of relatedLIst
			set listChanged to true
		else if iterNumber is 5 then
			display dialog "deletes element nr 3"
			set theThemesList to items 1 thru 2 of theThemesList & items 4 thru -1 of theThemesList
			set relatedLIst to items 1 thru 2 of relatedLIst & items 4 thru -1 of relatedLIst
			set listChanged to true
		else if iterNumber is 6 then
			set theTitle to "Choose Items from a Growing List"
			display dialog "inserts element at front"
			set theThemesList to getAThemesItemWhichIsMissing(origTheThemesList, theThemesList) & theThemesList -- {"System Properties"}
			set relatedLIst to getARelatedItemWhichIsMissing(origRelatedLIst) & relatedLIst
			set listChanged to true
		else if iterNumber is 7 then
			display dialog "inserts element at the end"
			set theThemesList to theThemesList & getAThemesItemWhichIsMissing(origTheThemesList, theThemesList) -- {"Application - Script Models"}
			set relatedLIst to relatedLIst & getARelatedItemWhichIsMissing(origRelatedLIst) -- {"chapter 7"}
			set listChanged to true
		else if iterNumber is 8 then
			display dialog "inserts element into third position"
			set theThemesList to items 1 thru 2 of theThemesList & getAThemesItemWhichIsMissing(origTheThemesList, theThemesList) & items 3 thru -1 of theThemesList
			set relatedLIst to items 1 thru 2 of relatedLIst & getARelatedItemWhichIsMissing(origRelatedLIst) & items 3 thru -1 of relatedLIst
			set listChanged to true
		else if iterNumber is 9 then
			set theTitle to "Choose Items from a Static List"
			if defaultPos > -1 then
				set defaultPos to defaultPos - 1
				copy origTheThemesList to theThemesList
				copy origRelatedLIst to relatedLIst
				set scoThemesListProperties to smartList's scoMakeListPropertyObject(defaultPos, theThemesList)
				set iterNumber to 0
			else
				display dialog "All Done!
I really hope you lfind it useful. 
		Enjoy! McUsr © 2010 and put in Public Domain" with title scriptTitle
				return
			end if
		end if
	end if
	if listChanged is true then
		
		
		-- I update the list based on the list which last were selected something from.
		set scoThemesListProperties to smartList's updateListProperties(theThemesList, scoThemesListProperties)
		set listChanged to false
	end if
end repeat
-- helper functions for the demo.
on getAThemesItemWhichIsMissing(theOrigList, theCurrentList)
	script o
		property l : theOrigList
		property m : theCurrentList
	end script
	global uniqueItemPos
	local thisRes
	set uniqueItemPos to 1
	repeat with i from 1 to (count theOrigList)
		if contents of item i of o's l is not in o's m then
			set uniqueItemPos to i
			set thisRes to contents of item i of o's l
			exit repeat
		end if
	end repeat
	return thisRes as list
end getAThemesItemWhichIsMissing

on getARelatedItemWhichIsMissing(theOrigList)
	global uniqueItemPos
	return contents of item uniqueItemPos of theOrigList as list
end getARelatedItemWhichIsMissing

script smartList
	
	on scoMakeListPropertyObject(intDefaultChoicePosition, lstListValues)
		script o
			property lastValues : {} -- the last selected values
			property origPositions : {} -- the positions in the list  were the last selected values was found before they were moved into new positions.
			property actualPositions : {} -- the positions the chosen_items held after they are moved.
			-- needs this list in order to adjust for growage or shrinkage. Only updates this when we create a new list.
			property defaultPositon : intDefaultChoicePosition -- the position to use when only one selected item else - "positioning scheme"
			-- positive number -> from pos and downwards when multiple.
			-- zero --> centered around center of list.
			-- negative --> from pos and downwards if possible when multiple.
			property lHalf : 0
			property lRest : 0
			property listCount : 0
		end script
		set o's listCount to count of lstListValues
		set o's lHalf to (o's listCount) div 2
		set o's lRest to (o's listCount) mod 2
		o
	end scoMakeListPropertyObject
	
	on updateListProperties(lstListValues, scoSmartListProperties)
		-- may change every property but the defaultChoicePosition
		-- filters out any old values which aren't there any more from previous lastValues.
		-- adjust original positions with regard to the new positions of the items  this should be by a factor of 1 or -1.
		-- You are REQUIRED TO CALL THIS ROUTINE whenever you ADD OR DELETE ON SINGLE ELEMENT TO THE LIST.
		script o
			property l : lstListValues
			property oldActualPositionsList : {} -- the old positons, nees this to adjust relations among lists.
			property newActualPositionList : {} -- the new actual positons in list -- for same reasons.
			property newOrigPositions : {} -- stores the updated positions here until we copy them back.
			property newLastValuesList : {} -- The new list of chosen values, should any have been deleted. during shrink.
		end script
		local newDefaultValCount, theDiff, newListCount, tmp_list, thisElement, oldOriginalValues, marker
		
		set newListCount to count of lstListValues
		if newListCount ≠ scoSmartListProperties's listCount then
			set theDiff to newListCount - (scoSmartListProperties's listCount)
			set scoSmartListProperties's listCount to newListCount
			set scoSmartListProperties's lHalf to newListCount div 2
			set scoSmartListProperties's lRest to newListCount mod 2
			
			copy o's l to tmp_list
			set text item delimiters to return
			set tmp_list to return & tmp_list & return
			set text item delimiters to {""}
			set newDefaultValCount to 0
			
			repeat with i from 1 to (count scoSmartListProperties's lastValues)
				set thisElement to contents of item i of scoSmartListProperties's lastValues
				if thisElement is in items of o's l then
					set newDefaultValCount to newDefaultValCount + 1 -- counting up the elements left if shrinkage.
					copy thisElement to end of o's newLastValuesList
					set end of o's newActualPositionList to -1 + (count (paragraphs of (text 1 thru (offset of (return & thisElement & return) in tmp_list) of tmp_list)))
					copy contents of item i of scoSmartListProperties's origPositions to end of o's newOrigPositions
					copy contents of item i of scoSmartListProperties's actualPositions to end of o's oldActualPositionsList
				end if
			end repeat
			-- what we got so far is any elements left in the last chosen values list together with 
			-- corresponding elements of the original and actual positions. we will now use the difference
			-- between the currently actual positions and the old actual positions to deduce how we must adjust
			-- the original positions to be in sync with the current state of the list.
			set marker to newDefaultValCount + 1
			repeat with i from 1 to newDefaultValCount
				if contents of item i of o's newActualPositionList is not contents of item i of o's oldActualPositionsList then
					set marker to contents of item i of o's oldActualPositionsList
					exit repeat
				end if
			end repeat
			-- here we adjust the positons in the newOrigPositons list according to our findings.
			-- we are bruteforcing our way through with no regards for order.
			repeat with i from 1 to newDefaultValCount
				if contents of item i of o's newOrigPositions ≥ marker then
					set item i of o's newOrigPositions to (contents of item i of o's newOrigPositions) + theDiff
				end if
			end repeat
			
			copy o's newActualPositionList to scoSmartListProperties's actualPositions
			copy o's newLastValuesList to scoSmartListProperties's lastValues
			copy o's newOrigPositions to scoSmartListProperties's origPositions
		end if
		return scoSmartListProperties
	end updateListProperties
	
	
	on getDefaults(blnReturnSingle, blnOriginalList, scoSmartListProperties)
		if blnReturnSingle is true then
			local itmCount
			set itmCount to (count scoSmartListProperties's lastValues)
			if itmCount is 0 then return {}
			if scoSmartListProperties's defaultPositon < 0 then
				return item itmCount of scoSmartListProperties's lastValues as list
			else
				return item 1 of scoSmartListProperties's lastValues as list
			end if
		else
			return scoSmartListProperties's lastValues as list
		end if
	end getDefaults
	
	
	
	--	Updates the order of the values of the list: based upon properties in the scriptObject scoSmartListProperties
	-- It manages two kinds of lists; 
	--				Original lists, which are the list which the item was selected from in the choose from list dialog.
	--				Related lists, which are lists which are in a 1 to 1 relation with the Original list.
	-- When fed either type of list it will:
	--				return the original positions of the items moved to maintain order among multiple lists.
	--				Those values are also stored internally in a list in the scriptObject scoSmartListProperties.
	--				The same goes for: the selected values, and their actual postions in the arranged list
	-- IMPLICATIONS:
	--		When updating the properties of a list if it has grown or shrunk, the operations must be performed
	--		in an orderly manner: 
	-- 		THE LAST LIST YOU USED updateList() ON SHOULD BE THE SAME LIST YOU USE TO UPDATE THE scoSmartListProperties
	--		SCRIPT OBJECT BY THE updateProperties() HANDLER WITH.
	
	on updateList(lstChosenItems, lstListValues, scoSmartListProperties, blnIsOriginalList)
		script o
			property l : lstListValues
			property saved_list : {}
		end script
		local choicesCount, listCount, intDefaultItemPosition, startpos, tmp_list, item_index, step, i, thisElement, newActualPositons, newOrigPositions, savedOrigPositions
		
		if lstChosenItems is false then
			tell me to display alert "listUpdater's updateList: I really shouldn't be given a list with one value of false... I quit!"
			error number -128
		end if
		
		set choicesCount to (count of lstChosenItems)
		set listCount to scoSmartListProperties's listCount
		if choicesCount is 0 then
			return {}
		else if choicesCount > listCount then
			tell me to display alert "listUpdater's updateList: There were more chosen items than elements in the list supposed to be chosen from.I quit!"
			error number -128
		end if
		set intDefaultItemPosition to scoSmartListProperties's defaultPositon
		-- ** PRELIMNARY calculations for adjusting any optimistic whishes the user may have to what the list really contains .
		-- ex. wants the chosen item to be placed in last positon and have chosen 3 ...
		if intDefaultItemPosition < 0 then -- then relative addressing from the end -1 is last element and so on.
			if listCount + intDefaultItemPosition < 0 then
				set startpos to listCount
			else
				set startpos to listCount + intDefaultItemPosition + 1
			end if
			-- chosen items should effectively end in the final position.
			set startpos to startpos + 1 - choicesCount
			
		else if intDefaultItemPosition is 0 then -- calculates start position relative to center of list.
			local cHalf, cRest, lHalf, lRest
			set cHalf to choicesCount div 2
			if cHalf is 0 then set cHalf to 1
			set lHalf to scoSmartListProperties's lHalf
			if lHalf is 0 then set lHalf to 1
			set cRest to choicesCount mod 2
			
			set lRest to scoSmartListProperties's lRest
			
			if cRest - lRest = 0 then
				set startpos to (lHalf + 1 - cHalf)
			else if cRest = 1 then -- lCount even and minimum 2 
				set startpos to (lHalf - cHalf)
			else if lRest = 1 then -- cCount even 
				set startpos to (lHalf - cHalf + 1)
			end if
		else -- intDefaultItemPosition > 0 rather vanilla positioning.
			local posAdjust
			if intDefaultItemPosition > listCount then
				set intDefaultItemPosition to listCount
			end if
			set startpos to intDefaultItemPosition
			-- ensures that the list can actualla fit into free slots lefte, otherwize adjust startpos upwards.
			set posAdjust to (intDefaultItemPosition + choicesCount - 1) - listCount
			if posAdjust > 0 then
				set startpos to startpos - posAdjust
			else
				set startpos to intDefaultItemPosition -- starts at that position and works downwards
			end if
		end if
		
		
		if blnIsOriginalList is false then -- we are updated an related list based upon the changes we did on the list we choosed items from.
			-- This is a rather odd situation, since we can have lists we never display.
			if class of item 1 of lstChosenItems is not integer then
				tell me to display alert "listUpdater's updateList: The chosen items were not numbers as they should be when the list is a *related list*. I quit!"
				error number -128
			else
				-- arrangements for making the centering work in ALL situations.
				-- creates a new set of lastValues based upon origPositions, opposite to below. if intDefaultPosition < 1 items in origpositions is in order.
				set scoSmartListProperties's lastValues to {} -- makes a new set of saved values.
				if scoSmartListProperties's origPositions is {} then return {}
				-- we got some original positions, which is what we are feeding on, 
				if intDefaultItemPosition is 0 then
					-- special precautions to make centering  the chosen item it always work, this is tricky because the first
					-- items in the list may be chosen, so we have to move the items to the end before setting them
					-- into the correct positions. At the moment I see now way of combining this with the
					-- the branch covering  elements to be sat with absolute or relative positioning
					set newOrigPositions to {}
					copy scoSmartListProperties's origPositions to savedOrigPositions -- ! saved
					copy o's l to tmp_list -- only does this when we have to!
					-- retain original positions of the items.
					set text item delimiters to return --  credits Emmanuel Levy's indexof() handler
					set tmp_list to return & tmp_list & return
					set text item delimiters to {""}
					
					repeat with choice_item_index from 1 to choicesCount -- what's in this loop is mainly due to Nigel Garvey.
						-- we put the elements reversed onto the end of list before setting the into correct postions.
						set item_index to listCount - choice_item_index + 1 -- Cater for reverse ordering from the end of the list.
						set end of newOrigPositions to item_index
						set thisElement to item (item (choicesCount - choice_item_index + 1) of savedOrigPositions) of o's l
						set i to -1 + (count (paragraphs of (text 1 thru (offset of (return & thisElement & return) in tmp_list) of tmp_list)))
						-- Does it have to be moved forwards or backwards (or neither)?
						if (item_index > i) then
							set step to 1
						else
							set step to -1
						end if
						-- Move the intervening items up or down by one.
						repeat with i from i to (item_index - step) by step
							set item i of o's l to item (i + step) of o's l
						end repeat
						-- Insert the item into the required location.
						set item item_index of o's l to thisElement
						copy o's l to tmp_list -- The superfluos one will be used below!
						set text item delimiters to return
						set tmp_list to return & tmp_list & return
						set text item delimiters to {""}
					end repeat
					copy newOrigPositions to scoSmartListProperties's origPositions
					
					repeat with choice_item_index from 1 to choicesCount
						set item_index to startpos + choice_item_index - 1 -- static positon, the list arranges for us.
						set thisElement to item (listCount - choicesCount + choice_item_index) of o's l
						
						set end of scoSmartListProperties's lastValues to thisElement
						set i to -1 + (count (paragraphs of (text 1 thru (offset of (return & thisElement & return) in tmp_list) of tmp_list)))
						-- Does it have to be moved forwards or backwards (or neither)?
						if (item_index > i) then
							set step to 1
						else
							set step to -1
						end if
						-- Move the intervening items up or down by one.
						repeat with i from i to (item_index - step) by step
							set item i of o's l to item (i + step) of o's l
						end repeat
						-- Insert the item into the required location.
						set item item_index of o's l to thisElement
					end repeat
					-- after care
					copy savedOrigPositions to scoSmartListProperties's origPositions
					return scoSmartListProperties's origPositions
					-- Shouldnt be necessary to do anything with actuaal positions.
				else
					copy o's l to o's saved_list -- can be optimized but ...
					repeat with choice_item_index from 1 to choicesCount
						copy o's l to tmp_list -- only does this when we have to!
						-- retain original positions of the items.
						set text item delimiters to return --  credits Emmanuel Levy's indexof() handler
						set tmp_list to return & tmp_list & return
						set text item delimiters to {""}
						if intDefaultItemPosition < 0 then
							set item_index to (startpos + choicesCount - choice_item_index)
						else
							set item_index to (startpos + choice_item_index - 1)
						end if
						set thisElement to item (item choice_item_index of scoSmartListProperties's origPositions) of o's saved_list
						set end of scoSmartListProperties's lastValues to thisElement
						set i to -1 + (count (paragraphs of (text 1 thru (offset of (return & thisElement & return) in tmp_list) of tmp_list)))
						-- Does it have to be moved forwards or backwards (or neither)?
						if (item_index > i) then
							set step to 1
						else
							set step to -1
						end if
						-- Move the intervening items up or down by one.
						repeat with i from i to (item_index - step) by step
							set item i of o's l to item (i + step) of o's l
						end repeat
						-- Insert the item into the required location.
						set item item_index of o's l to thisElement
					end repeat
					return scoSmartListProperties's origPositions
				end if
			end if
		else if blnIsOriginalList is true then
			-- creates new orig positions based upon lstChosenItems to use from a related list.
			if intDefaultItemPosition < 0 then -- NEW center works.
				set lstChosenItems to reverse of lstChosenItems
			end if
			copy lstChosenItems to scoSmartListProperties's lastValues
			copy o's l to tmp_list
			set scoSmartListProperties's origPositions to {}
			-- retain original positions of the items.
			set text item delimiters to return -- will be leveraged below.
			set tmp_list to return & tmp_list & return
			set text item delimiters to {""}
			repeat with i from 1 to choicesCount --  loop credits Emmanuel Levy's indexof() handler
				try
					set end of scoSmartListProperties's origPositions to -1 + (count (paragraphs of (text 1 thru (offset of (return & item i of scoSmartListProperties's lastValues & return) in tmp_list) of tmp_list)))
				on error
					tell me to display alert "listUpdater's updateList: There were an element in the choices list that were not present in the list of possible choices. I quit!"
					error number -128
				end try
			end repeat
			
			if intDefaultItemPosition is 0 then -- must assure that the elements are placed AFTER startpos in order for NEXT STEP to work.
				-- we  move every item which is to be centered to then end of the list in reverse order in order for them
				-- to be positioned correctly.
				
				set newOrigPositions to {}
				copy scoSmartListProperties's origPositions to savedOrigPositions -- ! saved
				repeat with choice_item_index from 1 to choicesCount -- what's in this loop is mainly due to Nigel Garvey.
					set item_index to listCount - choice_item_index + 1 -- Cater for reverse ordering from the end of the list.
					set end of newOrigPositions to item_index
					set i to -1 + (count (paragraphs of (text 1 thru (offset of (return & item choice_item_index of scoSmartListProperties's lastValues & return) in tmp_list) of tmp_list)))
					-- Does it have to be moved forwards or backwards (or neither)?
					if (item_index > i) then
						set step to 1
					else
						set step to -1
					end if
					-- Move the intervening items up or down by one.
					repeat with i from i to (item_index - step) by step
						set item i of o's l to item (i + step) of o's l
					end repeat
					-- Insert the item into the required location.
					set item item_index of o's l to item choice_item_index of scoSmartListProperties's lastValues --  listElm -- replacement_item
					copy o's l to tmp_list -- The superfluos one will be used below!
					set text item delimiters to return
					set tmp_list to return & tmp_list & return
					set text item delimiters to {""}
				end repeat
				copy newOrigPositions to scoSmartListProperties's origPositions
			end if -- this will work automatically on a related list. 
			repeat with choice_item_index from 1 to choicesCount -- what's in this loop is mainly due to Nigel Garvey.
				if intDefaultItemPosition < 0 then
					set item_index to (startpos + choicesCount - choice_item_index)
				else
					set item_index to (startpos + choice_item_index - 1)
				end if
				set i to -1 + (count (paragraphs of (text 1 thru (offset of (return & item choice_item_index of scoSmartListProperties's lastValues & return) in tmp_list) of tmp_list)))
				-- Does it have to be moved forwards or backwards (or neither)?
				if (item_index > i) then
					set step to 1
				else
					set step to -1
				end if
				-- Move the intervening items up or down by one.
				repeat with i from i to (item_index - step) by step
					set item i of o's l to item (i + step) of o's l
				end repeat
				-- Insert the item into the required location.
				set item item_index of o's l to item choice_item_index of scoSmartListProperties's lastValues --  listElm -- replacement_item
				copy o's l to tmp_list -- The superfluos one will be used below!
				set text item delimiters to return
				set tmp_list to return & tmp_list & return
				set text item delimiters to {""}
			end repeat
			-- finally we create a list with the actual positions -- needs them for reference should we ever need to
			-- update any relations among lists, should an item have been deleted or inserted.
			if intDefaultItemPosition is 0 then -- after care
				copy savedOrigPositions to scoSmartListProperties's origPositions
			end if
			set scoSmartListProperties's actualPositions to {}
			repeat with choice_item_index from 1 to choicesCount
				set end of scoSmartListProperties's actualPositions to -1 + (count (paragraphs of (text 1 thru (offset of (return & item choice_item_index of scoSmartListProperties's lastValues & return) in tmp_list) of tmp_list)))
			end repeat
		end if
		return scoSmartListProperties's origPositions
	end updateList

” A simpler version that returns the elements of a second 1:1 related list
” based upon a selection of items to first.
” The two lists must be corresponding item by item.
”PARAMETERS:
” lstItemsFromFirst : The items from the first list we want to find corresponding items in the second for.
” lstTheFirstList : The first list
” lstTheSecondList: The second list
” RETURNS:
” Any corresponding items.
on getRelatedItems(lstItemsFromFirst, lstTheFirstList, lstTheSecondList)
	script o
		property theItems : lstItemsFromFirst
		property firstList : lstTheFirstList
		property secondList : lstTheSecondList
	end script
	local itemsCount, firstListCount, secondListCount, tmp_list, origPositions, newList, step, i, thisElement, newActualPositons, savedOrigPositions
	-- initial qualification of parameters.
	if lstItemsFromFirst is false then
		tell me to display alert "smartList's getRelatedItems: I really shouldn't be given a list with one value of false... I quit!"
		error
	end if
	set itemsCount to (count of lstItemsFromFirst)
	set firstListCount to (count of lstTheFirstList)
	set secondListCount to (count of lstTheSecondList)
	-- secondary qualification of parameters
	if firstListCount ≠ secondListCount then
		tell me to display alert "smartList's getRelatedItems: I really shouldn't be given lists with different number of items : firstList : " & firstListCount & " second list : " & secondListCount & " I quit!"
		error
	else if itemsCount > firstListCount then
		tell me to display alert "smartList's getRelatedItems:  The number of the chosen items is " & itemsCount & " which is more than there is in the actual list : " & firstListCount & " I quit!"
		error
	end if
	if itemsCount is 0 then
		return {}
	end if
	-- The rest of the error conditions would now be that we couldn't find items in the lstTheFirstList which were in the lstItemsFromFirst
	-- get the positions of the elements in the first list.
	copy o's firstList to tmp_list
	set text item delimiters to return --  credits Emmanuel Levy's indexof() handler
	set tmp_list to return & tmp_list & return
	set text item delimiters to {""}
	set origPositions to {}
	repeat with choice_item_index from 1 to itemsCount
		set end of origPositions to -1 + (count (paragraphs of (text 1 thru (offset of (return & (item choice_item_index of o's theItems) & return) in tmp_list) of tmp_list)))
	end repeat
	
	if 0 is in origPositions then
		tell me to display alert "smartList's getRelatedItems:  There were items in the items from first list which I didn't find in the first - base - list!  I quit!"
		error
	end if
” Retrieves the corresponding items from the second list.
	set newList to {}
	repeat with aPosition in origPositions
		set end of newList to contents of item aPosition of o's secondList
	end repeat
	return newList
end getRelatedItems

end script

on showInfoAbout(positioningType)
	local scriptTitle
	set scriptTitle to "smartListDemo"
	if positioningType is -1 then
		display dialog "¢ The default position is now specified to be in the
  last position of the list.

¢ A chosen element or elements will be moved to
  or towards this position.

¢ This positions is specified relative to the
   end of the list.

¢ It is possible to specify f.ex position -3 to place
   an element in the pos 3 elements above the end
   of the list." with title scriptTitle
	else if positioningType is 0 then
		display dialog "¢ The default position now specified to be  in the
  middle of the list.

¢ A chosen element will be moved to or towards
  this position centred around the middle.

¢ This functionality can be obtained by specifying
   a default position of 0."
	else if positioningType is 1 then
		display dialog "Observe that the lists grow and shrink under the demo. Please also observe that the  corresponding element in the related list -either the themes list or the chapter list, are updated with the corresponding elment in the same position.

Pleas try to select elements, single and multiple and combinations thereof, and watch the results as the list is growing or shrinking.
I hope you have the patience to go through the somewhat boring and maybe far to long demo.

	Thanks - McUsr" with title scriptTitle
		
		
		display dialog "¢ The default position is now specified to be in the
  first position of the list.

¢ A chosen element or elements  will be moved to
  or towards this position.

¢ This position is specified  absolutely in the list.
¢ It is possible to f.ex specify pos nr 3." with title scriptTitle
	end if
end showInfoAbout


Hopefull you have now sensed that the code for this functionality is very transparent to what your code really is meant to do and doesn’t obstruct it in any way.

This is specific to the handler call, which delivers the default items, in your choose dialog call.

You can provide an integer specifying the accurate or relative position in the list that the default item should have. Note, you can also use the number 0 to specify the middle position in the list.

If you specify the middle position, then if you had selected more than one item from last choose dialog, then the elements will be positioned around the middle position.

About the code

Emmanuel Levy and Nigel Garvey has provided the optimized code for this script object to work pretty fast. All blame, -as the idea for this is mine.
All necessary code is baked into a script object that consists of properties and four handlers (where only two of them is necessary), that’s all you have to do.
– Yeah and it should fairly simple to move the script object into a script server :).

It is fairly robust, however, if the default position submitted does not exist within the list then it will try to put the items as near that position as possible. Negative offsets are allowed, letting you specify for instance a position of “3 or the 3d element from the end, as the default position should you so choose.

The handler is meant to act within your code as transparently as possible. You should be able to just add the handler statements and forget all about it (as shown in example above). Everything is taken care of behind the scenes. A little extra work is required if you have another list which is related to the list with choices, but the single handler call handles that too, you just need to add the extra statements.

script object contents

– documented handler calls.


Sorted Lists

It is very easy to just save the last chosen item, or set of items into a property, which is saved with the script as long as the scriptrunner you use saves the script upon an exit with some return value. (Apples vanilla Script Menu does that.)

Example of retaining last chosen items in a sorted list.

Best Regards

McUsr

Hello!

ArrrgggH! :slight_smile:

If you have run the demo already, then it hasn’t worked properly if you haven’ done the changes as below.

Please change the line:


set defaultPos to -1 -- This is an important variable, holding the default position or positioning method.

to :


set defaultPos to 1 -- This is an important variable, holding the default position or positioning method.

Then you will se all three modes demonstrated. (I have updated the code above.) Sorry.

I hope this runs fast enough to be usable under Tiger and Leopard on older machines.
Please let me know. (Especially if it don’t)

Best regards

McUsr

Hello.

Updated the code with a simple handler for just returning the related items from a list based on a 1 to 1 correspondence (positioning of items) in the two lists.

Its name is getRelatedItems and can be found at the bottom of the script object in the first post above.

Best Regards

McUsr

Hello

I have edited the documentation, but it is not done yet.

Best Regards

McUsr