While trying to understand spec, Iâve written an example of using dynamic specs. https://github.com/StephanEggermont/documentation/blob/master/SpecDynamicExa... A list of parties, where instances of different subclasses can be added. Comments & improvements are welcome. Stephan !!Parties, a dynamic example In an address book we find addresses of both people and organizations. The generalization of both is called a party ('Analysis Patterns', Martin Fowler). In this example we'll make an address book where we can add both persons and companies. They will have different attributes. We show how to do add them with a dynamic user interface, and minimize duplication. !!! The party model Create the abstract superclass ==Party== [[[ Object subclass: #DOParty instanceVariableNames: '' classVariableNames: '' category: 'Domain-Parties' ]] A party responds to the ==fullName== message. [[[ DOParty>fullName ^'Full name' ]]] The subclasses are going to override this message. Create a subclass for persons: [[[ DOParty subclass: #DOPerson instanceVariableNames: 'firstName lastName' classVariableNames: '' category: 'Domain-Parties' ]]] Create accessors for ==firstName== and ==lastName==. We don't want to need to handle ==nil== as a special case, so return the empty string if the instVars are nil. [[[ DOPerson>firstName ^firstName ifNil: [ '' ] DOPerson>firstName: aString firstName := aString DOPerson>lastName ^lastName ifNil: [ '' ] DOPerson>lastName: aString lastName := aString ]]] We can now override the ==fullName==. [[[ DOPerson>fullName ^self firstName, ' ', self lastName ]]] Create a subclass for companies [[[ DOParty subclass: #DOCompany instanceVariableNames: 'companyName' classVariableNames: '' category: 'Domain-Parties' ]]] And its accessors and the overridden method [[[ DOCompany>companyName ^ companyName ifNil: [''] DOCompany>companyName: anObject companyName := anObject DOCompany>fullName ^ self companyName ]]] In this example we will simply keep all parties in the image. We create a class to hold parties [[[ Object subclass: #DOPartiesModel instanceVariableNames: 'parties' classVariableNames: '' category: 'Domain-Parties' ]]] And lazily initialize with a collection [[[ DOPartiesModel>parties ^parties ifNil: [ parties := OrderedCollection new ] DOPartiesModel>parties: aCollection parties := aCollection ]]] On the class side we add an instanceVariable ==default== as the singleton and two methods to access and reset it. [[[ DOPartiesModel class instanceVariableNames: 'default' DOPartiesModel>>default ^default ifNil: [ default := self new ] DOPartiesModel>>reset default := nil ]]] !!! A dynamic editor To edit a single party, we create a subclass of ==DynamicComposableModel== [[[ DynamicComposableModel subclass: #DOPartyEditor instanceVariableNames: 'partyClass' classVariableNames: '' category: 'Domain-Parties' ]]] When we instantiate this editor, we'll tell it on what kind of party it operates and store that in the ==partyClass==. On the instance side we add accessors and on the class side we use that in a constructor. A ==DynamicComposableModel== has a complex initialization proces, so we use a separate ==basicNew== and ==initialize== to set the ==partyClass== early enough. [[[ DOPartyEditor>partyClass: aPartyClass partyClass := aPartyClass DOPartyEditor>partyClass ^partyClass DOPartyEditor>>on: aPartyClass ^self basicNew partyClass: aPartyClass; initialize; yourself ]]] This class has no ==defaultSpec==, as it is only created with a dynamic spec. The editor is going to be a separate window. The window title is dependent of the class. Party and subclasses define it at the class side. [[[ DOParty>>title "override in subclasses" ^'Party' DOCompany>>title ^'Company' DOPerson>>title ^'Person' DOPartyEditor>title ^ partyClass title ]]] The editor needs to know what fields need to be created. On the class side of the Party subclasses we return an array of symbols representing the fields. This will do for the example, for a real application with differnt kinds of fields Magritte descriptions are much more suitable. [[[ DOParty>>fields ^self subclassResponsibility DOCompany>>fields ^#(#companyName) DOPerson>>fields ^#(#firstName #lastName) ]]] Now we can initialize the widgets. ==instantiateModels== expects pairs of field names and field types and adds them to the widgets dictionary. They are then laid out in one column, given some default values and added in focus order. [[[ DOPartyEditor>initializeWidgets |models| models := OrderedCollection new. partyClass fields do: [ :field | models add: field; add: #TextInputFieldModel ]. self instantiateModels: models. layout := SpecLayout composed newColumn: [ :col | partyClass fields do: [ :field | col add: field height: self class inputTextHeight]]; yourself . self widgets keysAndValuesDo: [ :key :value | value autoAccept: true; entryCompletion:nil; ghostText: key. self focusOrder add: value] . ]]] The last thing needed is to calculate how large the window should be. It is going to be used as a dialog with ok and cancel that take up a height of about three input fields. [[[ DOPartyEditor>initialExtent ^ 300@(self class inputTextHeight*(3+partyClass fields size)) ]]] Now we can test the editor with ==(DOPartyEditor on: DOCompany) openDialogWithSpec== and ==(DOPartyEditor on: DOPerson) openDialogWithSpec== !!! The adres book We can now make the address book with a search field and buttons to add persons and companies. Add a class [[[ ComposableModel subclass: #DOPartiesList instanceVariableNames: 'search addPerson addCompany list' classVariableNames: '' category: 'Domain-Parties' ]]] As soon as something is typed in the search field, the list should show the list of parties having a fullName containing the search term, ignoring case. [[[ DOPartiesList>refreshItems |searchString| searchString := search text asLowercase. list items: (DOPartiesModel default parties select: [: each | searchString isEmpty or: [each fullName asLowercase includesSubstring: searchString]]); displayBlock: [ :each | each fullName]. ]]] We can now create the widgets and the default layout (class side) [[[ DOPartiesList>initializeWidgets search := self newTextInput. search autoAccept: true; entryCompletion:nil; ghostText: 'Search'. addPerson := self newButton. addPerson label: '+Person'. addCompany := self newButton. addCompany label: '+Company'. list := self newList. self refreshItems. self focusOrder add: search; add: addPerson; add: addCompany; add: list. DOPartiesList>>defaultSpec <spec: #default> ^SpecLayout composed newColumn: [ :col | col newRow: [:row | row add: #search; add: #addPerson; add: #addCompany] height: ComposableModel toolbarHeight; add: #list]; yourself ]]] ]]] When the user clicks on the ok button of the party editor, we need to create an instance of the right subclass, read the field values out of the editor and assign them to the attributes of the new instance. We do that using meta-programming (==perform:== and ==perform:with:==). Then we add the instance to the model and need to refresh the list. Add a method setting the okAction block of the editor. [[[ DOPartyList>addPartyBlockIn: anEditor anEditor okAction: [ |party| party := anEditor model partyClass new. anEditor model partyClass fields do: [ :field | party perform: (field asMutator) with: (anEditor model perform: field) text ]. DOPartiesModel default parties add: party. self refreshItems ]. ]]] Now we can initialize the presenter [[[ DOPartyList>initializePresenter search whenTextChanged: [ :class | self refreshItems ]. addPerson action: [ |edit| edit := (DOPartyEditor on:DOPerson) openDialogWithSpec. self addPartyBlockIn: edit]. addCompany action: [ |edit| edit := (DOPartyEditor on: DOCompany) openDialogWithSpec. self addPartyBlockIn: edit ] ]]] Don't forget the accessors [[[ DOPartyList>addCompany ^addCompany DOPartyList>addPerson ^addPerson DOPartyList>items: aCollection list items: aCollection DOPartyList>list ^list DOPartyList>search ^search ]]] protocol [[[ DOPartyList>resetSelection list resetSelection DOPartyList>title ^ 'Parties' ]]] and protocol-events [[[ DOPartyList>whenAddCompanyClicked: aBlock addCompany whenActionPerformedDo: aBlock DOPartyList>whenAddPersonClicked: aBlock addPerson whenActionPerformedDo: aBlock DOPartyList>whenSelectedItemChanged: aBlock list whenSelectedItemChanged: aBlock ]]] This can be tested with ==DOPartiesList new openWithSpec==