You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document is a summary of the convention we harnesed in the eBay Israel Social Center (ISC) for client-side development. It is built upon Backbone.Marionette and relies heavily on its Application, Module, Controller and AppRouter objects. We extended these classes with syntactic sugar, bootstrapping and other features we needed in order to simplify the way we work.
NOTE: The extensions were built up until v1.0.0-rc2 and do not include (yet) the Backbone update changes (we are still using EventBinders for example).
We used Marionette with our extensions to build the StubHub 'Go With Friends' service. The service allows users to arrange group events, invite friends, see who's in, and decide on the best tickets for everyone.
Some examples:
You can check the service yourself by going to StubHub, picking an event, and clicking the Go With Friends button at the top left.
A Marionette Module is a representation of a page or flow which includes a Controller, Router and other classes required for the flow. The Module does not control the flow. It contains the ‘instructions’ for how to initialize such one (we call it the 'DNA of the flow').
We also use it for namespacing. Since most of our classes are wrapped in a App.module definition, we can reference other classes or objects within the module as Module.NeededClass instead of its full path:
App.module'SelectionFlow', (Module, App) ->classModule.Component# ... class definitionobject=newModule.Component() # instead of App.SelectionFlow.Component
We used to use modules as global vents for the flow they represents, but it turned out it couples them too tightly to the Module. Instead we now pass vents/EventAggregators to whatever needs them.
How do we structure our Application?
Each module has its own folder. The folder contains a definition file (index.js), a Controller and an optional Router. The rest of the Module's classes are contained in folders named by their roles (models, views etc).
Flow Modules represent complex components (made of several views and models), with a controller that mediates between the different views of the components. In some cases the different states in those components also have dedicated routes.
They are defined similarly to generic Modules:
App.flowModule'SelectionFlow', options
The flowModule method accepts the following options:
define: Additional definition for the Module. Here we can add new initializers, finalizers and Module logic.
region: A Marionette Region object in which the page will be shown. If one is not given, the Module will use the region option used in the Module's start call instead.
startFlow: A boolean value which determines whether the flow starts automatically once the Module initialization sequence ends (by calling the Module's Controller start method).
What happens when a Flow Module is started?
The Module creates instances of a Controller which is defined under the Module's namespace. The Controller receives the same options used in the Module's start call, and also saves a reference of the region given in the Module's definition/start call.
After the Controller is created, the Module also creates an AppRouter similarly (only if one is defined) with that Controller in its options.
How do I close/end a Flow?
You can either close the flow temporiarly using the Module's Controller close method (more on that further on), or shut the flow completely (unitialization) by calling the Module's stop method.
A Page module is a Flow Module which represents a page in the application. There should only be a single active Page Module in the application simultaneously. A Page Module is very similar to a Flow Module and can accept the same options.
They are different by:
The startFlow option is always set to true - The flow starts automatically once the initialization sequence ends.
The Module checks if the Application object has a defaultRegion property. If so, it uses that Region as the region the page will be shown in.
Page Modules are defined similarly to Flow Modules:
App.pageModule'ShowPage', options
Pages can be easily replaced by calling one Page Module's stop method, and right after that calling the start method of a different Page Module.
If a Flow Module instructs how to build a flow, then a Controller is the actual object which manages it. We use the Controller objects to:
Define all the available major actions within a flow (or page).
Create and show the layout and views which are used in the flow.
Hold the models, collections and services instances required in the flow.
Manage other complex components which are represented by Flow Modules.
App.module'SelectionFlow', (Module, App) ->classModule.ControllerextendsBackbone.Marionette.Controllerinitialize: (options) -># create models' instances.# include flow modules.onStart:-># bind to events# models bootstrapping.# show layout.onClose:-># close layout# reset models.# actions
How do we use Flow Modules within Controllers?
Sometimes higher-level Controllers have to interact with complex components that are represented by Flow Modules. In that case, we use our own Controller extension - the includeModule method:
When we include a Flow Module with the includeModule method, the Controller starts that Module, and returns a reference of its Controller instance. After this, we can call its methods directly when needed (e.g. start, actions) and bind to events it triggers:
When the stop method of a Controller is called, all the included Flow Modules are stopped as well (which stops their Controllers as well). The Controller stop method, unlike close, means the Controller is uninitialized completely, rather than closed for a time being.
'This behavior is similar to submodules, why duplicate it?'
We added this extension after we had to use some complex components in different parts of our application. Originally we used these complex components as submodules:
App.module'CreationFlow'App.module'CreationFlow.SelectionFlow'# the submodule.
Later on we discovered we would need the SelectionFlow component in a different part of the application, and had to find a way to separate it from its parent module. Using includeModule we can manage these components from the outside (an higher-level Controller) and make sure they don't becomes zombies after a flow is no longer needed (calling the `stop method).
How do we manage the Views of the flow?
When a Controller is initialized by a Module, it receives a Marionette Region instance in the region option. We can then show views and layout in this region:
setLayout(LayoutClass, options) - Creates an instance of LayoutClass with options and shows it on the Controller's region. The method also saves a reference to the layout. You should usually call this method on the Controller's onStart method.
showViewInRegion(ViewClass, region, options) - Creates an instance of ViewClass with options and shows it in given region.
Having a region option, allows us to open certain flows in different regions. The most noteable example is opening a component on the page page (main region) or in a popup (which we have a region for).
The URL changes, then AppRouter calls a corresponding action method.
Something happens in the Application (e.g. user click), which triggers an action.
We don't simply change the URL and rely on the Router to call the corresponding action when we want to perform one:
It couples the flow heavily to the Router. What would happen if we want to get rid of URL changing in the application?
It makes the flow harder to follow. We would have to go to the Router and see what action is called when the URL changes instead of seeing the action itself called in the code.
Instead, the AppRouter binds to Controller events on its initialize method (we had to add an EventBinder to Marionette's AppRouter). The Controller triggers these events at the end of actions:
The handler calls Backbone.navigate without the trigger option set to true. This way, the URL will be changed but the action will not be called again.
How do we inform the Controller that it should call an action based only on a URL change?
This is relevant only when:
The page loads with the action hash already in the URL.
The user changes the URL manually.
In this case, the AppRouter will automatically call the corresponding action as defined in its appRoutes. This however creates a problem: The action is called but the Controller is not yet started (no bound events, no bootstrapped models, no layout shown etc).
In order to prevent this, we simply add a start() call at the beginning of each action. In order to prevent starting the Controller if it's already started, we added a started flag to the Controller which prevent starting an already started Controller.
# controller actioninviteMail:->@start() # this will not start the controller if it's already started.@showLayout(MailInviteView, ...)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters