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. |
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. > |
On 7 April 2011 16:04, Esteban Lorenzano <[hidden email]> wrote:
> 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. Yes, but that is another step , another milestone. If you do it gradually, first you clean the mess out, and then introduce announcements, otherwise you have good chances to not deliver, because for announcements you need to use different api and rewrite things everywhere. While model adaptors (i like the naming btw) could be introduced seamlessly without breaking existing code that much. -- Best regards, Igor Stasenko AKA sig. |
In reply to this post by Igor Stasenko
Hi Igor,
I'm not sure to understand. As an example: TrafficLight is my model with an action for the red light. If I build a view for it, I can use a Button with #performAction: Button>> performAction ^ model performAction So, my TrafficLight must implement a #performAction method. Now, if I want also an action for the green light performed when another button is pressed. It means that I've to implement a specific model class for the red and another for the green ? cheers Alain for example, I have a simple UI with two buttons Le 07/04/2011 15:26, Igor Stasenko a écrit : > 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. > |
On 7 April 2011 18:59, Alain Plantec <[hidden email]> wrote:
> Hi Igor, > I'm not sure to understand. > As an example: > TrafficLight is my model with an action for the red light. > If I build a view for it, I can use a Button with #performAction: > > Button>> performAction > ^ model performAction > > So, my TrafficLight must implement a #performAction method. > Now, if I want also an action for the green light performed when another > button is pressed. > It means that I've to implement a specific model class for the red and > another for the green ? For that case you use model adapters. So, then you do something like: button1 model: (self for: #performAction adapt: #turnRed) button2 model: (self for: #performAction adapt: #turnGreen) and so, a receiver (which is a model), will receive #turnRed, or #turnGreen messages, while button will still use same message for model - #performAction > cheers > Alain > > > for example, I have a simple UI with two buttons > > Le 07/04/2011 15:26, Igor Stasenko a écrit : >> >> 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. |
On 7 April 2011 19:23, Igor Stasenko <[hidden email]> wrote:
> On 7 April 2011 18:59, Alain Plantec <[hidden email]> wrote: >> Hi Igor, >> I'm not sure to understand. >> As an example: >> TrafficLight is my model with an action for the red light. >> If I build a view for it, I can use a Button with #performAction: >> >> Button>> performAction >> ^ model performAction >> >> So, my TrafficLight must implement a #performAction method. >> Now, if I want also an action for the green light performed when another >> button is pressed. >> It means that I've to implement a specific model class for the red and >> another for the green ? > > For that case you use model adapters. > So, then you do something like: > > button1 model: (self for: #performAction adapt: #turnRed) > button2 model: (self for: #performAction adapt: #turnGreen) > > and so, a receiver (which is a model), will receive #turnRed, or > #turnGreen messages, > while button will still use same message for model - #performAction > or alternatively, we could ask a widget to create and install an instance of model adaptor (so it will construct a properly initialized model adaptor, when you only need to fill the data in it): (button1 installModelAdaptorFor: self) actionSelector: #turnRed. (button2 installModelAdaptorFor: self) actionSelector: #turnGreen. >> cheers >> Alain >> >> >> for example, I have a simple UI with two buttons >> >> Le 07/04/2011 15:26, Igor Stasenko a écrit : >>> >>> 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. > -- Best regards, Igor Stasenko AKA sig. |
In reply to this post by Igor Stasenko
Le 07/04/2011 19:23, Igor Stasenko a écrit :
> button1 model: (self for: #performAction adapt: #turnRed) > button2 model: (self for: #performAction adapt: #turnGreen) yes, cool! It seems to be a good way to clean morphic cheers Alain |
2011/4/7 Alain Plantec <[hidden email]>:
> Le 07/04/2011 19:23, Igor Stasenko a écrit : >> >> button1 model: (self for: #performAction adapt: #turnRed) >> button2 model: (self for: #performAction adapt: #turnGreen) > > yes, cool! > It seems to be a good way to clean morphic > cheers > Alain > > Yes, it's delegating the job to specialized objects. Specialized objects shall know how to. That should prevent Morphs to be omniscient. My favourite name for the opposite pattern is "god programming": we all encountered the thousand lines functions with tons of imbricated if/switch logic in C or C// world, didn't we ? Somehow, Igor proposal reminds me the VW UI architecture were they pushed the wrapper pattern very far. Personnally, I found myself comfortable with it, but it had its detractors too. It would be interesting to analyze their arguments. cheers. Nicolas |
Why not do it like in Cocoa?
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaDesignPatterns/CocoaDesignPatterns.html%23//apple_ref/doc/uid/TP40002974-CH6-SW16
On Thu, Apr 7, 2011 at 9:26 PM, Nicolas Cellier <[hidden email]> wrote: 2011/4/7 Alain Plantec <[hidden email]>: |
Free forum by Nabble | Edit this page |