Custom action


As you are developing an explorable model of your application, you may find yourself repeatedly evaluating the same code snippets to perform a given action. How can you streamline these tasks?


Repeated tasks are annoying and time-consuming.

Remembering how to perform common tasks increases cognitive overload.


Add a custom action button to the key objects involved in the repeated tasks, encapsulating the boilerplate code to perfom them.


Consider GT slideshows. These are classes containing methods that use the <gtSlide> pragma. Here is a list of all such classes in this image:

(#gtSlide gtPragmas contents collect: #methodClass) asSet

The way to play a slideshow in a new window, for example, the GtTour Object subclass: #GtTour uses: TGtSlideShow instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Demo-MoldableDevelopment-Slideshows' slideshow, is to evaluate a snippet like this:

GtPresenterSlideShow openInSpace: GtTour

Sometime this information is stored in a class comment, as is the case for GtTour Object subclass: #GtTour uses: TGtSlideShow instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Demo-MoldableDevelopment-Slideshows' , or in cases where the slideshows inherit from a common class, it can be encapsulated in a class-side method, such as PrioritySlideshow>>#show show "NB: this is a class-side method." GtPresenterSlideShow openInSpace: self , but in any case, extra steps have to be performed to play the slideshow.

A solution is to add a custom <gtAction> button which will be displayed when you inspect the class. Here we see custom gtAction buttons to Inspect and to Play the GtPharo101 Object subclass: #GtPharo101 uses: TGtSlideShow instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Demo-Pharo101' slideshow.

Since most slideshow classes typically do share a common slideshow hierarchy, these actions are implemented in a trait TGtSlideShow Trait named: #TGtSlideShow instanceVariableNames: '' package: 'GToolkit-Presenter-Models - Support' , which can then used by all slideshow classes.

The implementation is straightforward in this case GtPharo101>>#gtOpenSlideshowActionFor: gtOpenSlideshowActionFor: anAction <gtAction> <gtClassAction> (self methods select: [ :m | m hasPragmaNamed: #gtSlide ]) isEmpty ifTrue: [ ^ anAction noAction ]. ^ anAction button priority: 11; tooltip: 'Play slideshow in new window'; icon: BrGlamorousVectorIcons play; action: [ :aButton | GtPresenterSlideShow openInSpace: self ]

In addition to the <gtAction> pragmas, there is also a <gtClassAction> pragma, so the button will also appear in a Coder.

The first line of the method checks that the class actually has a <gtSlide> method, otherwise the button will be suppressed (noAction). Then a button is displayed with a priority (the order in which buttons appear), a tooltip to display, an dicon and the action to perform.

Another typical example is the Open page in a new world tab button at the top of this page. You can Secondary-click on the button to see its implementation in LePage>>#gtOpenInNewTabActionFor: gtOpenInNewTabActionFor: anAction <lePageAction> ^ anAction button id: LeOpenPageInNewWorldTabElementId; tooltip: 'Open page in a new world tab'; icon: BrGlamorousVectorIcons spawn; action: [ :aButton | GtWorldUtility showSpaceWithTitle: self title inPagerWith: [ self asLepiterPagePhlowTool asElementDo: [ :e | e ] ] asStencil from: aButton ] . It streamlines the task of opening a new window for the current Lepiter notebook page, for example, by copying the page title, pasting it into Spotter, and then opening the found page.

Related patterns

A Viewable entity has a similar intent, but is more suitable when the goal is to quickly access information in an Inspector view that would otherwise be found only be one or more navigation steps.