Afterimage SDK Documentation

The Afterimage workflow:

As a user, you have the ability to create channels and add modules to them. When a channel is active on a screen, it will sequentially execute through its modules, displaying each in turn.

To create a module, the user adds variables to the module on the server as defined by the module developer. These variables (e.g. src, duration, text, etc.) are then passed to the client as metadata inside the modules. The client then loads the individual modules and presents the metadata to them. The modules then act upon the meta however they want.

As a creator, you first need to decide what variables you want available and add them to the server. Currently there is no streamlined way to do this, and they need to be added manually through the server code.

As far as module code itself goes, there are four AfterImage module functions available for use. They include AfterImage.load, AfterImage.start, Afterimage.stop, and AfterImage.destroy.


AfterImage.load

The AfterImage load function should include a single argument. This will automatically be passed in as the meta object from the server. It can then be used to set module variables. The load function is called as soon as a channel starts.

Example:



      var src;
      AfterImage.load = function(meta) {
          if (meta.hasOwnProperty('src')) {
              src = meta.src;
              // src can now be used inside the start function
          }
      }
      

The examples in this documentation will all be written with ES5, but AfterImage modules also work fine with ES2015 notation if that is a preference.


AfterImage.start

The AfterImage start function is called when a module is shown. It doesn't take any arguments, but can easily use global variables assigned from the load function. Following along the same vein as the example for the load function, here is an example for the start function:



      AfterImage.start = function() {
          // There are many ways we can use the src var from the load.
          // If we assume the src is an img url,
          // we can simply assign it to the background.
          document.body.style.backgroundImage = 'url("' + src + '")';
      }
      

AfterImage.stop

The AfterImage stop function is called when a module is hidden and the next module is shown.



      AfterImage.stop = function() {
          document.body.style.backgroundImage = 'none';
      }
      

Technically, we could just leave the image for the next time around. This function is probably more useful in instances where audio or video is in action. It could also be useful when a slideshow is being played and you don't want a flash of the previous image when changing to the new image in the start function. In fact, you could possibly change to the next image inside the stop function, though there are no guarantees it won't be displayed before the module itself is hidden.


AfterImage.destroy

The AfterImage destroy function is only called whenever server updates are being taken into account, e.g., the channel is changing or has been edited.



      ...
      // Load image
      var image = new Image();
      image.src = '...';
      ...
      AfterImage.destroy = function() {
          image = null;
      }
      

In most cases, this function probably won't be completely necessary. A case when it could be useful is when you are loading a slideshow containing hundreds of photos and want to clean up the image data before changing channels.


Example Modules

URL Example:

If you want to load a webpage or url of some sort, make sure you load it in an iframe as opposed to navigating away from the page. Navigating to a different url will make it so your module no longer has the SDK and can't receive commands from the client.

Given that we have the following meta from the server:



      {
          url: 'http://gitter.im/FreeCodeCamp/FreeCodeCamp'
      }
      

We can use the following code to display this gitter chat room url as an embed:



      // Variables
      var url;

      // Body styles
      document.body.style = 'margin:0;overflow:hidden'

      // Elements
      var iframe = document.createElement('iframe');
      iframe.frameBorder = 0;
      // Set the iframe height > 100%
      // To hide the join chat room btn
      iframe.style = 'display:none;height:calc(100% + 67px);width:100%';

      // Module functions
      AfterImage.load = function(meta) {
          if (meta.hasOwnProperty('url')) {
          // Gitter has an embed option
              url = meta.url + '/~embed';
          }
          iframe.src = url;
          document.body.appendChild(iframe);
      }

      AfterImage.start = function() {
          iframe.style.display = 'block';
      }

      AfterImage.stop = function() {
          iframe.style.display = 'none';
      }
      

Since this is a custom module, as both the user and programmer we'll know that the input should end without a backslash, but if we wanted to let others use this module, we would add some sort of parsing for the url.

Basic Example:



      // Global variables
      var src;
      // AfterImage module functions
      AfterImage.load = function(meta) {
          if (meta.hasOwnProperty('src')) {
              src = meta.src;
          }
      }
      AfterImage.start = function() {
          document.body.style.backgroundImage = 'url("' + src + '")';
      }
      AfterImage.stop = function() {
          document.body.style.backgroundImage = 'none';
      }
      

Example Slideshow Module:


      // Global variables
      var images, duration = 7000, timeout, index = 0;

      // Set body styles
      document.body.style.backgroundSize = 'cover';
      document.body.style.backgroundPosition = 'center';

      // AfterImage module functions
      AfterImage.load = function(meta) {
          if (meta.hasOwnProperty('images')) {
              images = meta.images.slice();
          }
          if (meta.hasOwnProperty('duration')) {
              duration = meta.duration;
          }
          // Load first image
          loadImage(images[index]);
      }
      AfterImage.start = function() {
          active = true;
          index = 0;
          slideshow();
      }
      AfterImage.stop = function() {
          active = false;
          clearTimeout(timeout);
      }

      function loadImage(url) {
          var image = new Image();
          image.src = url;
      }

      function slideshow() {
          if (active) {
              // Set image
              document.body.style.backgroundImage = 'url("' + images[index] + '")';
              // Increment index
              if (index >= images.length - 1) {
                  index = 0;
              } else {
                  index++;
              }
              // Load next image
              loadImage(images[index]);
              // Set timeout for next image
              timeout = setTimeout(slideshow, duration);
          }
      }
      

Example jQuery Module:



      // Global variables
      var duration = 10000, active = false, sentences = [], index = 0;

      // Load scripts
      const tag = document.createElement('script');
      tag.src = '../jquery.min.js';
      const firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // AfterImage module functions
      AfterImage.load = function(meta) {
          if (!window.jQuery) {
              requestAnimationFrame(AfterImage.load);
          } else {
              if (meta.hasOwnProperty('duration')) {
                  duration = meta.duration;
              }
              if (meta.hasOwnProperty('sentences')) {
                  sentences = meta.sentences;
              }
              // Set styles
              $('body').css({
                  backgroundColor: '#D1D3C4',
                  color: '#444554',
                  fontFamily: 'Palatino'
              })
              // Add elements
              $('body')
              .append($('<h1>', { id: 'container' })
                  .css({
                      position: 'absolute',
                      top: '50%',
                      transform: 'translateY(-100%)'
                  })
              );


              if (active) {
                  animate();
              }
          }
      }

      // Animation function
      function animate() {
          if (active) {
              var text = sentences[index];
              if (text === '/time') {
                  // Get time
                  var date = new Date(),
                      hours = date.getHours(),
                      minutes = date.getMinutes(),
                      pm = false;
                  // Format timestamp
                  if (hours > 12) {
                      pm = true;
                      hours -= 12;
                  } else if (hours === 0) {
                      hours = 12;
                  }
                  if (minutes < 10) {
                      minutes = '0' + minutes;
                  }
                  text = hours + ':' + minutes + (pm ? ' PM' : ' AM');
              }
              $('#container').text(text);
              $('#container').css({
                  left: '',
                  right: ''
              });
              $('#container').animate({
                  right: '10px'
              }, duration / 2).animate({
                  left: '10px'
              }, duration / 2, function() {
                  if (index > sentences.length - 2) {
                      index = 0;
                  } else {
                      index++;
                  }
                  animate();
              });
          }
      }

      AfterImage.start = function() {
          active = true;
          // Make sure jQuery is loaded
          if (window.jQuery) {
              animate();
          }
      }

      AfterImage.stop = function() {
          active = false;
          $('#container').finish();
          index = 0;
      }