Extending an AngularJS Workflow
===============================

A workflow extends the ``extensibleService``. This means that all workflows
inherit properties and methods provided by the ``extensibleService``. Extending
a workflow allows you to add your own steps, remove existing steps, and inject
custom data handling logic. Refer to inline documentation on what those
properties and methods are.

We highly recommend that you complete the
:ref:`tutorials-plugin` if you have not done so already.
If you do not know how to package and install a plugin, the rest of this
tutorial will not make sense! In this tutorial, we will examine an existing
workflow and how we can extend it as a plugin.

..  Note ::

    Although this tutorial focuses on extending a workflow, the steps here
    can easily be adapted to extend any service that inherited the
    ``extensibleService``. Examples of other extensible points include
    table columns and table actions.

File Structure
--------------

Remember that the goal of this tutorial is to inject our custom step into an
**existing** workflow. All of the files we are interested in reside in the
``static`` folder.

::

  myplugin
  │
  ├── enabled
  │   └── _31000_myplugin.py
  │
  └── static
       └── horizon
           └── app
               └── core
                   └── images
                       ├── plugins
                       │   └── myplugin.module.js
                       │
                       └── steps
                           └── mystep
                               ├── mystep.controller.js
                               ├── mystep.help.html
                               └── mystep.html

myplugin.module.js
------------------

This is the entry point into our plugin. We hook into an existing module via the
run block which is executed after the module has been initialized. All we need
to do is inject it as a dependency and then use the methods provided in the
extensible service to override or modify steps. In this example, we are going to
prepend our custom step so that it will show up as the first step in the wizard.

.. code-block:: javascript

  (function () {
    'use strict';

    angular
      .module('horizon.app.core.images')
      .run(myPlugin);

    myPlugin.$inject = [
      'horizon.app.core.images.basePath',
      'horizon.app.core.images.workflows.create-volume.service'
    ];

    function myPlugin(basePath, workflow) {
      var customStep = {
        id: 'mypluginstep',
        title: gettext('My Step'),
        templateUrl: basePath + 'steps/mystep/mystep.html',
        helpUrl: basePath + 'steps/mystep/mystep.help.html',
        formName: 'myStepForm'
      };
      workflow.prepend(customStep);
    }

  })();

..  Note ::

    Replace ``horizon.app.core.images.workflows.create-volume.service`` with
    the workflow you intend to augment.

mystep.controller.js
--------------------

It is important to note that the scope is the glue between our controllers,
this is how we are propagating events from one controller to another. We can
propagate events upward using the $emit method and propagate events downward
using the $broadcast method.

Using the $on method, we can listen to events generated within the scope. In
this manner, actions we completed in the wizard are visually reflected in the
table even though they are two completely different widgets. Similarly, you can
share data between steps in your workflow as long as they share the same parent
scope.

In this example, we are listening for events generated by the wizard and the
user panel. We also emit a custom event that other controllers can register to
when favorite color changes.

.. code-block:: javascript

  (function() {
    'use strict';

    angular
      .module('horizon.app.core.images')
      .controller('horizon.app.core.images.steps.myStepController',
        myStepController);

    myStepController.$inject = [
      '$scope',
      'horizon.framework.widgets.wizard.events',
      'horizon.app.core.images.events'
    ];

    function myStepController($scope, wizardEvents, imageEvents) {

      var ctrl = this;
      ctrl.favoriteColor = 'red';

      ///////////////////////////

      $scope.$on(wizardEvents.ON_SWITCH, function(e, args) {
        console.info('Wizard is switching step!');
        console.info(args);
      });

      $scope.$on(wizardEvents.BEFORE_SUBMIT, function() {
        console.info('About to submit!');
      });

      $scope.$on(imageEvents.VOLUME_CHANGED, function(event, newVolume) {
        console.info(newVolume);
      });

      ///////////////////////////

      $scope.$watchCollection(getFavoriteColor, watchFavoriteColor);

      function getFavoriteColor() {
        return ctrl.favoriteColor;
      }

      function watchFavoriteColor(newColor, oldColor) {
        if (newColor != oldColor) {
          $scope.$emit('mystep.favoriteColor', newColor);
        }
      }
    }

  })();

mystep.help.html
----------------

In this tutorial, we will leave this file blank. Include additional information
here if your step requires it. Otherwise, remove the file and the ``helpUrl``
property from your step.

mystep.html
-----------

This file contains contents you want to display to the user. We will provide a
simple example of a step that asks for your favorite color. The most important
thing to note here is the reference to our controller via the ``ng-controller``
directive. This is essentially the link to our controller.

.. code-block:: html

  <div ng-controller="horizon.app.core.images.steps.myStepController as ctrl">
    <h1 translate>Blue Plugin</h1>
    <div class="content">
      <div class="subtitle" translate>My custom step</div>
      <div translate style="margin-bottom:1em;">
        Place your custom content here!
      </div>
      <div class="selected-source clearfix">
        <div class="row">
          <div class="col-xs-12 col-sm-8">
            <div class="form-group required">
              <label class="control-label" translate>Favorite color</label>
              <input type="text" class="form-control"
                ng-model="ctrl.favoriteColor"
                placeholder="{$ 'Enter your favorite color'|translate $}">
            </div>
          </div>
        </div><!-- row -->
      </div><!-- clearfix -->
    </div><!-- content -->
  </div><!-- controller -->

Testing
-------

Now that we have completed our plugin, lets package it and test that it works.
If you need a refresher, take a look at the installation section in
:ref:`tutorials-plugin`.
