Previous
Contents
Next

A Case Study in Component Development - Additional Features


The implementation of the full expansion feature is nothing but a programming exercise.

Some modifications in method expand: are convenient:

expand: idx
   | cls subclasses newItems currentLevel |
  cls := collectionOfObjects at: idx.
  currentLevel := level at: idx.
  (idx < collectionOfObjects size
        and: [(level at: idx + 1) > currentLevel])
    ifTrue: [^self].

  subclasses := cls subclasses 
                     sort: [:cl1 :cl2 | cl1 name < cl2 name].
  newItems := self expandOneLevel: idx.

  collectionOfObjects := (collectionOfObjects copyFrom: 1 to: idx),
                           subclasses,
                           newItems first,
                           (collectionOfObjects
                                   copyFrom: idx + 1
                                   to: collectionOfObjects size).
  level := (level copyFrom: 1 to: idx),
                 (RunArray new: subclasses size withAll: currentLevel + 1),
                 newItems last,
                 (level copyFrom: idx + 1 to: level size).
  self changed: #getClassesList;
       changed: #getClassSelection.

The new method expandOneLevel: fetches the immediately subordinated elements, but it does not update the view:

expandOneLevel: idx
   | cls subclasses currentLevel |
  cls := collectionOfObjects at: idx.
  currentLevel := level at: idx.

  subclasses := cls subclasses sort: [:cl1 :cl2 | cl1 name < cl2 name].

  ^Array with: subclasses
         with: (RunArray new: subclasses size withAll: currentLevel + 1)

Full expansion is implemented by a new method that is similar to the method that implements one-level expansion. Like expand:, this message is designed to implement a menu item. It performs the required action and sends change-messages to update the view.

expandAll: idx
   | currentLevel |
  currentLevel := level at: idx.
  (idx < collectionOfObjects size
        and: [(level at: idx + 1) > currentLevel])
    ifTrue: [^self].
  self expandAllLevels: idx.
  self changed: #getClassesList;
       changed: #getClassSelection.

The real work is done in the method expandAllLevels: which expands one level and is then recursively called once for every new element. This method is designed to serve as a working-horse behind the scene - it does not send messages to update the view.

Note that it is crucial for the correctness of the algorithm to process the new elements from the last one to the first one.

expandAllLevels: idx
   | oneLevel newItems newLevel |

  oneLevel := self expandOneLevel: idx.
  newItems := oneLevel first.
  newLevel := oneLevel last.

  newItems isEmpty
    ifFalse:
      [collectionOfObjects := 
	        (collectionOfObjects copyFrom: 1 to: idx),
                    newItems,
                    (collectionOfObjects copyFrom: idx + 1 
                                         to: collectionOfObjects size).
       level := (level copyFrom: 1 to: idx),
                     newLevel,
                     (level copyFrom: idx + 1 to: level size).
      ].
  idx + newItems size to: idx + 1 by: -1 do:
    [:i | self expandAllLevels: i]

The condition newItems isEmpty is not strictly necessary, it is useful to avoid the splitting and concatenation of the collections when nothing will be inserted.

To make the new feature available, we have to add one option to the view menu:

getMenu: aCustomMenu
  selectionIdx = 0
     ifTrue: [^nil].
  aCustomMenu
       add: 'expand' target: self selector: #expand: argument: selectionIdx;
       add: 'expand all' target: self selector: #expandAll: argument: selectionIdx;
       add: 'collapse' target: self selector: #collapse: argument: selectionIdx.
   ^aCustomMenu

The change set for this example is TreeView2.1.cs.

This works beautiful, but it is a very special application. It is of course possible to copy the methods of this mini-application into every model that has to support a hierarchical list, but it is certainly better to have a component that implements the hierarchical list in a way that allows easy reuse. This is our next topic.


Previous
Contents
Next