Chapter 4. Implementing the User Interface
The classes making up your user interface should encapsulate their own behavior and appearance. View Components may require data or the occasional method invocation from the outside in order to have something to display or to know they need to change to another visual state. But they should be capable of making those visual transitions themselves once given the input and impetus. When they have something to communicate to the rest of the application, they should do this solely by dispatching events or setting properties and making method invocations on their child components. In the context of a PureMVC application, a View Component should never know anything about the PureMVC framework classes or their subclasses.
One reason it is a good idea to build the View Components after
building the Value Objects, and before getting deeply into the PureMVC
apparatus, is that you do not yet have the ability to access those Mediator
, Proxy
, and Command
classes since they have not yet been
created. Since we have already created our Value Objects and Enums, we can
populate our View Components with dummy VOs as we build them without having
to have all the rest of the system in place to feed them to us. In fact, you
should be able to farm out the development of the View Components to a
separate company, team, or team member that knows nothing at all about
PureMVC and still be successful. If the team is at another company, you
might want to repackage your Model classes into a separate library for their
use while they build the View Components.
The main thing to remember in your implementation of the View
Components is that they should expose an API of events, properties, and
methods for interacting with them. This API should hide the internal
implementation of the components. For example, it should not be expected
that the caller will reach into the component and set the dataProvider
property of a DataGrid
instance declared within. Instead a
bindable, public property named something like displayItems
should be exposed. The DataGrid
instance’s dataProvider
property should then be bound to the
displayItems
property. This means that
the Mediator
subclass that will tend the
component does not know or care how the component is implemented, it just
knows that there is a displayItems
property to be set for displaying the data. The View Component could later
be refactored to use a drop-down list with a different ID and the Mediator
would be completely unaffected by the
change.
Also, as with any class in an OOP application, try not to heap too many responsibilities onto any given View Component. Once you see a great many nested structures declared in one place and lots of associated logic and variables, you should consider creating custom components to replace large chunks of MXML. While this can be argued to create complexity (in the form of more classes), it actually simplifies the View Components, clarifies their roles, and makes them more reusable as you will see in this chapter.
A Tale of Two Views
After much work at the whiteboard considering how our goals for the Story Architect application should be translated into a user interface, it was determined that there will be two primary views to the application: a Chooser and an Editor. When you open up the application, you have a number of options available with regard to creating and managing your Stories and Series. You could choose a Story and export it as a file, delete it, change its name, etc. Or you could choose to write, and that leads you to the Editor, where you will be able to do just that. The Editor will provide you with the functionality for attaching descriptions and notes to any part of the Story, as well as a timeline for navigating and extending the Story.
While the functionality for dealing with a Series is a big part of the application, it is far less important to the primary goal of writing a Story, as is management of Cast and Milieu. This book will focus discussion on the primary Story-related use cases and user interface elements. Remember, our major goal for the first iteration is to create and persist a Story, edit and extend its structure, name any part of the structure, and add descriptions and notes to it.
Now we will examine some of the more important pieces of the user
interface. In addition to the main application and components, we will
define one bubbling event called AppEvent
that can carry data, and a class
similar to a Value Object called SelectionContext
for sharing information about
the current selection among the View Components.
With regard to collaborations, notice that all of these View
Components basically know only their direct child components, the AppEvent
class, the SelectionContext
class, Enum
and its subclasses, and ValueObject
and its subclasses. There are no
PureMVC Facade
, Mediator
, Command
, Proxy
, or Notification
classes referenced anywhere within
the View Components (with the exception of the main application itself,
which must know the Facade
to bootstrap
the startup process). View Components expose properties to receive data,
send events to communicate user intentions, observe and conform to the
shared selection context, and interact only with their direct children and
the data objects they are fed (either from a parent component or from a
Mediator
).
In the first view, upon opening the application, you can add or choose a Story or Series and access various functionalities associated with your selection. This is called the Chooser View and is shown in Figure 4-1. Functions like creating and managing the Story will open pop-ups to collect and apply the user input.
When you have created or chosen a Story from the list, then you are taken to the Editor View (see Figure 4-2), where you can immediately begin writing the current draft of the last scene in your Story, however the scenes happen to be grouped. You can also reveal the timeline and details components for navigating the Story and extending and annotating.
The Application
Class
StoryArchitect.mxml
Responsibilities
Declare and layout the
Chooser
and Editor componentsInitialize the PureMVC Facade
Trigger PureMVC startup process, passing a reference to the app for mediation
Define three display modes for Starting, Chooser, and Editor
Expose a bindable public property for setting the word count
Expose a bindable public property for setting the display mode
Expose a bindable public property for setting the
SelectionContext
Provide the
SelectionContext
object to children that require itControl visibility and layout inclusion of subcomponents based on display mode
Display mode in the status bar (and additionally word count when in Editor mode)
Listen to the Editor component for
ReportWordCount
AppEvents
, updating thewordCount
property when they occur
Collaborations
At runtime, Flash builds the main application (StoryArchitect
) first, initializing its
display list and variables. This makes it the perfect place to kick
off the initialization of the PureMVC apparatus. Thus, StoryArchitect
knows the ApplicationFacade
, for the purpose of
initializing it and triggering the startup process. The main
application is the exception to the rule about View Components not
knowing the PureMVC Facade. Interaction with the Facade by the
application should be limited to fulfilling these two
responsibilities. No other View Component ever has a reason to know
about the Facade.
StoryArchitect
also knows the
SelectionContext
class, a construct
created specifically for this application that allows components to be
informed about the current selection. When the SelectionContext
is set on StoryArchitect
, it is passed to its children
via Flex binding. You will note that many of the View Components in
our UI have this bindable property and pass it on to their children.
It allows us to, for example, have the Timeline
component automatically open up to
the current Draft of the last Scene in the last Chapter of the last
part of a Story. We will describe this class in more detail
shortly.
StoryArchitect
knows and
controls its direct children, the Chooser
and Editor
components.
StoryArchitect
is known by
the ApplicationFacade
, which
exposes a convenience method for passing the application to the
StartupCommand
, which references it
briefly to register the ApplicationMediator
, who will mediate
communications between StoryArchitect
and the rest of the
system.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- STORY ARCHITECT APPLICATION --> <s:WindowedApplication xmlns:editor="com.futurescale.sa.view.component.editor.*" xmlns:chooser="com.futurescale.sa.view.component.chooser.*" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:fx="http://ns.adobe.com/mxml/2009" applicationComplete="facade.startup(this); // startup app" minWidth="800" minHeight="600"> <fx:Script> <![CDATA[ import com.futurescale.sa.ApplicationFacade; import com.futurescale.sa.view.context.SelectionContext; public static const MODE_STARTING:String = "Starting..."; public static const MODE_CHOOSER:String = "Chooser"; public static const MODE_EDITOR:String = "Editor"; // Selection context shared between View Components. [Bindable] public var context:SelectionContext; // Word Count (displayed on status bar in Editor mode) [Bindable] public var wordCount:String = ""; // Display Mode (Chooser/Editor) [Bindable] private var mode:String = MODE_STARTING; // Initialize the PureMVC Facade private var facade:ApplicationFacade = ApplicationFacade.getInstance(); /** * Set the application display mode, and if showing * the Editor, initialize the word count from the * selected Story. */ public function setMode( mode:String ):void { this.mode=mode; if (mode == MODE_EDITOR && context.story) { wordCount = context.story.wordCount.toString(); } else { wordCount = ""; } } </fx:Script> <!-- LAYOUT --> <s:layout> <s:VerticalLayout horizontalAlign="center"/> </s:layout> <!-- STATUS BAR --> <s:status>{mode}{(wordCount != "")?" | "+wordCount+" words":""}</s:status> <!-- CHOOSER --> <chooser:Chooser id="chooser" width="100%" height="100%" includeInLayout="{mode == MODE_CHOOSER}" visible="{mode == MODE_CHOOSER}"/> <!-- EDITOR --> <editor:Editor id="editor" width="100%" height="100%" includeInLayout="{mode == MODE_EDITOR}" visible="{mode == MODE_EDITOR}" reportWordCount="wordCount=String(event.data)" context="{context}"/> </s:WindowedApplication>
The Chooser
Class
Chooser.mxml
Responsibilities
Declare and layout the
StoryChooser
andSeriesChooser
componentsExpose a bindable public property for setting the list of
StoryVO
sExpose a bindable public property for setting the list of
SeriesVO
sProvide the Story and Series lists to children
Collaborations
The Chooser
only knows and
controls its direct children, the StoryChooser
and SeriesChooser
components. Note that the
Chooser
does not require access to the SelectionContext
object, as its children
only display simple lists and dispatch events to be processed when a
selection is made and a button pressed.
The Chooser
is known by its
parent, the StoryArchitect
application, and the StartupCommand
, which references it briefly
to register the ChooserMediator
.
The ChooserMediator
who knows and mediates
communications between the rest of the system and the Chooser
(and indirectly its StoryChooser
and SeriesChooser
child components). The Story
and Series lists that populate the children will be set on the
Chooser
by the ChooserMediator
. The bubbling events that are dispatched from the
children will be handled by the ChooserMediator
who will set listeners on
the Chooser
.
Code
<?xml version="1.0" encoding="utf-8"?> <s:HGroup xmlns:s="library://ns.adobe.com/flex/spark" xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:chooser="com.futurescale.sa.view.component.chooser.*" verticalAlign="middle" horizontalAlign="center" paddingLeft="5" paddingRight="5" width="100%" paddingBottom="5" paddingTop="5" height="100%" > <fx:Script> <![CDATA[ import mx.collections.ArrayCollection; // The list of stories [Bindable] public var storyList:ArrayCollection; // The list of series [Bindable] public var seriesList:ArrayCollection; </fx:Script> <!-- STORY CHOOSER --> <chooser:StoryChooser id="storyChooser" storyList="{storyList}" width="50%" height="100%"/> <!-- SERIES CHOOSER --> <chooser:SeriesChooser id="seriesChooser" seriesList="{seriesList}" width="50%" height="100%"/> </s:HGroup>
The Story Chooser
Class
StoryChooser.mxml
Responsibilities
Declare a list for displaying the
StoryVO
s for selectionDeclare and layout the various buttons for acting on a selection
Control visibility and layout inclusion for buttons based on selection
Declare a prominent “Add” (+) button for adding a new Story
Dispatch appropriate events when buttons are pressed
Collaborations
The StoryChooser
knows and
controls its direct children: Flex Button
, Label
, and List
components. It also knows the AppEvent
class, which it constructs and
dispatches in response to certain button presses.
The StoryChooser
is known
only by its parent, the Chooser
component.
Code
<?xml version="1.0" encoding="utf-8"?> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" minWidth="390" minHeight="290"> <fx:Script> <![CDATA[ import com.futurescale.sa.view.event.AppEvent; import mx.collections.ArrayCollection; // The list of StoryVOs [Bindable] public var storyList:ArrayCollection; // Create and dispatch an AppEvent private function sendEvent( type:String, data:Object=null ):void { dispatchEvent( new AppEvent(type,data ) ); } </fx:Script> <!-- CHOOSER HEADER --> <s:HGroup fontSize="24" verticalAlign="middle" width="100%" height="50"> <!-- TITLE --> <s:Label text="Story" width="100%"/> <!-- ADD BUTTON--> <s:Button label="+" width="50" height="100%" click="sendEvent( AppEvent.ADD_STORY )" /> </s:HGroup> <!-- STORY LIST--> <s:List labelField="name" id="lstStories" dataProvider="{storyList}" fontSize="16" width="100%" height="100%"/> <!-- CONTROLS --> <s:HGroup fontSize="16" verticalAlign="middle" visible="{lstStories.selectedItem != null}" includeInLayout="{lstStories.selectedItem != null}" width="100%" height="100"> <!-- CAST / MILIEU --> <s:VGroup height="100%" width="25%"> <s:Button label="Cast" height="50%" width="100%"/> <s:Button label="Milieu" height="50%" width="100%"/> </s:VGroup> <!-- NOTES / OUTLINE --> <s:VGroup height="100%" width="25%"> <s:Button label="Notes" height="50%" width="100%"/> <s:Button label="Outline" height="50%" width="100%"/> </s:VGroup> <!-- MANAGE / EXPORT --> <s:VGroup height="100%" width="25%"> <!-- MANAGE --> <s:Button label="Manage" height="50%" width="100%" click="sendEvent( AppEvent.MANAGE_STORY, lstStories.selectedItem )" /> <!-- EXPORT--> <s:Button label="Export" height="50%" width="100%"/> </s:VGroup> <!-- WRITE --> <s:Button label="Write" fontSize="24" click="sendEvent( AppEvent.EDIT_STORY, lstStories.selectedItem )" height="100%" width="25%"/> </s:HGroup> </s:VGroup>
The Editor
Class
Editor.mxml
Responsibilities
Declare and layout a
TextArea
for writing or displaying read-only, aggregated textDeclare and layout the
Controls
subcomponentExpose a bindable public property for setting the
SelectionContext
Provide the
SelectionContext
object to children that require itProvide a public function for setting focus to the text editor
Update the text of the selected
DraftVO
and when text is editedDispatch an event reporting the current word count when text is edited
Control the
TextArea
’s font size and percentage width with values from theControls
componentDeclare Flex metadata indicating that the component will dispatch
ReportWordCount
AppEvents
Listen to the
Control
instance forSelectScene
andSelectDraft
AppEvent
s and set focus to theTextArea
when they occur (theAppEvent
s will still bubble and be handled by aMediator
)
Collaborations
The Editor
knows and controls
its direct children: the Flex TextArea
and Controls
custom component. It also knows the
AppEvent
class, which it constructs
and dispatches in response to text editing. It also knows the SelectionContext
class, which it references
in methods and binding expressions.
The Editor
is known by its
parent the StoryArchitect
application and by the StartupCommand
who references it briefly in
order to register the EditorMediator
. The
EditorMediator
will mediate communications between
the Editor
and the rest of the
system.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- EDITOR --> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:editor="com.futurescale.sa.view.component.editor.*" paddingBottom="5" paddingTop="5" horizontalAlign="center" paddingLeft="5" paddingRight="5" minWidth="400" minHeight="300" > <fx:Metadata> [Event(name="reportWordCount", type="com.futurescale.sa.view.event.AppEvent")] </fx:Metadata> <fx:Script> <![CDATA[ import com.futurescale.sa.view.context.SelectionContext; import com.futurescale.sa.view.event.AppEvent; // Selection Context [Bindable] public var context:SelectionContext; // Called when the user types in the text editor private function textEdit():void { if (context.draft) { // Update draft with latest text from the editor context.draft.text = textEditor.text; // Dispatch an event reporting the current wordcount of the story. var event:AppEvent = new AppEvent( AppEvent.REPORT_WORDCOUNT, context.story.wordCount ); dispatchEvent( event ); } } </fx:Script> <!-- TEXT EDITOR --> <s:TextArea id="textEditor" borderVisible="{context.draft != null}" percentWidth="{100 - controls.editMarginPct}" height="100%" visible="true" editable="{context.draft != null}" text="{context.selectedText}" change="textEdit()" fontSize="{controls.editFontSize}" fontFamily="serif" paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10"/> <!-- CONTROLS--> <editor:Controls id="controls" width="100%" selectDraft="textEditor.setFocus()" selectScene="textEditor.setFocus()" context="{context}"/> </s:VGroup>
The Editor Controls
Class
Controls.mxml
Responsibilities
Base on
Panel
with vertical layout showing the name of the selectedStoryVO
for the titleIn the control bar, declare buttons for discarding or saving edits
In the control bar, declare buttons for revealing the
Timeline
andDetails
componentsIn the control bar, declare sliders for controlling font size and margins for the text editor
Declare the
Timeline
andDetails
components and control their visibility and layout inclusion based on the buttonsWhen the “Timeline” and “Details” buttons are not selected, only the
Panel
header and control bar should be visibleExpose bindable public properties for the font size and margin percentage
Expose a bindable public property for setting the
SelectionContext
Provide the
SelectionContext
object to children that require itDispatch the appropriate
AppEvent
when the “Discard” or “Save” buttons are pressedDeclare Flex metadata indicating that the component will dispatch
SelectScene
andSelectDraft
AppEvents
Collaborations
The Controls
component knows
and controls its direct children: the Timeline
and Details
custom components as well as Flex
Panel
, Button
, Label
, and HSlider
components declared in its control
bar area. It also knows the AppEvent
class, which it constructs and
dispatches in response to text editing. It also knows the SelectionContext
class, which it references
in methods and binding expressions.
The Controls
component is
known only by its parent, the Editor
component.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- CONTROLS --> <s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:timeline="com.futurescale.sa.view.component.timeline.*" xmlns:details="com.futurescale.sa.view.component.details.*" title="{(context.series) ? context.series.name : context.story.name }" width="100%" minHeight="0"> <fx:Metadata> [Event(name="selectScene", type="com.futurescale.sa.view.event.AppEvent")] [Event(name="selectDraft", type="com.futurescale.sa.view.event.AppEvent")] </fx:Metadata> <fx:Script> <![CDATA[ import com.futurescale.sa.view.context.SelectionContext; import com.futurescale.sa.view.event.AppEvent; [Bindable] public var editFontSize:Number=16; [Bindable] public var editMarginPct:Number=0; // Selection Context [Bindable] public var context:SelectionContext; private function discardChanges():void { var event:AppEvent; if ( context.story ) { event = new AppEvent( AppEvent.DISCARD_STORY, context.story ); } else if ( context.series ) { event = new AppEvent( AppEvent.DISCARD_SERIES, context.series ); } dispatchEvent( event ); } private function saveChanges():void { var event:AppEvent; if ( context.story ) { event = new AppEvent( AppEvent.SAVE_STORY, context.story ); } else if ( context.series ) { event = new AppEvent( AppEvent.SAVE_SERIES, context.series ); } dispatchEvent( event ); } </fx:Script> <!-- DETAILS --> <details:Details id="details" context="{context}" width="100%" height="100%" visible="{detailsButton.selected}" includeInLayout="{detailsButton.selected}"/> <!-- TIMELINE --> <timeline:Timeline id="timeline" context="{context}" story="{context.story}" width="100%" height="100%" visible="{timelineButton.selected}" includeInLayout="{timelineButton.selected}"/> <!-- CONTROL BAR --> <s:controlBarContent> <!-- TOGGLE TIMELINE --> <s:ToggleButton id="timelineButton" label="Timeline"/> <!-- TOGGLE DETAILS --> <s:ToggleButton id="detailsButton" label="Details"/> <!-- SPACER --> <s:Spacer width="100%"/> <!-- FONT SIZE --> <s:Label text="Font Size"/> <s:HSlider id="fontSlider" change="editFontSize=fontSlider.value" minimum="12" maximum="48" value="{editFontSize}"/> <!-- MARGIN SIZE --> <s:Label text="Margins"/> <s:HSlider id="marginSlider" change="editMarginPct=marginSlider.value" minimum="0" maximum="75" value="{editMarginPct}"/> <!-- SPACER --> <s:Spacer width="100%"/> <!-- DISCARD OR SAVE CHANGES --> <s:Button label="Discard" click="discardChanges()"/> <s:Button label="Save" click="saveChanges()"/> </s:controlBarContent> <!-- PANEL LAYOUT --> <s:layout> <s:VerticalLayout gap="0"/> </s:layout> <!-- CONTROL BAR LAYOUT --> <s:controlBarLayout> <s:HorizontalLayout verticalAlign="middle" paddingLeft="5" paddingRight="5" paddingBottom="5" paddingTop="5"/> </s:controlBarLayout> </s:Panel>
The Details Component
Class
Details.mxml
Responsibilities
Declare the
ItemInfo
andNotes
componentsExpose a bindable public property for setting the
SelectionContext
Provide the
SelectionContext
object to children that require it
Collaborations
The Details
component knows
and controls its direct children, the ItemInfo
and Notes
custom components. It also knows the
SelectionContext
class, which it
references in methods and binding expressions.
The Details
component is
known only by its parent, the Controls
component. In a future iteration,
it will also be known by the Chooser
component, which will allow
modification of item info and the ability to add notes to the
top-level VOs when selected without having to enter the Editor
to do so.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- DETAILS --> <s:HGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:details="com.futurescale.sa.view.component.details.*" width="100%" height="100%"> <fx:Script> <![CDATA[ import com.futurescale.sa.view.context.SelectionContext; //Selection context [Bindable] public var context:SelectionContext; </fx:Script> <!-- ITEM INFO --> <details:ItemInfo context="{context}" width="50%" height="100%" /> <!-- NOTES --> <details:Notes context="{context}" width="50%" height="100%" /> </s:HGroup>
The Item Info Component
Class
ItemInfo.mxml
Responsibilities
Declare a
TextInput
andTextArea
for thename
anddescription
of the selected item (aValueObject
)Update the selected item’s
name
anddescription
in response to user editsDeclare buttons for deleting and reordering the selected in the list of its siblings (functionality deferred to a later iteration)
Only show the “Move” and “Delete” buttons when the selected item is not a top-level VO (
CastVO
,MilieuVO
,StoryVO
, orSeriesVO
)Expose a bindable public property for setting the
SelectionContext
Collaborations
The ItemInfo
component knows
and controls its direct children, the Flex Button
, TextInput
, and TextArea
components. It also knows the
SelectionContext
class, which it
references in methods and binding expressions. And it knows the
top-level VOs (CastVO
, MilieuVO
, StoryVO
, or SeriesVO
), which it must hide the “Move” and
“Delete” buttons for.
The ItemInfo
component is
known only by its parent, the Details
component.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- ITEM INFO --> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:details="com.futurescale.sa.view.component.details.*" width="100%" height="100%" paddingTop="5" paddingBottom="5" paddingLeft="5" paddingRight="5"> <fx:Script> <![CDATA[ import com.futurescale.sa.model.vo.CastVO; import com.futurescale.sa.model.vo.MilieuVO; import com.futurescale.sa.model.vo.SeriesVO; import com.futurescale.sa.model.vo.StoryVO; import com.futurescale.sa.model.vo.ValueObject; import com.futurescale.sa.view.context.SelectionContext; //Selection context [Bindable] public var context:SelectionContext; // Update the selected item's name when edited private function nameEdit():void { if (context.selectedItem) context.selectedItem.name = itemName.text; } // Update the selected item's description when edited private function descEdit():void { if (context.selectedItem) context.selectedItem.description = itemDesc.text; } // Show or hide the move and delete buttons based the selection private function showButtons( vo:ValueObject ):Boolean { return (!(vo is StoryVO) && !(vo is SeriesVO) && !(vo is CastVO) && !(vo is MilieuVO) ); } </fx:Script> <s:HGroup width="100%"> <!-- ITEM NAME --> <s:TextInput id="itemName" width="100%" height="30" prompt="Name" change="nameEdit()" text="{context.selectedItem.name}"/> <!-- MOVE ITEM LEFT --> <s:Button label="< Move" fontSize="16" height="100%" visible="{showButtons( context.selectedItem )}" includeInLayout="{showButtons( context.selectedItem )}"/> <!-- MOVE ITEM RIGHT --> <s:Button label="Move >" fontSize="16" height="100%" visible="{showButtons( context.selectedItem )}" includeInLayout="{showButtons( context.selectedItem )}"/> <!-- DELETE ITEM --> <s:Button label="Delete" fontSize="16" height="100%" visible="{showButtons( context.selectedItem )}" includeInLayout="{showButtons( context.selectedItem )}"/> </s:HGroup> <!-- ITEM DESCRIPTION --> <s:TextArea id="itemDesc" width="100%" height="100%" change="descEdit()" prompt="Description" text="{context.selectedItem.description}"/> </s:VGroup>
The Notes Component
Class
Notes.mxml
Responsibilities
Declare a Flex
Label
andList
for displaying the selected item’s Note listDeclare Flex
Buttons
for adding and removing NotesDeclare a Flex
TextArea
andTextInput
for editing the selected NoteDeclare a Flex
Button
for launching a browser to view the selected Note’s URLProvide a label function to supply names for the Notes in the
List
, since Notes do not have a name fieldPopulate the form fields upon selection of a Note in the list
Update the selected Note’s
text
andurl
in response to user editsDispatch appropriate events when the “Add Note” (+) button is pressed (remove function deferred to a later iteration)
Expose a bindable public property for setting the
SelectionContext
Wrap the selected item’s
Vector
ofNoteVO
s in anArrayCollection
for theList
dataProvider
Collaborations
The Notes
component knows and
controls its direct children, the various Flex Button
, TextInput
, List
, and TextArea
components. It also knows the
SelectionContext
class, which it
references in methods and binding expressions. And it knows the
NoteVO
, ValueObject
, and AppEvent
classes. It also dispatches
AppEvents for adding and selecting a Note.
The Notes
component is known
only by its parent, the Details
component.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- NOTES --> <s:HGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:details="com.futurescale.sa.view.component.details.*" width="100%" height="100%"> <fx:Script> <![CDATA[ import com.futurescale.sa.model.vo.NoteVO; import com.futurescale.sa.model.vo.ValueObject; import com.futurescale.sa.view.context.SelectionContext; import com.futurescale.sa.view.event.AppEvent; import mx.collections.ArrayCollection; // Selection context [Bindable] public var context:SelectionContext; // Wrap the vector in a collection for the list. public function wrapNotes( vo:ValueObject ):ArrayCollection { var notes:Array = new Array(); for each (var note:NoteVO in vo.notes) { notes.push(note); } return new ArrayCollection( notes ); } // Add a note to the selected item private function addNote():void { var event:AppEvent = new AppEvent( AppEvent.ADD_NOTE ); event.data = context.selectedItem; dispatchEvent( event ); } // Select a note from the selected item's note list private function selectNote():void { var event:AppEvent = new AppEvent( AppEvent.SELECT_NOTE ); event.data = noteList.selectedItem; dispatchEvent( event ); } // Provide a label for notes in the list private function noteLabelFunction(item:Object):String { var label:String = "Note "; var notes:Vector.<NoteVO> = context.selectedItem.notes; for ( var i:int = 0; i<notes.length; i++ ) { if ( notes[i].xml === NoteVO(item).xml ) { label += String(i+1); break; } } return label; } // Update selected note text when edited private function textEdit():void { if (context.note) context.note.text = noteText.text; } // Update selected note URL when edited private function urlEdit():void { if (context.note) context.note.url = noteURL.text; } // Open a browser to view the selected note URL private function openURL():void { var urlRequest:URLRequest = new URLRequest(context.note.url); navigateToURL(urlRequest); } </fx:Script> <!-- NOTES LIST MANAGEMENT --> <s:VGroup height="100%" paddingRight="0" paddingLeft="0" paddingBottom="5" paddingTop="5"> <!-- LABEL --> <s:HGroup height="30" width="100%" verticalAlign="bottom" > <s:Label text="Notes" fontSize="16"/> </s:HGroup> <!-- NOTE LIST --> <s:List id="noteList" width="100%" height="100%" dataProvider="{ wrapNotes( context.selectedItem ) }" change="selectNote()" labelFunction="noteLabelFunction"/> <!-- NOTE BUTTONS --> <s:HGroup width="100%"> <s:Button label="-" width="50%" enabled="{noteList.selectedItem != null}"/> <s:Button label="+" width="50%" click="addNote()"/> </s:HGroup> </s:VGroup> <!-- NOTE FORM --> <s:VGroup height="100%" width="100%" enabled="{context.note != null}" paddingRight="5" paddingLeft="5" paddingBottom="5" paddingTop="5"> <!-- NOTE TEXT --> <s:TextArea id="noteText" text="{context.note.text}" prompt="Note Text..." change="textEdit()" width="100%" height="100%"/> <!-- NOTE URL --> <s:HGroup width="100%" paddingRight="0" paddingLeft="0" paddingBottom="0" paddingTop="0"> <!-- URL INPUT --> <s:TextInput id="noteURL" width="100%" prompt="Note URL..." text="{context.note.url}" change="urlEdit()" /> <!-- GO BUTTON --> <s:Button label="Go" click="openURL()" enabled="{context.note.url.length != 0}" /> </s:HGroup> </s:VGroup>
The Timeline Component
Class
Timeline.mxml
Responsibilities
Base on Flex
Scroller
classDeclare Flex
HGroup
for containing the scrollable contentExpose a public property for setting the
StoryVO
to be displayedCreate a
StoryTile
and replace any existing children of theHGroup
with it when the StoryVO is setProvide a label function to supply names for the notes in the
List
, since Notes do not have a name fieldExpose a bindable public property for setting the
SelectionContext
Provide the displayed
StoryTile
with theSelectionContext
object
Collaborations
The Timeline
component knows
and controls its direct children, the Flex HGroup
and custom StoryTile
components. It also knows the
SelectionContext
class, which it
passes to the StoryTile
, and the
StoryVO
, which it uses to create a
StoryTile
.
The Timeline
component is
known only by its parent, the Controls
component.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- TIMELINE --> <s:Scroller xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"> <fx:Script> <![CDATA[ import com.futurescale.sa.model.vo.StoryVO; import com.futurescale.sa.view.context.SelectionContext; // Selection context [Bindable] public var context:SelectionContext; // The Story public function set story( storyVO:StoryVO ):void { _story = storyVO; storyGroup.removeAllElements(); if (storyVO) { var storyTile:StoryTile = new StoryTile(); storyTile.context = context; storyTile.story = story; storyGroup.addElement( storyTile ); } } public function get story( ):StoryVO { return _story; } private var _story:StoryVO; </fx:Script> <!-- STORY GROUP --> <s:HGroup id="storyGroup" width="100%" height="100%" paddingRight="5" paddingBottom="5" paddingLeft="5"/> </s:Scroller>
The Story Tile
Class
StoryTile.mxml
Responsibilities
Base on Flex
VGroup
classDeclare a Flex
HGroup
for containingPartTile
,ChapterTile
, orSceneTile
instancesDeclare a Flex
ToggleButton
for selecting theStoryVO
Expose a public property for setting the
StoryVO
to be displayedWhen
StoryVO
is selected, create appropriatePartTile
,ChapterTile
, orSceneTile
instances, replacing any existing children of theHGroup
When the appropriate tile components are added to the
HGroup
, add anAddTile
to the end, set to dispatch the appropriate event for adding another childRemoving and recreating all tiles when the
StoryVO
is selected or deselected will have the effect of expanding or collapsing those tilesExpose a bindable public property for setting the
SelectionContext
Provide the displayed
PartTile
,ChapterTile
, orSceneTile
instances with theSelectionContext
object
Collaborations
The StoryTile
component knows
and controls its direct children, the Flex HGroup
, and custom PartTile
, ChapterTile
, SceneTile
, and AddTile
components. It knows the SelectionContext
class, which it passes to
the tile components, and the StoryVO
, PartVO
, ChapterVO
, and SceneVO
, which it uses to create the
appropriate tile components depending on the Story type.
The StoryTile
component is
known only by its parent, the Timeline
component. In future iterations, it
will also be known by the SeasonTile
component when Series
functionality is added to the Timeline
component.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- STORY TILE --> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" height="100%" width="100%"> <fx:Script> <![CDATA[ import com.futurescale.sa.model.vo.ChapterVO; import com.futurescale.sa.model.vo.PartVO; import com.futurescale.sa.model.vo.SceneVO; import com.futurescale.sa.model.vo.StoryVO; import com.futurescale.sa.view.context.SelectionContext; import com.futurescale.sa.view.event.AppEvent; // Selection context [Bindable] public var context:SelectionContext; // The Story. [Bindable] public function set story( storyVO:StoryVO ):void { _story = storyVO; } public function get story():StoryVO { return _story; } private var _story:StoryVO; // Remove or add tiles according to the selected story private function changeStorySelection( selection:Boolean ):Boolean { if (!selection) { removeTiles(); } else { createTiles(); } return selection; } // Create Tiles private function createTiles():void { removeTiles(); var addTile:AddTile = new AddTile(); if ( story.useScenes ) { // Create the SceneTiles var scenes:Vector.<SceneVO> = story.scenes; for (var s:int=0; s< scenes.length; s++ ) { var sceneTile:SceneTile = new SceneTile(); sceneTile.context = context; sceneTile.scene = scenes[s]; tileGroup.addElement( sceneTile ); } // Create the 'Add Scene' tile addTile.addType = AppEvent.ADD_SCENE; addTile.addTarget = story; } else if ( story.useChapters ) { // Create the ChapterTiles var chapters:Vector.<ChapterVO> = story.chapters; for (var c:int=0; c<chapters.length; c++ ) { var chapterTile:ChapterTile = new ChapterTile(); chapterTile.context = context; chapterTile.chapter = chapters[c]; tileGroup.addElement( chapterTile ); } // Create the 'Add Chapter' tile addTile.addType = AppEvent.ADD_CHAPTER; addTile.addTarget = story; } else if ( story.useParts ) { // Create the PartTiles var parts:Vector.<PartVO> = story.parts; for (var p:int=0; p< parts.length; p++ ) { var partTile:PartTile = new PartTile(); partTile.context = context; partTile.part = parts[p]; tileGroup.addElement( partTile ); } // Create the 'Add Part' tile addTile.addType = AppEvent.ADD_PART; addTile.addTarget = story; } tileGroup.addElement( addTile ); tileGroup.percentHeight=100; } // Remove any existing tiles private function removeTiles():void { tileGroup.removeAllElements(); tileGroup.percentHeight=0; } // Select the story private function selectStory():void { var appEvent:AppEvent = new AppEvent(AppEvent.SELECT_STORY); appEvent.data = story; dispatchEvent(appEvent); } </fx:Script> <!-- TILE GROUP --> <s:HGroup id="tileGroup" width="100%"/> <!-- STORY BUTTON --> <s:ToggleButton id="storyButton" click="selectStory()" selected="{changeStorySelection(context.story.uid == story.uid)}" height="100%" width="100%" minWidth="150" minHeight="25" label="{story.name}"/> </s:VGroup>
The Part Tile
Class
PartTile.mxml
Responsibilities
Base on Flex
VGroup
classDeclare a Flex
HGroup
for containingChapterTile
instancesDeclare a Flex
ToggleButton
for selecting thePartVO
Expose a public property for setting the
PartVO
to be displayedWhen
PartVO
is selected createChapterTile
instances, replacing any existing children of theHGroup
When the appropriate tile components are added to the
HGroup
, add anAddTile
to the end, set to dispatch the appropriate event for adding another childRemoving or recreating all tiles when the
PartVO
is selected or deselected will have the effect of expanding or collapsing those tilesExpose a bindable public property for setting the
SelectionContext
Provide the displayed
ChapterTile
instances with theSelectionContext
object
Collaborations
The PartTile
component knows
and controls its direct children, the Flex HGroup
, and custom ChapterTile
and AddTile
components. It also knows the
SelectionContext
class, which it
passes to the tile components, and the PartVO
and ChapterVO
, which it uses to create the
appropriate tile components. Finally, it knows the AppEvent
class, which it uses to inform the
AddTile
of the appropriate event to
dispatch.
The PartTile
component is
known only by its parent, the StoryTile
component.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- PART TILE --> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" height="100%" width="100%"> <fx:Script> <![CDATA[ import com.futurescale.sa.model.vo.ChapterVO; import com.futurescale.sa.model.vo.PartVO; import com.futurescale.sa.view.context.SelectionContext; import com.futurescale.sa.view.event.AppEvent; // Selection context [Bindable] public var context:SelectionContext; // The Part [Bindable] public var part:PartVO; // Remove or add tiles according to the selected Part private function changePartSelection( selection:Boolean ):Boolean { if (!selection) { removeTiles(); } else { createTiles(); } return selection; } // Create Tiles private function createTiles():void { // Create the Chapter tiles removeTiles(); var chapters:Vector.<ChapterVO> = part.chapters; for (var i:int=0; i<chapters.length; i++ ) { var chapterTile:ChapterTile = new ChapterTile(); chapterTile.context = context; chapterTile.chapter = chapters[i]; chapterGroup.addElement( chapterTile ); } // Create the 'Add Chapter' tile var addTile:AddTile = new AddTile(); addTile.addType = AppEvent.ADD_CHAPTER; addTile.addTarget = part; chapterGroup.addElement( addTile ); chapterGroup.percentHeight=100; } // Remove Tiles private function removeTiles():void { chapterGroup.removeAllElements(); chapterGroup.percentHeight=0; } // Toggle the Part selection private function selectPart():void { var event:AppEvent; if (partButton.selected) { event = new AppEvent(AppEvent.SELECT_PART); } else { event = new AppEvent(AppEvent.DESELECT_PART); removeTiles(); } event.data = part; event.related = context.story; dispatchEvent(event); } </fx:Script> <!-- CHAPTER GROUP --> <s:HGroup id="chapterGroup" width="100%"/> <!-- PART BUTTON --> <s:ToggleButton id="partButton" click="selectPart()" selected="{changePartSelection(context.part.uid == part.uid)}" height="100%" width="100%" minWidth="150" minHeight="25" label="{part.name}"/> </s:VGroup>
The Chapter Tile
Class
ChapterTile.mxml
Responsibilities
Base on Flex
VGroup
classDeclare a Flex
HGroup
for containingSceneTile
instancesDeclare a Flex
ToggleButton
for selecting theChapterVO
Expose a public property for setting the
ChapterVO
to be displayedWhen
ChapterVO
is selected, createSceneTile
instances, replacing any existing children of theHGroup
When the appropriate tile components are added to the
HGroup
, add anAddTile
to the end, set to dispatch the appropriate event for adding another childRemoving or recreating all tiles when the
ChapterVO
is selected or deselected will have the effect of expanding or collapsing those tilesExpose a bindable public property for setting the
SelectionContext
Provide the displayed
SceneTile
instances with theSelectionContext
object
Collaborations
The ChapterTile
component
knows and controls its direct children, the Flex HGroup
, and custom SceneTile
and AddTile
components. It also knows the
SelectionContext
class, which it
passes to the tile components, and the SceneVO
and ChapterVO
, which it uses to create the
appropriate tile components. Finally, it knows the AppEvent
class, which it uses to inform the
AddTile
of the appropriate event to
dispatch.
The ChapterTile
component is
known only by its two possible parents, the StoryTile
and PartTile
components.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- CHAPTER TILE --> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" height="100%" width="100%"> <fx:Script> <![CDATA[ import com.futurescale.sa.model.vo.ChapterVO; import com.futurescale.sa.model.vo.SceneVO; import com.futurescale.sa.view.context.SelectionContext; import com.futurescale.sa.view.event.AppEvent; // Selection context [Bindable] public var context:SelectionContext; // The Chapter [Bindable] public var chapter:ChapterVO; // Remove or add tiles according to the selected Chapter private function changeChapterSelection( selection:Boolean ):Boolean { if (!selection) { removeTiles(); } else { createTiles(); } return selection; } // Create Tiles private function createTiles():void { // Create SceneTiles removeTiles(); var scenes:Vector.<SceneVO> = chapter.scenes; for (var i:int=0; i<scenes.length; i++ ) { var sceneTile:SceneTile = new SceneTile(); sceneTile.context = context; sceneTile.scene = scenes[i]; sceneGroup.addElement( sceneTile ); } // Create 'Add Scene' Tile var addTile:AddTile = new AddTile(); addTile.addType = AppEvent.ADD_SCENE; addTile.addTarget = chapter; addTile.addArgument = (context.part)?context.part:context.story; sceneGroup.addElement( addTile ); sceneGroup.percentHeight=100; } // Remove tiles private function removeTiles():void { sceneGroup.removeAllElements(); sceneGroup.percentHeight=0; } // Toggle the Chapter selection private function selectChapter():void { var event:AppEvent; if (chapterButton.selected) { event = new AppEvent(AppEvent.SELECT_CHAPTER); } else { event = new AppEvent(AppEvent.DESELECT_CHAPTER); removeTiles(); } event.data = chapter; event.related = (context.part)?context.part:context.story; dispatchEvent(event); } </fx:Script> <!-- SCENE GROUP --> <s:HGroup id="sceneGroup" width="100%"/> <!-- CHAPTER BUTTON --> <s:ToggleButton id="chapterButton" click="selectChapter()" selected="{changeChapterSelection(context.chapter.uid == chapter.uid)}" height="100%" width="100%" minWidth="150" minHeight="25" label="{chapter.name}"/> </s:VGroup>
The Scene Tile
Class
SceneTile.mxml
Responsibilities
Base on Flex
VGroup
classDeclare a Flex
HGroup
for containingDraftTile
instancesDeclare a Flex
ToggleButton
for selecting theSceneVO
Expose a public property for setting the
SceneVO
to be displayedWhen
SceneVO
is selected, createDraftTile
instances, replacing any existing children of theHGroup
When the appropriate tile components are added to the
HGroup
, add anAddTile
to the end, set to dispatch the appropriate event for adding another childRemoving or recreating all tiles when the
SceneVO
is selected or deselected will have the effect of expanding or collapsing those tilesExpose a bindable public property for setting the
SelectionContext
Provide the displayed
DraftTile
instances with theSelectionContext
object
Collaborations
The SceneTile
component knows
and controls its direct children, the Flex HGroup
, and custom DraftTile
and AddTile
components. It also knows the
SelectionContext
class, which it
passes to the tile components, and the SceneVO
and DraftVO
, which it uses to create the
appropriate tile components. Finally, it knows the AppEvent
class, which it uses to inform the
AddTile
of the appropriate event to
dispatch.
The SceneTile
component is
known only by its three possible parents: the StoryTile
, PartTile
, and ChaperTile
components.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- SCENE TILE --> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" height="100%" width="100%"> <fx:Script> <![CDATA[ import com.futurescale.sa.model.vo.DraftVO; import com.futurescale.sa.model.vo.SceneVO; import com.futurescale.sa.view.context.SelectionContext; import com.futurescale.sa.view.event.AppEvent; //Selection context [Bindable] public var context:SelectionContext; // The Scene [Bindable] public var scene:SceneVO; // Remove or add tiles according to the selected Scene private function changeSceneSelection( selection:Boolean ):Boolean { if (!selection) { removeTiles(); } else { createTiles(); } return selection; } // Create Tiles private function createTiles():void { // Create the Draft tiles removeTiles(); var drafts:Vector.<DraftVO> = scene.drafts; for (var i:int=0; i<drafts.length; i++ ) { var draftTile:DraftTile = new DraftTile(); draftTile.context = context; draftTile.draft = drafts[i]; draftGroup.addElement( draftTile ); } // Create the 'Add Draft' tile var addTile:AddTile = new AddTile(); addTile.addType = AppEvent.ADD_DRAFT; addTile.addTarget = scene; draftGroup.addElement( addTile ); draftGroup.percentHeight=100; } // Remove tiles private function removeTiles():void { draftGroup.removeAllElements(); draftGroup.percentHeight=0; } // Toggle the Scene selection private function selectScene():void { var event:AppEvent; if (sceneButton.selected) { event = new AppEvent(AppEvent.SELECT_SCENE); } else { event = new AppEvent(AppEvent.DESELECT_SCENE); removeTiles(); } event.data = scene; event.related = (context.chapter)?context.chapter:context.story; dispatchEvent(event); } </fx:Script> <!-- DRAFT GROUP --> <s:HGroup id="draftGroup" width="100%"/> <!-- SCENE BUTTON --> <s:ToggleButton id="sceneButton" click="selectScene()" selected="{changeSceneSelection(context.scene.uid == scene.uid)}" height="100%" width="100%" minWidth="150" minHeight="25" label="{scene.name}"/> </s:VGroup>
The Draft Tile
Class
DraftTile.mxml
Responsibilities
Base on Flex
VGroup
classDeclare a Flex
HGroup
for padding to exact height of other tiles, since this tile has no childrenDeclare a Flex
ToggleButton
for selecting theDraftVO
Expose a public property for setting the
DraftVO
to be displayedClicking the
ToggleButton
should set the selectedSceneVO
’scurrentDraft
and dispatch the appropriate event to select theDraftVO
Expose a bindable public property for setting the
SelectionContext
Provide the displayed
DraftTile
instances with theSelectionContext
object
Collaborations
The DraftTile
component knows
and controls its direct children: the Flex HGroup
and Button
components. It also knows the
SelectionContext
class, the
DraftVO
class, and the AppEvent
class.
The DraftTile
component is
known only by its parent, the SceneTile
component.
Code
<?xml version="1.0" encoding="utf-8"?> <!-- DRAFT TILE --> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:component="com.futurescale.sa.view.component.*" height="100%" width="100%"> <fx:Script> <![CDATA[ import com.futurescale.sa.model.vo.DraftVO; import com.futurescale.sa.model.vo.SceneVO; import com.futurescale.sa.view.context.SelectionContext; import com.futurescale.sa.view.event.AppEvent; // Selection context [Bindable] public var context:SelectionContext; // The Draft [Bindable] public var draft:DraftVO; // Toggle the Draft selection public function selectDraft():void { context.scene.currentDraft = draft; var appEvent:AppEvent = new AppEvent( AppEvent.SELECT_DRAFT ); appEvent.data = draft; appEvent.related = context.scene; dispatchEvent(appEvent); } </fx:Script> <!-- PADDING GROUP --> <s:HGroup width="100%"/> <!-- DRAFT BUTTON --> <s:ToggleButton id="draftButton" click="selectDraft()" selected="{context.draft.uid == draft.uid}" height="100%" width="100%" minWidth="75" minHeight="25" label="{draft.name}"/> </s:VGroup>
The Selection Context
Class
SelectionContext.as
Responsibilities
Expose public methods for selecting various
ValueObject
subclasses. These methods should ensure the deselection of related items or items below in the Story or Series hierarchy. For example, selecting a Part should clear the current Chapter, Scene, Draft, and Note selections, but not the Story, Season, or Series selections or the Cast and Milieu selections. Selecting a Story would also select its Cast and Milieu and clear the Part, Chapter, Scene, Draft, and Note selections.A bindable public
ValueObject
typedselectedItem
property should always be set to the item being selected by a selection method. Thus,selectStory( story:StoryVO )
would not only set thestory
property to the inboundStoryVO
, but also set theselectedItem:ValueObject
property as well. TheselectedItem
property is used by theItemInfo
andNotes
View Components, which work with anyValueObject
.Expose bindable public properties for reading the currently selected
CastVO
,ChapterVO
,CharacterVO
,DraftVO
,CastVO
,MilieuVO
,NoteVO
,PartVO
,SceneVO
,SeasonVO
,SeriesVO
,SettingVO
,StoryVO
, andValueObject
.
Collaborations
The SelectionContext
class
knows CastVO
, ChapterVO
, CharacterVO
, DraftVO
, CastVO
, MilieuVO
, NoteVO
, PartVO
, SceneVO
, SeasonVO
, SeriesVO
, SettingVO
, StoryVO
, and ValueObject
classes, for which it maintains
the entire system’s notion of selected items for operations.
The SelectionContext
class is
known by most all View Components, which look to its properties to
determine their selection-related behaviors. The SelectionContext
class is also known by
various Command
s, including
AddItemCommand
, DeleteItemCommand
, ApplySelectionCommand
, RemoveSelectionCommand
, ApplyChangesCommand
, DiscardChangesCommand
, StartupCommand
, etc.
Code
package com.futurescale.sa.view.context { import com.futurescale.sa.model.vo.CastVO; import com.futurescale.sa.model.vo.ChapterVO; import com.futurescale.sa.model.vo.CharacterVO; import com.futurescale.sa.model.vo.DraftVO; import com.futurescale.sa.model.vo.MilieuVO; import com.futurescale.sa.model.vo.NoteVO; import com.futurescale.sa.model.vo.PartVO; import com.futurescale.sa.model.vo.SceneVO; import com.futurescale.sa.model.vo.SeasonVO; import com.futurescale.sa.model.vo.SeriesVO; import com.futurescale.sa.model.vo.SettingVO; import com.futurescale.sa.model.vo.StoryVO; import com.futurescale.sa.model.vo.ValueObject; /** * The currently selected items in the UI. * * When calling the select methods, items * below the selection in the Series/Story * hierarchy as well as the selected Note, * Cast and Milieu are automatically nulled * as appropriate. */ [Bindable] public class SelectionContext { public static const NAME:String = "SelectionContext"; public function selectSeries( series:SeriesVO ):void { this.selectedItem = series; this.series = series; this.season = null; this.story = null; this.part = null; this.chapter = null; this.scene = null; this.draft = null; this.note = null; if ( series ) { this.cast = series.cast; this.milieu = series.milieu; this.setting = null; this.character = null; } } public function selectSeason( season:SeasonVO ):void { this.selectedItem = season; this.season = season; this.story = null; this.part = null; this.chapter = null; this.scene = null; this.draft = null; this.note = null; } public function selectStory( story:StoryVO ):void { this.selectedItem = story; this.story = story; this.part = null; this.chapter = null; this.scene = null; this.draft = null; this.note = null; if ( story ) { this.cast = story.cast; this.milieu = story.milieu; this.setting = null; this.character = null; } selectedText = (story)? story.getText(true) : ""; } public function selectPart( part:PartVO ):void { this.selectedItem = part; this.part = part; this.chapter = null; this.scene = null; this.draft = null; this.note = null; selectedText = (part)? part.getText(true) : ""; } public function selectChapter( chapter:ChapterVO ):void { this.selectedItem = chapter; this.chapter = chapter; this.scene = null; this.draft = null; this.note = null; selectedText = (chapter)? chapter.getText(true) : ""; } public function selectScene( scene:SceneVO ):void { this.selectedItem = scene; this.scene = scene; this.draft = null; this.note = null; selectedText = (scene)? scene.getText(true) : ""; if ( scene && scene.currentDraft ) selectDraft( scene.currentDraft ); } public function selectDraft( draft:DraftVO ):void { this.scene.currentDraft = draft; this.draft = draft; this.note = null; selectedText = (draft)? draft.text : ""; } public function selectNote( note:NoteVO ):void { this.note = note; } public function selectCharacter( character:CharacterVO ):void { this.selectedItem = character; this.character = character; this.note = null; } public function selectSetting( setting:SettingVO ):void { this.selectedItem = setting; this.setting = setting; this.note = null; } public var selectedText:String; public var selectedItem:ValueObject; public var series:SeriesVO; public var season:SeasonVO; public var story:StoryVO; public var part:PartVO; public var chapter:ChapterVO; public var scene:SceneVO; public var draft:DraftVO; public var note:NoteVO; public var cast:CastVO; public var milieu:MilieuVO; public var character:CharacterVO; public var setting:SettingVO; }
The App Event
Class
AppEvent.as
Responsibilities
Some people like to build separate classes for every Event
their system will use, and that is a
perfectly valid approach. In this application, we will have one custom
Event
class with many possible types.
With the AppEvent
, we are creating one part
of a two-part protocol. The constants defined on the AppEvent
class represent, in one place, all
of the intentions that will arise from the user interface to be
processed somewhere, either higher up in the display list, or deeper
in the system by Mediator
s and
Command
s. The other part of that
two-part protocol is the AppConstants
file, where we will define, in
a similar way, all of the notification names that will be shared by
the View and Controller tiers. There will often be duplication between
the two files, leading one to ponder whether they should be combined
into a single file, and the answer is no. There will not always be a one-to-one
relationship between event names and notification names, as you will
shortly see. It is best to separate event and notification names and
let the Mediators take care of translation. This two-part protocol
approach is in keeping with the promise made earlier about being able
to farm out the building of the entire UI to a developer or team that
does not know anything about PureMVC. It lets the UI developer decide
what events to dispatch and the PureMVC developers decide on how best
to mediate those events.
Also notice we take the approach of defining verbs and nouns as
private constants and then combine them in public constants that are
used for AppEvent
types. This is
not necessary, but it can have a clarifying effect on your event
naming.
Extend the Flash
Event
classDefine public static constants for valid
AppEvent
typesDefine public
Object
typed properties for data and related dataAccept
type
,data
, andrelated
arguments on the constructor, setting them to the associated public propertiesConstructor should call superclass constructor with the passed in
type
, atrue
bubbling
argument, and atrue
cancelable
argument
Collaborations
The AppEvent
class knows only
the Flash Event
class that it
extends, and the ActionScript Object
class that it has public properties
for.
The AppEvent
class is known
by most all View Components, which dispatches events defined by its
public, static type constants. The AppEvent
class is also known by various
Mediator
s, who listen to their View
Components for them, and many Command
s, including AddItemCommand
, DeleteItemCommand
, ApplySelectionCommand
, RemoveSelectionCommand
, ApplyChangesCommand
, DiscardChangesCommand
, which interpret the
AppEvent
s relayed to them inside
Notification
s by Mediator
s in order to complete their
business logic.
Code
package com.futurescale.sa.view.event { import flash.events.Event; public class AppEvent extends Event { // Components of the AppEvent types private static const ADD:String = "add"; private static const DELETE:String = "delete"; private static const MANAGE:String = "manage"; private static const SELECT:String = "delect"; private static const DESELECT:String = "deselect"; private static const EXPORT:String = "export"; private static const OUTLINE:String = "outline"; private static const EDIT:String = "edit"; private static const DISCARD:String = "discard"; private static const SAVE:String = "save"; private static const REPORT:String = "report"; private static const MOVE:String = "move"; private static const SERIES:String = "Series"; private static const SEASON:String = "Season"; private static const EPISODE:String = "Episode"; private static const STORY:String = "Story"; private static const PART:String = "Part"; private static const CHAPTER:String = "Chapter"; private static const SCENE:String = "Scene"; private static const DRAFT:String = "Draft"; private static const CAST:String = "Cast"; private static const CHARACTER:String = "Character"; private static const MILIEU:String = "Milieu"; private static const SETTING:String = "Setting"; private static const NOTE:String = "Note"; private static const WORDCOUNT:String = "WordCount"; private static const AHEAD:String = "Ahead"; private static const BACK:String = "Back"; // AppEvent types public static const ADD_SERIES:String = ADD+SERIES; public static const ADD_SEASON:String = ADD+SEASON; public static const ADD_EPISODE:String = ADD+EPISODE; public static const ADD_STORY:String = ADD+STORY; public static const ADD_PART:String = ADD+PART; public static const ADD_CHAPTER:String = ADD+CHAPTER; public static const ADD_SCENE:String = ADD+SCENE; public static const ADD_DRAFT:String = ADD+DRAFT; public static const ADD_CHARACTER:String = ADD+CHARACTER; public static const ADD_SETTING:String = ADD+SETTING; public static const ADD_NOTE:String = ADD+NOTE; public static const DELETE_SERIES:String = DELETE+SERIES; public static const DELETE_SEASON:String = DELETE+SEASON; public static const DELETE_EPISODE:String = DELETE+EPISODE; public static const DELETE_STORY:String = DELETE+STORY; public static const DELETE_PART:String = DELETE+PART; public static const DELETE_CHAPTER:String = DELETE+CHAPTER; public static const DELETE_SCENE:String = DELETE+SCENE; public static const DELETE_DRAFT:String = DELETE+DRAFT; public static const DELETE_CHARACTER:String = DELETE+CHARACTER; public static const DELETE_SETTING:String = DELETE+SETTING; public static const DELETE_NOTE:String = DELETE+NOTE; public static const DISCARD_STORY:String = DISCARD+STORY; public static const DISCARD_SERIES:String = DISCARD+SERIES; public static const EXPORT_SERIES:String = EXPORT+SERIES; public static const EXPORT_STORY:String = EXPORT+STORY; public static const EDIT_SEREIES:String = EDIT+SERIES; public static const EDIT_STORY:String = EDIT+STORY; public static const SAVE_STORY:String = SAVE+STORY; public static const SAVE_SERIES:String = SAVE+SERIES; public static const MANAGE_SERIES:String = MANAGE+SERIES; public static const MANAGE_STORY:String = MANAGE+STORY; public static const MANAGE_CAST:String = MANAGE+CAST; public static const MANAGE_MILIEU:String = MANAGE+MILIEU; public static const MOVE_AHEAD:String = MOVE+AHEAD; public static const MOVE_BACK:String = MOVE+BACK; public static const OUTLINE_SERIES:String = OUTLINE+SERIES; public static const OUTLINE_STORY:String = OUTLINE+STORY; public static const REPORT_WORDCOUNT:String = REPORT+WORDCOUNT; public static const SELECT_SERIES:String = SELECT+SERIES; public static const SELECT_SEASON:String = SELECT+SEASON; public static const SELECT_STORY:String = SELECT+STORY; public static const SELECT_PART:String = SELECT+PART; public static const SELECT_CHAPTER:String = SELECT+CHAPTER; public static const SELECT_SCENE:String = SELECT+SCENE; public static const SELECT_DRAFT:String = SELECT+DRAFT; public static const SELECT_CHARACTER:String = SELECT+CHARACTER; public static const SELECT_SETTING:String = SELECT+SETTING; public static const SELECT_NOTE:String = SELECT+NOTE; public static const DESELECT_PART:String = DESELECT+PART; public static const DESELECT_CHAPTER:String = DESELECT+CHAPTER; public static const DESELECT_SCENE:String = DESELECT+SCENE; public var data:Object; // optional data object public var related:Object; // optional related data object public function AppEvent( type:String, data:Object=null, related:Object=null ) { super(type, true, true); this.data = data; this.related = related; } } }
Get ActionScript Developer's Guide to PureMVC now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.