Screen

Role

  • A screen is a view. It can contain views and primitives,
  • A screen gets the business data and connects its children to the data,
  • A screen handles navigation / gesture,
  • A screen handles back behavior,
  • A screen manages focus,
  • A screen listens event-bus (services, …),
  • A screen routes to other screens,

Declaration

import $Screen from "@Screen";

/**
 * @screen
 */
export default $Screen.declare("LiveScreen", {
    properties: {
        deferred: false
    },

    // Listen message events (from services, views, ...)
    listen: ["screen.ready"],

    // Listen input events (keys, mouse, touch, ...)
    inputs: [],

    // Inputs event blocked during screen loading
    blockedInputs: [],

    // The children that compose the screen (view's references)
    children: {},

    methods: {
       /////////////////////////////////////////////////////
       // Extension points
       /////////////////////////////////////////////////////
      /**
       * Prepare screen data: get the business data to feed the views
       * View should always be fulfilled with business model(s).
       *
       * It must returns a Object.
       *
       * @param {Object} [context] The context shared by the screens during display
       * @returns {Promise<Object,Error>} returns a promise resolved with the data
       */
      prepareData: function(context) {
          return Promise.resolve({});
      },

      /**
       * Connect the data prepared by {@link prepareData} to the views.
       * Use the data property, which is the return value of {@link prepareData}.
       *
       * @param {Object} [context] The context shared by the screens during display
       */
      connectData: function (context) {},

      /**
       * Called by RouterManager to start loader.
       * In non deferred mode it will be called at the beginning of prepareData.
       * In deferred mode it will be called at the end of show (transition animations finished) if prepareData/connectData not finished yet
       */
      loadingStart: function () {},

      /**
       * Called by RouterManager to stop loader.
       * It will be called at the end of connectData.
       */
      loadingStop: function () {},

      /**
       * Called before the screen is shown
       * The flow is interrupted until the promise has been resolved/rejected
       *
       * @param {Object} [context] The context shared by the screens during display
       */
      beforeShow: function(context) {},

      /**
       * Called before the screen is hidden
       * The flow is interrupted until the promise has been resolved/rejected
       *
       * @param {Object} [context] The context shared by the screens during display
       */
      beforeHide: function(context) {},

      /////////////////////////////////////////////////////
      // Callbacks
      /////////////////////////////////////////////////////

      /**
       * Called when the screen is ready
       * The data have arrived
       * It is the right place to connect the data to children views
       */
      onScreenReady: function(){
          var context = this.context;
      }
   }
})

Expected use

An application is designed as a succession of screens. A Screen is a component that contains a set of Views. One screen can contain as many views as it needs and a View can belong to several screens.

The screen loads business data (i.e. Models) provided by services and feeds its Views with them. An application always starts on a Screen, which is loaded by the Router.

There is always one active screen during the whole lifecycle of the application, named the currentScreen. When a screen is displayed, all its views are shown. When it is hidden, all its views are hidden.

Extension points

  • prepareData() : get the data to feed the views, which should always be fulfilled with business model(s)
  • connectData() : connect the prepared data to the views
  • beforeShow() : called before the screen is shown
  • beforeHide() : called before the screen is hidden
  • loadingStart() : start a loader
  • loadingStop() : stop the loader

Display mode

  • Non-deferred : in non-deferred mode (default), when going from currentScreen to targetScreen, the router stays on the currentScreen until the targetScreen is ready to be shown (data have been retrieved, connected to the view, targetScreen is builded). Once the targetScreen is ready, the router will launch the animations and will switch to the targetScreen

  • Deferred : in deferred mode, when going from currentScreen to targetScreen, the router builds the targetScreen and switches immediately to it. Within the same time, it acquires data. Once the data are retrieved and the view connected, it will launch the animations.

Screen display sequence in non-deferred mode

application
application
routerManager
routerManager
targetScreen
targetScreen
prepareData
prepareDa...
show
show
subscribe events
subscribe events
show children
show children
focusChild
focusChild
connectData
connectDa...
loadingStop
loadingSt...
currentScreen
currentScreen
unsubscribe events
unsubscribe events
eventBus
eventBus
loadingStart
loadingSt...
display target screen
display target screen
beforeShow
beforeShow
wait animations end
wait anim...
routerManager.screenChanged
routerManager.screenChanged
Text is not SVG - cannot display

Deferred mode

By comparison, in deferred mode, prepare data and display tasks are done in parallel.

par
par
[Preparing Data]
application
application
display target screen
display t...
routerManager
routerManager
[Displaying Screen]
routerManager.screenChanged
routerManager.screenChanged
targetScreen
targetScreen
prepareData
prepareDa...
show
show
subscribe events
subscribe events
show children
show children
focusChild
focusChild
connectData
connectDa...
loadingStop
loadingSt...
beforeShow
beforeShow
wait animations end %3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22routerManager.screenChanged%22%20style%3D%22html%3D1%3BverticalAlign%3Dbottom%3BendArrow%3Dopen%3Bdashed%3D1%3BendSize%3D8%3BfontSize%3D18%3B%22%20edge%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20relative%3D%221%22%20as%3D%22geometry%22%3E%3CmxPoint%20x%3D%22-220.5%22%20y%3D%22760%22%20as%3D%22sourcePoint%22%2F%3E%3CmxPoint%20x%3D%22379.5%22%20y%3D%22760%22%20as%3D%22targetPoint%22%2F%3E%3CArray%20as%3D%22points%22%3E%3CmxPoint%20x%3D%2250%22%20y%3D%22760%22%2F%3E%3CmxPoint%20x%3D%22250%22%20y%3D%22760%22%2F%3E%3C%2FArray%3E%3C%2FmxGeometry%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
wait anim...
currentScreen
currentScreen
unsubscribe events
unsubscribe events
eventBus
eventBus
loadingStart
loadingSt...
Text is not SVG - cannot display

Caution

Remarks

  • The loadingStart/loadingStop is not represented here but works on deferred mode,
  • the router has a allowConnectDataDuringAnimation property (false by default): if enabled, connectData is called as soon as prepareData is resolved.

Events

Listening to events

The screen is a subscriber: it can listen events and can react to them by using the reserved keywords listen and inputs. They are both arrays containing the type of the event to watch. The screen must define as many on{EventRoleName} methods as events defined into the listen and inputs arrays. When the event is dispatched, the callback is executed.

import $Screen from "@Screen";

/**
 * @screen
 */
export default $Screen.declare("LiveScreen", {

   listen: ["player.sourceChangeSuccess"],

   inputs: [
      "keypress.UP",
      "keypress.DOWN",
      "keypress.LEFT",
      "keypress.RIGHT"
   ],

   methods: {
      /**
       * Update the source
       *
       * @param {Source} source The new source being played
       */
       onPlayerSourceChangeSuccess: function(source) {
         // TODO Update the channel list with the new source !
       },

       // Navigation
       onKeypressUP: function() {
          // TODO up implementation
       },
       onKeypressDOWN: function() {
          // TODO down implementation
       },
       onKeypressLEFT: function() {
          // TODO left implementation
       },
       onKeypressRIGHT: function() {
          // TODO right implementation
       }
   }
});

Automatic subscription/un-subscription to events

When a screen is shown, it automatically subscribes to the list of the events contained in listen and inputs keywords. When a screen is hidden, it automatically un-subscribes to the list of the same events. It means that only the active screen is notified by the bus, which is always what you would like to do !

Inheritance and method overriding

The declarative way offers a simple mechanism to manage inheritance with listeners. If you need to add a behavior to a callback tied to an event, simply overwrites the callback method, then calls the super method . Finally, you can do what is needed to be done

import $LiveScreen from "@LiveScreen";

/**
 * @screen
 */
export default $LiveScreen.declare("ZapBannerScreen", {

   methods: {
       /**
        * @override
        */
       onPlayerSourceChangeSuccess: function(source) {
          $LiveScreen.prototype.onPlayerSourceChangeSuccess.apply(this,arguments);
          // just do what is needed to be done...
       }
    }
});

If, for some reason, you don’t want to react to the event, just overrides the callback with an empty function.

export default $LiveScreen.declare("ZapBannerScreen", {

   methods: {
      /**
        * @override
        */
      onPlayerSourceChangeSuccess: function(source) {
          console.debug("onPlayerSourceChangeSuccess deactivated for ZapBannerScreen");
      }
   }

});

Adding animations

Animations between screens are managed by the core: you should never handle an animation between two screens by hand. To declare a screen animation, you can use the animations class keyword.