Durchsuchbare Dokumentation aufrufen | Zurück zur Dokumentationsübersicht

Navigation: Dokumentationen agorum core > agorum core für Entwickler > agorum core cards


Cards für Workflows (Beispiel Freigabeworkflows)

Diese Dokumentation beschreibt, wie Workflow-basierte Cards (Cardlets) für die Verwendung auf Mobilgeräten optimiert werden können. Am Beispiel des Freigabeworkflows agorum.workflow.releaseXEyes wird gezeigt, wie Benutzer direkt über die Objektkarte (z. B. im Eingang (aktuell)) interagieren können, ohne in die komplexe Workflow-Oberfläche wechseln zu müssen.

Mobile Workflows bzw. Workflow-Schritte

Typisches Szenario

Ein typischer Anwendungsfall für das Bearbeiten eines Workflow-Schritts sind Freigabeworkflows, etwa für:

Problem: Das verantwortliche Management ist oft unterwegs und muss Dokumente freigeben, hat aber nur ein Smartphone zur Verfügung. Die Standard-Workflow-Oberfläche beinhaltet Bearbeitungsaufgaben, die auf Mobilgeräten nur schwer oder gar nicht durchgeführt werden können.

Lösung: Durch Workflow-Cards können Freigaben direkt über die Objektkarte durchgeführt werden:

Wann macht die Optimierung für die mobile Verwendung Sinn?

Die Verwendung von Cards für Workflow-Schritte ist geeignet für:

Die Verwendung von Cards für Workflow-Schritte ist nicht geeignet für:

Hinweis: In Cards werden keine Eingabefelder direkt eingebettet. Benutzereingaben erfolgen stattdessen über Message-Popups (Dialoge).

Workflow-Integration

Die folgenden Schritte erläutern die Integration eines Workflows in eine Card anhand des Freigabeworkflows agorum.workflow.releaseXEyes.

  1. Decorator prüft Workflow und Schritt und entscheidet, ob das Cardlet eingeblendet wird.
  2. Build-Funktion lädt Workflow-Variablen und zeigt sie in der Card an.
  3. Buttons lösen Aktionen aus (Freigeben, Ablehnen, Zurück zum Ersteller).
  4. Optional/abhängig von der Konfiguration: Zuweisung übernehmen (Acquire), bevor Aktionen verfügbar sind.
  5. Kommentar-Popup öffnet sich, Kommentar wird als Workflow-Variable übergeben, anschließend wird der Workflow fortgesetzt.

Schritt 1: Decorator erstellen

Der Decorator entscheidet, wann die Workflow-Card angezeigt wird.  Im Beispiel wird geprüft, ob das Objekt zu einem Token des Workflows agorum.workflow.releaseXEyes gehört und der aktuelle Schritt uiReleaseStep ist.

let decorators = require('/agorum/roi/customers/agorum.cards/js/decorators');
let metadata = require('common/metadata');

/**
 * @type {decorators.Decorator}
 */
module.exports = (cardlet, object) =>
  decorators.with(cardlet, 'content', content => {
    let data = metadata().load(object, 'sys_acw_processName', 'sys_acw_stepName').data();

    if (data.sys_acw_processName !== 'agorum.workflow.releaseXEyes') return;
    if (data.sys_acw_stepName !== 'uiReleaseStep') return;

    // Add the Release X-Eyes Cardlet to content
    content.items = (content.items || []).concat([
      {
        type: 'agorum.workflow.releasexeyes.releaseXEyes',
        id: object.UUID,
      },
    ]);
  });

 

Wichtige Metadaten:

Schritt 2: Cardlet erstellen

Das Cardlet definiert die Darstellung und Interaktion. Es lädt die Token-Variablen per workflow.get(tokenId) und rendert diese als Display-Gruppe. Zusätzlich werden je nach Zustand Buttons angezeigt.

/* global sc */

let _ = require('common/i18n').translate;
let aguila = require('common/aguila');

let cards = require('/agorum/roi/customers/agorum.cards/js/cards');
let workflow = require('/agorum/roi/customers/agorum.dev/js/lib/workflow');
let message = require('/agorum/roi/customers/agorum.composite/js/lib/message');

// TODO: remove after updating to 11.13.0+
let ui;

try {
  ui = require('/agorum/roi/customers/acworkflow/js/common/ui');
} catch (ignored) {
  (() => {})(ignored);
}

if (!ui || !ui.hasAcquired) {
  ui = {
    hasAcquired: variables => variables.sys_acw_assignee === sc.loginUserUuid,
    mayAcquire: () => false,
  };
}

/**
 * Helper function: Removes HTML tags from a string
 *
 * @param {string} html
 */
let stripHtml = html => (html ? html.replace(/<[^>]*>/g, '').trim() : '');

/**
 * Helper function: Shows an input popup
 *
 * @param {string} title
 * @param {(comment: string) => void} fn
 */
let showCommentPopup = (title, fn) =>
  aguila.enter(() =>
    message
      .popup(title)
      .text(_('agorum.workflow.releasexeyes.popup.comment.text'))
      .element({
        type: 'agorum.composite.form.element.text',
        name: 'comment',
        label: _('agorum.workflow.releasexeyes.popup.comment.label'),
        textArea: true,
        validation: [
          {
            required: true,
          },
        ],
      })
      .focus('comment')
      .ok()
      .cancel()
      .show((buttonName, value) => {
        if (buttonName === 'ok') {
          fn(value.comment);
        }
      })
  );

/**
 * Build function: Creates the cardlet structure
 *
 * @type {cards.Builder}
 */
exports.build = (cx, def) => {
  cx.meta = {
    id: def.id,
  };

  let variables = workflow.get(def.id);

  // Create display items
  let displayItems = [
    { label: _('agorum.workflow.releasexeyes.label.step'), value: variables.step || '' },
    { label: _('agorum.workflow.releasexeyes.label.stepText'), value: variables.stepText || '' },
    { label: _('agorum.workflow.releasexeyes.label.releaseText'), html: stripHtml(variables.releaseText || '') },
  ];

  // Add escalation date only if available
  if (variables.doneBy) {
    displayItems.push({
      label: _('agorum.workflow.releasexeyes.label.escalationDate'),
      value: new Date(variables.doneBy),
      precision: 'minute',
    });
  }

  let buttons;
  let acquired = ui.hasAcquired(variables);

  if (acquired) {
    // show actions if acquired by this user
    buttons = [
      {
        type: 'agorum.button',
        name: 'approve',
        text: _('agorum.workflow.releasexeyes.button.approve'),
        color: 'success',
      },
      {
        type: 'agorum.button',
        name: 'reject',
        text: _('agorum.workflow.releasexeyes.button.reject'),
        color: 'warning',
      },
      {
        type: 'agorum.vertical',
        flex: 1,
      },
      {
        type: 'agorum.button',
        name: 'backToCreator',
        text: _('agorum.workflow.releasexeyes.button.backToCreator'),
        color: 'medium',
      },
    ];
  } else if (ui.mayAcquire(variables)) {
    // show acquisition buttons, if allowed
    let force = acquired === false;

    buttons = [
      {
        type: 'agorum.button',
        name: force ? 'acquireForce' : 'acquire',
        text: _('agorum.workflow.releasexeyes.button.acquire' + (force ? '.force' : '')),
      },
    ];
  } else {
    buttons = [];
  }

  return {
    type: 'agorum.vertical',
    items: [
      {
        type: 'agorum.display.group',
        labelWidth: 150,
        items: displayItems,
      },
      {
        type: 'agorum.horizontal',
        name: 'actions',
        items: buttons,
      },
    ],
  };
};

let event = (exports.event = cards.eventHandler());

// Event handlers for "Acquire" buttons
event.path('actions', 'acquire').on('elementClicked', cx => workflow.acquire(cx.meta.id));
event.path('actions', 'acquireForce').on('elementClicked', cx => workflow.acquire(cx.meta.id, true));

// Event handler for "Back to Creator" button
event.path('actions', 'backToCreator').on('elementClicked', cx =>
  showCommentPopup(_('agorum.workflow.releasexeyes.popup.backToCreator.title'), comment =>
    aguila
      .fork(
        workflow.leave(cx.meta.id, 'backToCreator', {
          editorComment: comment,
        })
      )
      .then(
        message.alert(
          _('agorum.workflow.releasexeyes.message.success.title'),
          _('agorum.workflow.releasexeyes.message.backToCreator.success')
        )
      )
  )
);

// Event handler for "Approve" button
event.path('actions', 'approve').on('elementClicked', cx =>
  showCommentPopup(_('agorum.workflow.releasexeyes.popup.approve.title'), comment =>
    aguila
      .fork(() =>
        workflow.leave(cx.meta.id, 'released', {
          editorComment: comment,
        })
      )
      .then(() =>
        message.alert(
          _('agorum.workflow.releasexeyes.message.success.title'),
          _('agorum.workflow.releasexeyes.message.approve.success')
        )
      )
  )
);

// Event handler for "Reject" button
event.path('actions', 'reject').on('elementClicked', cx =>
  showCommentPopup(_('agorum.workflow.releasexeyes.popup.reject.title'), comment =>
    aguila
      .fork(() =>
        workflow.leave(cx.meta.id, 'notReleased', {
          editorComment: comment,
        })
      )
      .then(() =>
        message.alert(
          _('agorum.workflow.releasexeyes.message.success.title'),
          _('agorum.workflow.releasexeyes.message.reject.success')
        )
      )
  )
);

 

Wichtige Konzepte:

Schritt 3: Kommentar-Popup erstellen

Wie im Beispiel zu Schritt 2 gezeigt, wird ein Message-Popup für Benutzereingaben verwendet. Beim Erstellen empfiehlt es sich, die mögliche Verwendung auf mobilen Geräten zu berücksichtigen: 

Schritt 4: Event-Handler implementieren

Event-Handler verarbeiten Button-Klicks und steuern den Workflow. Relevante Aufrufe:

Zusätzlich wird in UI-Kontexten häufig aguila.enter() verwendet, um UI-Operationen thread-sicher auszuführen. In der Referenzimplementierung ist aguila.enter() bereits im Popup-Helper gekapselt.