Adaptive Component: AC-Stage

This Adaptive Component provides a user interface component that shows an arbitrary number of items of a list of items either horizontally or vertically. The shown item or items can be changed by either a swipe gesture or an API call. In case the requirements are not met by the client, a vertical list of all items is created as a fall-back showing all items.

1 Enabling

Use the register-ac action to enable the AC-Stage in your site.

<flow>
  <register-ac name="ac-stage"/>
  <default-request/>
  <parse/>
</flow>

The ac-stage AC creates the following DC properties:

Property Description Value Type Possible Values
ac/ac-stage/mode The mode for the stage: stage (for proper stage behaviour) or rollout (for the fallback vertical list). String stage, rollout
ac/ac-stage/gestures Whether gestures can be used to handle the stage. Boolean
ac/ac-stage/effects/slide Whether the stage uses a slide effect for switching elements. Boolean
ac/ac-stage/css/preferTranslate2d Whether the effect should use a 2d transformation instead of a 3d, which can cause problems if the stage includes certain form elements. Boolean

Effects:

The stage will switch elements using a slide effect, as long as the client is not known to have problems animating the effect. If the animation is problematic, the slide effect defaults to a simple swap effect without animation.

2 Syntax

2.1 The ac-stage element

Contexts in which this element can be used:
Where flow content is expected (see the definition of flow content in the HTML5 specification).
Content model:
Zero or more ac-stage-element elements.
Content attributes:
start (optional)
infinite (optional)
sizing (optional)
slots (optional)
slot-orientation (optional)
snap-to-current (optional)
preload-count (optional)
HTML5 global attributes

The ac-stage element represents the stage on which an item is shown. In the client DOM this is represented by a div element.

The optional start attribute sets the position (starting with 0) of the first (top/left) item to be shown initially. Valid values are integers >= 0 and < the number of elements on the stage. The default is 0.

The optional infinite attribute marks the stage to be “infinite”: the first element is preceded by the last, the last is followed by the first. Valid values are true and false. The default is true.

The optional sizing attribute configures how the height of the stage element is determined. Valid values are none and auto. When the none value is used, the stage element uses the height set via CSS, regardless of the height of the displayed elements. With the auto value, the stage element changes its size according to the displayed elements. The default is none.

The optional slots attribute configures how many elements can be displayed at the same time. Valid values are integers greater than 0. Regardless of the current element count the width of each is recalculated as a percentage of the stage. When the slots value is greater than the number of currently displayable elements, the stage elements are aligned to the left. The default is 1.

The optional slot-orientation attribute configures in which direction elements are aligned and gestures to use the stage are interpreted. Valid values are horizontal and vertical. The default is horizontal.

The optional snap-to-current attribute configures the snapping behaviour for effects that support this different kind of snapping, like the slide effect. If enabled, the elements will snap to a position indicated by the user gesture allowing it to override the default snapping behavior that is used when for example the next or prev functions are called. Valid values are true and false. The default is false.

By setting the optional preload-count attribute, you may configure to preload images with visibility prioritization in more than one stage-element on each side of the visible slots. The default is 1.

Note: Currently the stage needs a height declared via CSS if sizing="auto" is not used. Example:
<style>
#myStage {
  height: 5em;
}
</style>
...
<ac-stage id="myStage">
  <ac-stage-element>...</ac-stage-element>
  ...
</ac-stage>

2.2 The ac-stage-element element

Contexts in which this element can be used:
As a child of an ac-stage element.
Content model:
Flow content (see the definition of flow content in the HTML5 specification).
Content attributes:
HTML5 global attributes

The ac-stage-element element represents an item to be shown on the stage. In the client DOM this is represented by a div element.

3 JavaScript API

Note: The JavaScript API is only provided for non-rollout stages.

3.1 Function ac.stage.stage

The ac.stage.stage function provides an ac.stage.Stage object for the given id.

Note: ac.stage.stage(...) must not be called before the stageinit event is fired. If debugging is enabled, any premature calls result in a thrown Error.

Signature:

ac.stage.stage(id)

Arguments:

id
{string} The id attribute value of the ac-stage element.

Returns {ac.stage.Stage} The Stage object, or false if the given id is undefined, empty or does not identify an ac-stage element. Throws an error, in case of any invocations before the stageinit event is fired if debugging is enabled.

3.2 Function ac.stage.destroy

The ac.stage.destroy function destroys an {ac.stage.Stage} object so another stage with the same ID can be initialized. This is handled automatically by FIT when using Partial Page Loading.

If you are using custom AJAX functionality, you should always destroy stages being removed or replaced. Otherwise new stages with the same ID as previously existing stages cannot be initialized.

Signature:

ac.stage.destroy(id)

Arguments:

id
{string} The id attribute value of the ac-stage element.

Returns nothing.

3.3 The ac.stage.Stage object

This object provides an interface to the stage.

3.3.1 Method addEventListener

Adds an event listener.

Signature:

addEventListener(eventType, listener)

Parameters:

eventType
{string} The event type.
listener
{function} The listener.

Listeners may be registered for the following event types: stageposchanged.

3.3.2 Method removeEventListener

Removes an event listener.

Signature:

removeEventListener(eventType, listener)

Parameters:

eventType
{string} The event type.
listener
{function} The listener.

Listeners may be removed for the following event types: stageposchanged.

3.3.3 Method prev

Shows the previous item.

Signature:

prev()

Returns {ac.stage.Stage} The Stage object.

Fires a stageposchanged if the position was changed.

3.3.4 Method next

Shows the next item.

Signature:

next()

Returns {ac.stage.Stage} The Stage object.

Fires a stageposchanged event if the position was changed.

3.3.5 Method show

Shows the item at the given position if the given position is a number and in the valid range (< 0 or >= number of elements on the stage).

Signature:

show(pos)

Arguments:

pos
{number} The position of the item to be shown (starting with 0).

Returns {ac.stage.Stage} The Stage object.

Fires a stageposchanged event if the position was changed.

3.3.6 Method addElement

Adds a new element with the given content at the given position.

Signature:

addElement(content[, pos])

Arguments:

content
{Element|Text|Comment|DocumentFragment} The DOM node to fill the new element to be added.
[pos]
{number} The position at which to add the new element (optional: default is append at the end).

Returns {number} The position at which the new element was added, or -1 if the given position is not a number or not in the valid range (< 0 or > number of elements on the stage) or the type of the given content is not one of Element, Text, Comment, DocumentFragment.

3.3.7 Method removeElement

Removes an element at the given position.

Signature:

removeElement(pos)

Arguments:

pos
{number} The position of the element to remove.

Returns {DocumentFragment} The content of the removed element, or null if the given position is not a number or not in the valid range (< 0 or >= number of elements on the stage) or the stage is empty.

3.3.8 Method setSlots

Changes the number of displayable elements. The operation is ignored if the given slot number is not an integer number or < 1.

Signature:

setSlots(slots)

Arguments:

slots
{number} The integer number of simultaneously displayable elements.

Returns {ac.stage.Stage} The Stage object.

3.3.9 Method getPos

Returns the index of the element on the first (top/left) position.

Signature:

getPos()

Returns {number} The index of the element on the first (top/left) position.

3.3.10 Method getPositions

Returns the index of each displayed element in order of appearance.

Signature:

getPositions()

Returns {number[]} Array containing the currently displayed element indices, in order.

3.3.11 Method setSnapToCurrent

Defines whether the current stage will, when moved with a pointer, snap to the currently visible elements or to the next elements as if called with prev() or next().

Signature:

setSnapToCurrent(enable)

Arguments:

enable
{boolean} Whether snap-to-current shall be enabled for this stage or not.

Returns {ac.stage.Stage} The Stage object.

3.3.12 Property queue

The stage needs a queue, if calls to next, prev, show or setSlots are made during an ongoing transition effect.

In that case, subsequent calls of the named methods are stored in a queue, which offers the following methods:

3.3.12.1 Method isEmpty

Will return true if the queue is empty, false otherwise.

Signature:

queue.isEmpty()

Returns {boolean} Whether the queue contains any elements.

3.3.12.2 Method emptyQueue

Will remove all elements in the queue.

Signature:

queue.emptyQueue()

3.3.13 Method autosize

Resizes current and all nested stages to the height of the highest contained visible element.

Signature:

autosize()

Returns {ac.stage.Stage} The Stage object.

3.4 Events

Listeners can be registered for the following events:

3.4.1 The stageinit event

This event is fired when all stages have been initialized. The parameter supplied for listeners is of type ac.stage.StageInitEvent.

Note: If <ai-document-fragment> elements are present in the document, the stageinit event is fired once for every <ai-document-fragment> element containing stages, with all the infos for the stages contained in this fragment. No stageinit events will be generated for stages outside of <ai-document-fragment> elements, because they won’t be sent to the client.

Properties:

type
{string} The event type (‘stageinit’).
stageInitInfos
{<a href="AC-Stage.html#obj-StageInitInfo">ac.stage.StageInitInfo[]`} Array containing StageInitInfo objects.

ac.stage.StageInitInfo objects have the following properties:

stage
{ac.stage.Stage} The stage object.
elementCount
{number} The integer number of ac-stage-element elements.
id
{string} The id of the ac-stage element.
pos
{number} The initial (top/left) position.
positions
{number[]} The initial positions.

Listeners for this event must be registered using the JavaScript Events API.

Example:

ai.addEventListener('stageinit', function(ev) {
  console.log(ev);
});

Note: This event type is registered with the clearOnPPLRequest option enabled, thus all event listeners will be cleared when a PPL request is started.

3.4.2 The stageposchanged event

This event is fired when when the stage position is changed. The parameter supplied for listeners is of type ac.stage.StagePosChangedEvent.

Properties:

id
{string} The id of the ac-stage element.
newPos
{number} The new (top/left) position.
oldPos
{number} The old (top/left) position.
newPositions
{number[]} The new positions.
oldPositions
{number[]} The old positions.
type
{string} The event type (‘stageposchanged’)

Listeners for this event must be registered using the addEventListener method of the ac.stage.Stage object.

Example:

...
function _stagePosChangedListener(ev) {
  console.log(ev);
}
function _stageInitListener(ev) {
  ev.stageInitInfos.forEach(function(info) {
    /* register a listener for stageposchanged on every initialized stage */
    info.stage.addEventListener('stageposchanged', _stagePosChangedListener);
  });
  ai.removeEventListener('stageinit', _stageInitListener);
}
ai.addEventListener('stageinit', _stageInitListener);
...

4 Example:

For the ai-if comment to work, make sure to enable text filtering in config.xml.

<!DOCTYPE html>
<html>
  <head>
...
    <style>
.stage {
  width: 90%;
  /* Do not restrict the height in rollout mode! */
/* ai-if="ac/ac-stage/mode != 'rollout'"*/
  height: 18em;
/* ai-endif */
  border: thin solid #ddd;
  margin: 0 auto;
}
    </style>
...
  </head>
  <body>
    <ac-stage id="stage1" class="stage" style="background: #fee" start="1" infinite="true">
      <ac-stage-element id="stage1_0" class="stelement1">
        <p><img src="bee.jpg" alt="My little bee" ...></p>
      </ac-stage-element>
      <ac-stage-element id="stage1_1" class="stelement2">
        <p>second element</p>
      </ac-stage-element>
      <ac-stage-element>
        <p>third element</p>
      </ac-stage-element>
    </ac-stage>
  </body>
</html>

The following JavaScript code shows an example of how event listeners can be used to dynamically add a button panel with ‘prev’ and ‘next’ buttons and a simple indicator for the stage.

(function() {

  /** * Inserts the given new node after the given reference node. * * @param {Node} newChild The new child node. * @param {Node} refChild The reference child node. */
  function _insertAfter(newChild, refChild) {
    var
      /* @type Node */
      parent = refChild.parentNode
    /* @type Node */
    , ns = refChild.nextSibling
    ;
    if (ns) {
      parent.insertBefore(newChild, ns);
    } else {
      parent.appendChild(newChild);
    }
  }

  /** * Creates a button panel (p element) with prev and next buttons for the stage with the given id. * * @param {ac.stage.Stage} stage The stage. * @return {Element} The button panel element. */
  function _createButtonPanel(stage) {
    var
      /* @type Element */
      p = document.createElement('p')
    ;
    /** * Registers a listener for an event on a target. * * @param {EventTarget} eventTarget The event target. * @param {string} type The event type. * @param {EventListener|Function} listener The listener. * @param {boolean} useCapture true if the user wishes to initiate capture, false otherwise. */
    function _addEventListener(eventTarget, type, listener, useCapture) {
      var
        /* @type string */
        toae = ''
      ;
      if (typeof eventTarget.addEventListener === 'function') {
        eventTarget.addEventListener(type, listener, useCapture);
      } else if ((toae = typeof eventTarget.attachEvent) === 'object' || toae === 'function') {
        eventTarget.attachEvent('on' + type, listener);
      }
    }
    /** * Creates a button with label and click command. * * @param {string} label The button label. * @param {function} command The command to execute on click. * @return {Element} The button element. */
    function _createButton(label, command) {
      var
        /* @type Element */
        button = document.createElement('button')
      ;
      button.setAttribute('type', 'button');
      button.appendChild(document.createTextNode(label));
      _addEventListener(button, 'click', command, true);
      return button;
    }
    p.appendChild(_createButton('previous', function() {stage.prev();}));
    p.appendChild(document.createTextNode(' '));
    p.appendChild(_createButton('next', function() {stage.next();}));
    return p;
  }

  /** * Creates an indicator (fieldset element) with the given label for the stage with the given id and length. * * @param {string} label The indicator label. * @param {string} stageId The stage id. * @param {number} l The number of elements on the stage. * @return {Element} The indicator. */
  function _createIndicator(label, stageId, l) {
    var
      /* @type Element */
      fieldset = document.createElement('fieldset')
    /* @type Element */
    , legend = document.createElement('legend')
    /* @type number */
    , i = 0
    /* @type Element */
    , radio = null
    ;
    legend.appendChild(document.createTextNode(label));
    fieldset.appendChild(legend);
    for (i = 0; i < l; i++) {
      radio = document.createElement('input');
      radio.type = 'radio';
      radio.name = 'indicator_' + stageId;
      radio.id = 'indicator_' + stageId + '_' + i;
      radio.disabled = true;
      if (i > 0) {
        fieldset.appendChild(document.createTextNode(' '));
      }
      fieldset.appendChild(radio);
    }
    return fieldset;
  }

  /** * Adds listeners. * * @param {Event} ev The event fired when this function is called. */
  function _addListeners(ev) {
    /** * Listener for the stageposchanged event: changes the indicator position. * * @param {ac.stage.StagePosChangedEvent} data The stageposchanged event data. */
    function _stagePosChangedListener(data) {
      var
        /* @type Element */
        radio = document.getElementById('indicator_' + data.id + '_' + data.newPos)
      ;
      if (console) {
        console.log(data);
      }
      if (radio) {
        radio.checked = true;
      }
    }
    /** * Adds a button panel and an indicator. * * @param {ac.stage.StageInitInfo} data The stage init information. */
    function _createButtonsAndIndicator(data) {
      var
        /* @type ac.stage.Stage */
        stage = data.stage
      /* @type Element */
      , p = _createButtonPanel(stage)
      /* @type Element */
      , stageEl = document.getElementById(data.id)
      /* @type Element */
      , radio = null
      ;
      _insertAfter(p, stageEl);
      _insertAfter(_createIndicator('indicator', data.id, data.elementCount), p);
      if (radio = document.getElementById('indicator_' + data.id + '_' + data.pos)) {
        radio.checked = true;
      }
      stage.addEventListener('stageposchanged', _stagePosChangedListener);
    }
    /** * Listener for the stageinit event. * * @param {ac.stage.StageInitEvent} data The stageinit event data. */
    function _stageInitListener(data) {
      if (console) {
        console.log(data);
      }
      data.stageInitInfos.forEach(_createButtonsAndIndicator)
      ai.removeEventListener('stageinit', _stageInitListener);
    }
    if (window.ac && ac.stage && !_listenersAdded) {
      if (ai.dc.getProperty('ac/ac-stage/mode') !== 'rollout') {
        ai.addEventListener('stageinit', _stageInitListener);
      }
      _listenersAdded = true;
      ai.removeEventListener('ready', _addListeners);
    }
  }
  var
    _listenersAdded = false
  ;
  ai.addEventListener('ready', _addListeners);
})();

5 Limitations

  • AC-Stage in AC-Scroll may not work properly in Mobile Internet Explorers.
  • AC-Scroll in AC-Stage may not work properly in Mobile Internet Explorers.