Firefly, at its core, uses the popular Flux + React architecture.  It emphasizes on composable view components and unidirectional data flow as illustrated below.  Below are very brief description of the major components.  You can get more information on these two technologies here:

https://facebook.github.io/flux/

http://facebook.github.io/react/

 What is flux?

firefly flux     

Action Creators

These are functions that get called when something happens that could change the application state.  These actions are triggers by user interactions, server events or API.  Its job is to create an Action object and then send it to the dispatcher.  There are 2 types of Action Creators; synchronous and asynchronous.  Synchronous creator creates one action while asynchronous creator creates at least 2 actions.  One when the creator is called, and one when the server's call returns.  There are cases where you may need to perform additional functions which trigger more actions.

Dispatcher

This is a singleton object that receives actions from the Action Creators and then dispatches them to all of the registered Callbacks.  The callbacks are not subscribed to particular events.  Every action is dispatched to every registered callback.  Callbacks can be deferred in whole or part until other callbacks have been executed.  Later in the Stores section, we will define the major inter-store dependencies.

Stores

Stores contain the application state and logic.  Only getter methods are exposed.  The dispatcher will send all actions to all stores.  The store decide when or how it should update its state.  It can query other stores via their getter methods.  When you rely on other store, you must declare the dependency.  If state changes, emit a change event to notify the Views.

Views

React provides the kind of composable and freely re-renderable views we need for the view layer. Close to the top of the nested view hierarchy, a special kind of view listens for events that are broadcast by the stores that it depends on. We call this a controller-view, as it provides the glue code to get the data from the stores and to pass this data down the chain of its descendants. We will use controller-views throughout the application to manage section of the page. 

Firefly's flux

Firefly uses redux.js which is a modified version of flux.  The main differences are single store, reducer functions, and the merge of dispatcher and stores.  The diagram below shows the modified data flow after we replace flux with redux and added other keyed pieces.  From here on, we will use redux's terminology instead of flux when referring to the components below.  

http://rackt.github.io/redux/

redux

Actions

There are two categories of actions: Action and RawAction.
Action is an object that contains a unique 'type' field to identify the action and a 'payload' field to carry the data to the reducer(s).  It is important that the action's payload is known by the user of this action, mainly the reducers.  Action types are unique and they define a specific action affecting the state of the application.  Remember, an action flow through all of the reducers.  It may affect many parts of the state tree.  To keep action types unique, it's best to prepend it with a short domain name.
RawAction is the same as Action.  However, we called an action a RawAction when it has not been consume by the ActionCreator.  The RawAction's payload may contain data that never get into the reducers.  An example would be an action to fetch data from the server.  The payload of this action contains the parameters needed to make the server call, and not the actual data coming back from the server.   The data from the server call will be delivered via a new Action.

Creators

Reducers

Redux reducers are pure functions.  This has many benefits. It's pretty easy to log every action, and you will basically get features like replay and undo/redo for free.  Unfortunately, in real-world use cases, it is impossible to have reducers without side effects. For example, API calls, logging, caching, local storage, etc.  That is why we need to introduce the concept of "side effects" into the reducer.  The modified diagram below illustrate the flow of actions with 'effect' added to the picture.  If it's not obvious, effect must be asynchronous.

Packaging

In practice, it's very common to import {actionTypes, actions, reducer} tuple for each use case.  Therefore, it makes sense for these components to be bundled together in an isolated module that is self contained.  We call this bundle a controller, because it controls and manages its designated data node.  So, the AppDataCntrl.js files contains the {actionTypes, actions, reducer} tuple responsible for the 'appData' data node immediately under the application state tree.

View

View vs Component

For the purpose of this documentation, we will define view as a presentation of a piece of data within the main data store.  It is connected to the main store and will react to its state changes.  Component is an interface element used to display data that is given to it.  It is totally encapsulated and will display the data given to it no matter where it came from.  Below is a diagram illustrating the composition of a view and its layers of components.

 

React Views

Here is a React's component lifecycle diagram for reference.  It outlines the states a component goes through from mounting, to update, and then unmount.

React Lifecycle

Layout

Firefly provides a framework and a set of views and reusable components.  It's up to the user of Firefly to layout those components.  As part of the framework, Firefly also provides high level components that can be easily assemble to create an application.  Below is a example of how a WISE application may look like.

<WISE>
<WiseBanner bg='/irsa.gif' login={this.handleLogin} />
<Menu data={this.props.appData.menu} />
<Notification data={this.props.appData.notifications} />
<SearchPanel data={this.props.searches} forms={this.createForms()}/>
<div>
<PlotsDisplay data={this.props.allPlots} visible={this.props.layout.plotsView.isVisible} />
<TablesDisplay data={this.props.tables} visible={this.props.layout.tablesView.isVisible} />
<ChartsDisplay data={this.props.allCharts} visible={this.props.layout.chartsView.isVisible} />
</div>
<ErrorView data={this.props.appData.errors} flow='top'/>
</WISE>

Beside WiseBanner, the other components are provided by Firefly.  WISE may freely lay out the components as it pleases.  The component developer also have very clear boundaries as to what it manages and what it should not.

Application State

In Firefly, all application state is stored as a single object tree.  In the diagram below, the yellow boxes denote main branches immediately below the root.  It is most likely that there is a one to one relationship with a sub-reducer.

Firefly Stores

API

These are a set of API made available to the outside world to gain access to the inner functions of Firefly.  This is a good place to introduce an abstraction layer to remove the coupling between the View's interactions and the ActionCreators.  
By internally mapping Requests to an ActionCreator, this allow for additional mappings as well as overriding default mappings.  
Say by default, APP_SHOW_LOGIN opens a generic login panel.  A user of firefly can map that action to open a custom login panel.  
This mapper maps a constant string to an action creator.  When firefly.process is called, it will take the request's ID, find the mapped ActionCreator, then call the it with the request's params.  The rest is all flux.  
To save unnecessary boilerplate code, the mapper should return a 'passthrough' ActionCreator for any request that is not mapped.  The 'passthrough' ActionCreator creates an action based on the Request object, then dispatch it.  This allow firefly to process a simple-action request without creating  and mapping an ActionCreator for it.
process request flow
Utility functions for dispatching

Often it makes sense to have a utility functions to call process. This function will build the payload and define the action type. This is a good way to document and default the payload parameters. This type of utility function should implement the following standard:

  • The function name should start with "dispatch"
  • The action type as the second part of the name
  • The function should be exported from their related controller
  • The function parameters should the documented with jsdocs
  • Optional parameters should be clear

Example - if action type is PLOT_IMAGE and the PLOT_IMAGE action is exported from the ImagePlotCntlr module. The the name should be processPlotImage.

ImagePlotCntlr.processPlotImage()
Extending the Framework

This is where you can extend the framework to provide additional functionalities.  
Before calling bootstrap, you can register additional actions and reducers into the framework.  
Bootstrap should return a promise when it's done to allow user of Firefly to do its own bootstrap. 
firefly.process(Request) should have have a conditional mode so the API users can control flow and handle errors.  This can be a separate function or one with optional condition parameter. 

api

Server Events

Events coming from the server.  This communication can be between server and client or peer to peer depending on the channels.  Events should be converted into Requests that get processed by the application.  This approach keep the flux core clean.  Server Events are just another way of interacting with the application using the same mechanism as routing and user interactions.

Routing

Routing is a mechanism for mapping the components of the application to the URL.  The proposed BrowserHistory is a history implementation for DOM environments that support the HTML5 history API.  It provides the cleanest looking URLs and it does not hijack the hash(#) feature of a URL.  If a browser does not support this HTML5 history API, it will fall back to using a full page refresh.

Regardless whether we use HashHistory or HTML5 BrowserHistory the path pattern should be the same.  We will loosely follow a restful api approach where the path leads to a resource or a service with optional parameters.  The parameters follow the queryString convention.  

Examples:

/s/posSearch?target=m51&size=0.2334&band=1,2,3,4

This open the search panel, select the Position Search form and filled in the form fields with the given parameters.

/v/imgSearch?id=posSearch&target=m51&size=0.2334&band=1,2,3,4

This open the results view and submit an action using 'imgSearch' to select an Action Creator and the queryString to compose the search criteria.

/v;mode=expanded/imgPlot?id=showPlot&byPath=/{workspace}/upload/a.fits

This switch the app to a single component in expanded mode and submit an action to fetch the image.

If we have to support previous URL scheme, we can map the older url to a view that translate the older '#id=blah&n=v[&n=v]' scheme to the newer one.

On Startup

Application Startup

Follow the action

 

 

Data Visualization Components

Firefly has several data visualization components.  

 

 

FITS Image Visualization

todo

 

Table Visualization

todo

XYPlot Visualization

todo

Histogram Visualization

todo

Development Environment

Directory Structure
firefly/
+--src
| +--fftools
| | +--java
| | | +--edu/caltech/ipac/fftools
| | +--js
| | | +--app
| | +--html
| | | +--images
| | +--styles
| +--firefly
| | +--java
| | | +--edu/caltech/ipac/fftools
| | +--js
| | | +--core
| | | +--visualize
 | | | | +--ui
 | | | +--tables
| | +--html
| | | +--images
| | +--styles

 

Setup Env

cd ${firefly_repo}
$ npm install
This will install all modules listed as dependencies in the package.json file.  If the dependency is already up-to-date, it will not re-install.
Although you are free to run this command at any time, it is not necessary.  During any gradle build where node_modules is needed, it will automatically run this command to update the dependencies.

Build

$ cd ${firefly_repo}/src/fftools
$ npm run build
This build the client side code by bundling the modules and its dependencies into a single javascript file.  In the near future, we will find the best way to split up the code to keep initial loading time low.
This is equivalent to $ cd ${firefly_repo}; gradle :fftools:buildClient

Lint

$ cd ${firefly_repo}/src/fftools
$ npm run lint

To apply additional rules in .eslint-strict.json, use
$ npm run lint:strict

Dev Mode

$ cd ${firefly_repo}/src/fftools
$ npm run dev
This monitor your source directory for changes.  Once detected, it will rebuild and pushed the changes to your deployment area.