I'm using a similar approach for Mars. Views in Mars always consume from a similar structure and we have the concept of "model adaptor" when you need to adapt from your model to the views. We also have a couple of generic adaptors who adapts nicely to your model. Let's say we need to place a string into a MRLabel, and model for label is prepared to understand #content message. so you say something like this: MRLabel new model: ('My label' adaptAccessor: #yourself); ...etc... and that way you have "cleanness" in both sides :) other problems is when widgets triggers events (like your example, a button click, named performAction). I think here you should not trigger anything but an announcement. Your performAction should look more like: performAction self announce: ButtonPressed and your "client code" should look: MRButton new on: ButtonPressed do: [ ... whatever... ] So, summarizing.... I "more or less" agree with your approach. Or better I mean: I agree in the "accessing" part, but I would like using announcements to trigger events. cheers, Esteban El 07/04/2011, a las 10:26a.m., Igor Stasenko escribió:
I have an idea how to refactor the pluggable morphs mess.
The main principle behind all pluggables, that you have a model (which is held in instance variable), and then morph having additional ivars to speak with this model, which the selector names you should use to access certain state from model.
For example:
AlignmentMorph subclass: #PluggableButtonMorph instanceVariableNames: 'model label getStateSelector actionSelector getLabelSelector getMenuSelector shortcutCharacter askBeforeChanging triggerOnMouseDown offColor onColor feedbackColor showSelectionFeedback allButtons arguments argumentsProvider argumentsSelector gradientLook enabled actionBlock getColorSelector getEnabledSelector' classVariableNames: 'UseGradientLook' poolDictionaries: '' category: 'Morphic-Pluggable Widgets'
as you can see it is crowded with all those ivars, where part of them used to speak with model, and part of them used for keeping flags and additional parameters which you can customize when creating a button. getStateSelector actionSelector getLabelSelector getMenuSelector arguments argumentsProvider argumentsSelector gradientLook actionBlock getColorSelector getEnabledSelector
and this is really messy.. Take a look at code:
performAction "Inform the model that this button has been pressed. Sent by the controller when this button is pressed. If the button's actionSelector takes any arguments, they are obtained dynamically by sending the argumentSelector to the argumentsProvider"
enabled ifFalse: [^self]. askBeforeChanging ifTrue: [model okToChange ifFalse: [^ self]]. self actionBlock ifNotNil: [ ^ self actionBlock value]. actionSelector ifNotNil: [actionSelector numArgs == 0 ifTrue: [model perform: actionSelector] ifFalse: [argumentsProvider ifNotNil: [arguments := argumentsProvider perform: argumentsSelector]. model perform: actionSelector withArguments: arguments]]
Now think if we could replace this with simple:
performAction "Inform the model that this button has been pressed. Sent by the controller when this button is pressed. If the button's actionSelector takes any arguments, they are obtained dynamically by sending the argumentSelector to the argumentsProvider"
enabled ifFalse: [^self]. model performAction.
so, in this case, we just put an obligation onto model to perform any action in response to button clicked, and button no longer cares about crappy logic how it should send a message to model (via action block, via actionSelector ... and whether it should pass any additional arguments or not..)
So, in all places which using things in a way like:
getListItems ^ model perform: getListItemsSelector
now will be replaced with clean:
getListItems ^ model getListItems
So, a pluggable morph no longer cares about keeping all those selectors and maintain an obscure logic whether selector is nil or not nil etc etc.. Let model handle it!
Then you may ask, what if i have a model, which doesn't implements such protocol (required for specific pluggable widget). The answer is simple: - create a pluggable model class for given widget, which wraps around your object and can keeps all those messy 'getLabelSelector' 'getMenuSelector' , inside and mediates between your model and widget.
The benefit of it that widgets code will be much more cleaner. And another benefit is that you know what exact protocol is needed if you want to use your object as a model to given widget (list, button etc). And in most of the cases, you don't need to use this messy 'getXYZSelector', because you can simply answer to messages sent by widget and provide necessary data. But if you wanna go messy, you just use wrapper model, which contains all those 'getXYZSelectors' which you can set to whatever you like.
-- Best regards, Igor Stasenko AKA sig.