tag:robotlegs.tenderapp.com,2009-10-18:/discussions/robotlegs-2/13189-keeping-things-dryRobotlegs: Discussion 2018-10-18T16:35:58Ztag:robotlegs.tenderapp.com,2009-10-18:Comment/378887032015-09-09T23:15:38Z2015-09-09T23:15:38ZKeeping things DRY<div><p>Two questions about adhering to DRY as much as possible.</p>
<p>1) Add/Edit Views: When creating/editing something object in the
application, the view is basically the same. If we are editing
data, the view must be populated with data from the context (likely
via an event), but if we are adding a new object, we want the form
to be blank. How can we open the view and ask for stored data if we
are in EDIT mode, but not do it in ADD mode? Right now I have given
my view a public mode property, and adding event listeners in the
mediator based on the mode but this seems like its violating the
idea of a mediator.</p>
<p>2) I'm finding that a number of models I have set up have
one-to-one representations with value objects that are passed
around the application. This gave off a bit of a code smell. Here
is an example that can perhaps explain the problem better. A lot of
these models are store settings or preferences, not lists of
data.</p>
<p>Let's say one function of the app is to create a report (e.g. a
text report or pdf report) of some pieces of data tracked by the
model tier. We want to be able to create the report in multiple
file formats. The app can be configured to format the data printed
on the reports. So if we wanted to print a list of customers, we
want to have the option to print their name in "Last, First"
format, or "First Last" format etc. These settings apply to ALL
reports, so I feel like we want to keep these in a model.
Implementing this in RL/MVC, this is what I have.</p>
<ol>
<li>ReportSettingsModel -- persist the settings to be applied to
reports<br></li>
<li>ReportSettingsVO -- shuttle configuration from the view to the
model<br></li>
<li>ReportSettingsView -- offer UI for to configure
reports<br></li>
<li>XXXReportFileService -- writes data to a report file (XXX could
be PDF or TXT)</li>
</ol>
<p>The problem: ReportSettingsVO and ReportSettingsModel are almost
identical. This isn't DRY! It feels wrong. Is it? Should settings
be tracked in value objects and injected into the models that need
them? Could we create these VOs on the fly in commands, and the use
injector.mapValue to apply it to all injection points?</p></div>dkartentag:robotlegs.tenderapp.com,2009-10-18:Comment/378887032015-09-10T12:36:25Z2015-09-10T12:36:25ZKeeping things DRY<div><p>I don't know how you set the mode (state) of your view, when you
add the view to the stage through view's API or on user
interactions from within the view?</p>
<blockquote>
<p>Right now I have given my view a public mode property, and
adding event listeners in the mediator based on the mode but this
seems like its violating the idea of a mediator.</p>
</blockquote>
<p>Does this mean that you are asking the view about its state in
mediator's initialize method?<br>
Like, if( view.mode=="editing" ) addContextListener for some
event...?</p>
<ul>
<li>If you don't like this approach, you can let the view dispatch
an event when its state changes, and the mediator can remove/add
event listeners like this:</li>
</ul>
<pre>
<code>private function onViewStateChanged(event:ViewStateEvent):void
{
removeContextListener(SomeContextEvent.SOME_TYPE, onSomeType, SomeContextEvent);
if(event.message=="editing")
addContextListener(SomeContextEvent.SOME_TYPE, onSomeType, SomeContextEvent);
}</code>
</pre>
<ul>
<li>
<p>or, the view can send a ViewStateEvent with 2 different types,
ADD_LISTENERS, REMOVE_LISTENERS and the mediator would add or
remove events in the corresponding handlers</p>
</li>
<li>
<p>another possibility is to let the view itself decide if it is
interested in the data coming from the mediator depending on its
state.</p>
</li>
<li>
<p>another option (for the case of adding views to the stage that
have a predefined role):</p>
</li>
</ul>
<p>Create interfaces for your view, say, IAddThings, IEditThings
and let the view implement both 'behaviours'. When you need an
editing view you add it to the stage as IEditThings, and as
IAddThings when you need it for other purposes...<br>
Create 2 mediators, one for the IAddThings and another for the
IEditThings and inject IAddThings into AddThingsMediator and
IEditThings into EditThingsMediator.<br>
Create 2 guards for the 2 mediators. The EditThingsMediator should
be created if the view's state is "editing" (the view can be
injected into the guard, which can interogate view's state).<br>
The AddThingsMediator should be created only if view's state is
"adding" or is not "editing" or whatever condition you need to
check.<br>
The mediators should add event listeners only for the events of
interest to them.<br>
This solution might be the cleanest, but it is a bit more
convoluted. Let me know if you need more clarification on this.</p></div>Ondina D.F.tag:robotlegs.tenderapp.com,2009-10-18:Comment/378887032015-09-10T15:42:07Z2015-09-10T15:42:07ZKeeping things DRY<div><p>For the use case you've presented I'd use a ReportSettingsModel
holding a property 'report' of type ReportSettingsVO, as shown in
the example below. The view is setting the values on a new
ReportSettingsVO and is dispatching an event with a payload of type
ReportSettingsVO.<br>
ReportSettingsCommand passes the event's payload to the model. When
the GenerateReportCommand is triggered, it reads the model's
properties and passes them onto the ReportFileService.<br>
So, ReportSettingsModel has a property report: ReportSettingsVO and
it could contain other properties as well, as VOs or other types,
but the 2 properties carried by the ReportSettingsVO are not part
of the ReportSettingsModel.<br>
Tell me why this wouldn't work for you?</p>
<pre>
<code>public class ReportSettingsVO
{
protected var _reportType:String;
protected var _rowsPerPage:Number;
public function ReportSettingsVO(reportType:String, rowsPerPage:Number)
{
_reportType = reportType;
_rowsPerPage = rowsPerPage;
}
public function get reportType():String
{
return _reportType;
}
public function get rowsPerPage():Number
{
return _rowsPerPage;
}
}</code>
</pre>
<p>ReportSettingsModel::</p>
<pre>
<code>public class ReportSettingsModel
{
private var _report:ReportSettingsVO;
//private var _someOtherVO:SomeOtherVO;
//private var anotherThing:String;
public function ReportSettingsModel()
{
}
public function get report():ReportSettingsVO
{
return _report;
}
public function set report(value:ReportSettingsVO):void
{
_report = value;
}
}</code>
</pre>
<p>ReportView - editing the report settings::</p>
<pre>
<code>var reportType:String = someComponent.value;//PDF/TXT
var report:ReportSettingsVO = new ReportSettingsVO(reportType , 5);
....
dispatchEvent(new ReportSettingsEvent(ReportSettingsEvent.REPORT_SETTINGS_CHANGED, report));</code>
</pre>
<p>ReportSettingsCommand::</p>
<pre>
<code>[Inject]
public var event: ReportSettingsEvent;
[Inject]
public var model:ReportSettingsModel;
public function execute():void
{
model.report = event.report;
}</code>
</pre>
<p>GenerateReportCommand, needing the report settings::</p>
<pre>
<code>[Inject]
public var model:ReportSettingsModel;
[Inject]
public var service: ReportFileService;
public function execute():void
{
service.doYourThing(model.report.reportType);
//or just
service.doYourThing(model.report);
}</code>
</pre>
<blockquote>
<p>Should settings be tracked in value objects and injected into
the models that need them?</p>
</blockquote>
<p>Are you referring to the same scenario as the one above? If the
answer is yes, then I'd say no, you shouldn't inject the vos into
the models.</p>
<p>If you're setting the VO's values once at the app start and
you're not going to edit them later, then you could inject them
wherever you needed them.</p>
<blockquote>
<p>Could we create these VOs on the fly in commands, and the use
injector.mapValue to apply it to all injection points?</p>
</blockquote>
<p>Yes, you can create classes on the fly like this:</p>
<pre>
<code>var someVO:SomeVO = injector.getOrCreateNewInstance(SomeVO);
if (!injector.hasMapping(SomeVO))
injector.map(SomeVO).toValue(someVO);
someVO.someProperty = "new value";</code>
</pre>
<p>but you have to decide whether it makes sense to use vos like
this.<br>
You should know by now that injecting everything everywhere comes
at a price!</p></div>Ondina D.F.tag:robotlegs.tenderapp.com,2009-10-18:Comment/378887032015-09-11T19:10:40Z2015-09-11T19:10:40ZKeeping things DRY<div><p>Once I had written out my question, the answer became more
obvious. Thanks for confirming with your wisdom!</p></div>dkarten