close

Filter

loading table of contents...

Studio Developer Manual / Version 2404

Table Of Contents

9.36.5.2 Built-In Services And Utilities

In the previous section, the basics of the Service Agent API were covered. This section covers the built-in services and related utility that are ready to use in custom apps. In addition, the services that are already offered by the Content App and the Workflow App are covered.

Framework Services

Several service types and their descriptors are already defined in the Studio Apps framework. The following services are defined in the @coremedia/studio-client.app-context-models module and can be used as top-level imports.

  • StudioAppService: This service represents a Studio App itself. For each app with a proper manifest according to the guidelines from Section Section 9.36.4, “App Manifest and Apps Menu Entries”, a StudioAppService is automatically registered where the most important feature is the app's descriptor with properties of type StudioAppServiceProps. They mirror the contents of the manifest and provide this information at run-time.
  • RouterService: This service allows to set the sub-URL/path of an app. As described in Section Section 9.36.4, “App Manifest and Apps Menu Entries”, setting up a RouterService is mandatory for each app that offers URL shortcuts in its manifest. Otherwise the RunAppEntry#run() methods returned from studioApps._.observeRunAppSections() do not work. For the RouterService only the interface is provided by the framework. A sub-path is set via the method RouterService#setPath(). How this path is actually manifested in the app is up to the app's implementation. For example, the My-Edited-Contents App uses a HashRouter and the useNavigate() hook is used for the implementation of the RouterService#setPath().
  • AutoLogoutService: In the CoreMedia Studio, the user is automatically logged out after a certain period of inactivity. For a smooth user experience it is important that all apps log out jointly. For this purpose, a custom app needs to register its own AutoLogoutService. Note that the service does not do the actual log-out itself. Instead, it is responsible for tracking user inactivity and communicate this with the other AutoLogoutServices of other apps. So typically, the service is embedded in some form of a wider login context. For example, in the My-Edited-Contents App, the service is set up as follows:
    import {
      AutoLogoutService,
      createAutoLogoutService,
      createAutoLogoutServiceDescriptor
    } from "@coremedia/studio-client.app-context-models";
    import InputActivityTracker
      from "@coremedia/studio-client.app-context-models/activitytracker/InputActivityTracker";
    import FetchActivityTracker
      from "@coremedia/studio-client.app-context-models/activitytracker/FetchActivityTracker";
    ...
    
    async #setupAutoLogoutService(): Promise<void> {
      this.#autoLogoutService = createAutoLogoutService({
        autoLogoutDelay: 1800000,
        activityTrackers: [
          new InputActivityTracker(document.body),
          new FetchActivityTracker(),
        ],
        serviceAgent: getServiceAgent(),
      });
      getServiceAgent().registerService(
        this.#autoLogoutService,
        createAutoLogoutServiceDescriptor()
      );
      this.#autoLogoutSubscription = this.#autoLogoutService
        .observe_status()
        .subscribe(async (status) => {
          if (status === "loggedOut" || status === "autoLoggedOut") {
            await this.#doLogout();
            await this.tearDownAutoLogoutService();
          }
        });
    }
    The service is created with a delay of 30 minutes and two activity trackers, then registered with the Service Agent and finally an observer for the service's status is set up. If the status switches to "loggedOut" or "autoLoggedOut", then the actual logout is performed via doLogout() which is not covered here and is not part of the AutoLogoutService. In addition, when the user explicitly logs out in the current app for example via a button then AutoLogoutService#forceLogout() must be called to trigger the logout.

    Just as all apps should log out jointly, logging into one app should also reload the other apps that are currently opened in a browser window but do not have a valid session. The above mentioned StudioAppService offers a method for that. In the My-Edited-Contents App, this code is executed upon login:

    const appServices = getServiceAgent().getServices(
      createStudioAppServiceDesc(), {state: "running"}
    ).filter(handler => handler.descriptor.provider !== getServiceAgent().getOrigin());
    appServices.forEach(
      async (appServiceHandler) =>
        (await appServiceHandler.fetch()).reloadWithoutSession()
    );

Studio Apps Utilities

As already mentioned in Section Section 9.36.3, “Accessing the Studio Apps Context”, the studioApps utility is also available as a top-level import of the @coremedia/studio-client.app-context-models module. It provides the following utility functions:

  • initAppServices(): This method has already been covered in Section Section 9.36.4, “App Manifest and Apps Menu Entries”.
  • observeRunSections(): This method has already been covered in Section Section 9.36.4, “App Manifest and Apps Menu Entries”.
  • getShortcutRunnerRegistry(): This method has already been covered in Section Section 9.36.4, “App Manifest and Apps Menu Entries”.
  • runApp(): This method takes a ServiceDescriptorWithProps<StudioAppService, StudioAppServiceProps> argument to run the app that this descriptor denotes. If the app is already running in a browser window, it is brought into the foreground.
  • getMyAppDescriptor(): Returns the ServiceDescriptorWithProps<StudioAppService, StudioAppServiceProps> of the current app. It mirrors the contents of the app's manifest.
  • focusMe(): Focuses the current app and brings its browser window / tab into the foreground. This method is typically called inside service methods where an action is triggered that must be immediately visible to the user, for example ContentFormService#openContentForm().
  • getAppStartupParameters(): Sometimes an app is not just run in a browser window but also with additional parameters. Currently, this is only the case when the app is run via a service runner for one of the app's cmServices from their manifest, see Section Section 9.36.4, “App Manifest and Apps Menu Entries”. The startup parameters are then available via this method. In this case, it provides the app with the information which service was initially requested to trigger the app's start-up. An example to use this method is given in Section Section 9.36.6, “Multi-Instance Apps”.
  • setWindowOpenHandler(): Apps are opened in browser windows / tabs. This method allows to override the handler that is called when an app is run or brought into the foreground. In the future this might be a lever to allow for more advanced PWA features. For now, it is mainly to deal with browser security restrictions. A popup blocker might prevent the focussing of an app's window / tab. This goes unchecked by the default window handler of the Studio Apps framework. But the module @coremedia/studio-client.app-context-models provides a handler that display a warning for that. It is currently used by all built-in apps and also by the My-Edited-Contents App.
    import { openOrFocusApp, studioApps }
      from "@coremedia/studio-client.app-context-models";
    
    studioApps._.setWindowOpenHandler(openOrFocusApp);
    
  • addWindowValidityObservable(): This is another tool to deal with browser security restrictions. For app windows to be able to interact properly with each other (especially to focus each other), they need to be in the same so-called "window group" of the browser. This is only the case, if all apps were started beginning with one app and further apps are always opened from one of the already running ones. Once a browser window is opened isolated from this chain and an app run in it, this app is not connected to the other apps. This method allows to add an Observable to track whether the current app window is a connected one. By default, no such Observable is in place but the module @coremedia/studio-client.app-context-models provides one. It is currently used by all built-in apps and also for the My-Edited-Contents App.
    import { observeWindowStudioAppsConnection, studioApps }
      from "@coremedia/studio-client.app-context-models";
    
          studioApps._.addWindowValidityObservable(
            observeWindowStudioAppsConnection()
          );
    
    In addition, the My-Edited-Contents App also reacts if the window connection cannot be established.
    studioApps._.observeWindowValidity().subscribe((valid) => {
      if (!valid) {
        alert("This browser tab has no connection to the other Studio browser tabs. Please close it and continue in another Studio browser tab.");
      }
    });

Data Transfer Services

The module @coremedia/studio-client.interaction-services-api contains some base services for data transfers between apps.

  • DragDropService: The built-in CoreMedia Studio Apps, namely the Content App and the Workflow App, offer to drag content items between apps and thus between browser windows. For this, HTML5 drag/drop is used (also cf. Section Section 9.14, “HTML5 Drag And Drop”). In order to allow such drag operations to result in a drop in a custom app, it is sufficient to just register a "drop" event handler. However, the DragDropService also offers information about the dragged items during the drag operation and not only once the drop happens. The service is only running during a drag operation and unregistered once this is finished.

    The property DragDropService#dataTransferItems mirrors the drag data of an HTML5 drag / drop event (DragEvent.dataTransfer.items). No matter whether the service data is used or the data from the drop event, they have the same structure. For example, for a typical drag operation of content items, the drag data looks like this:

    {
      "cm-studio-rest/uri-list": "[\"content/3350\",\"content/3356\",\"content/3370\",\"content/3360\"]",
      "cm-content/uuid-list": "[\"14e56f5c-170a-43d6-8381-a9230f202040\",\"ca06b1ca-6d62-4040-a72b-3ff15b1f9dc8\",\"b5b54648-268b-41cb-91cd-1e1c805ae172\",\"66c1b819-1142-4480-9faa-0ed4d50a370c\"]",
      "cm-member/uuid-list": "[]",
    }

    Here it is visible that different flavors / types of drag data exist. The first two types are just different representations of the same content items. But it is visible that also member items (users and groups) might be dragged. The currently supported types are enumerated in DataTransferTypes of the module @coremedia/studio-client.interaction-services-api.

    The following shows the drop handler of the My-Edited-Contents App to receive content items via a drag drop operation from the Content App.

    onDrop={event => {
      const uriListData = event.dataTransfer.getData(DataTransferTypes.CM_STUDIO_REST_URI_LIST);
      if (!uriListData) {
        event.preventDefault();
        return;
      }
      const parsedUriList = JSON.parse(uriListData);
      if (!parsedUriList || !Array.isArray(parsedUriList)) {
        event.preventDefault();
        return;
      }
      const contentUriRestTemplate = new URITemplate(ContentImpl.REST_RESOURCE_URI_TEMPLATE);
      const contents = parsedUriList.filter((uri) =>
        contentUriRestTemplate.matches(uri)).map(beanFactory._.getRemoteBean);
      contents && contents.length > 0
        && session._.getConnection().getCapListRepository()
        .getEditedContents().addItems(contents);
    }}
    

    While custom apps can receive drops of content items from the built-in apps, there currently exists no further support to set up a drag operation from a custom app back to the built-in apps. However, it is possible if HTML5 drag / drop is used and the data is set up according to the above structure.

  • Clipboard: Similar to drag / drop, the clipboard allows to transfer content or member items between apps. The Clipboard of the module @coremedia/studio-client.interaction-services-api implements the Clipboard Web API and offers methods for reading and writing clipboard data. To access the clipboard, the global constant clipboard from @coremedia/studio-client.interaction-services-api can be used. For it to be initialized, the app needs an import of @coremedia/studio-client.interaction-services-impl/init somewhere.

    The clipboard reads and writes data in the form of ClipboardItems. These items support different flavors / types, analogous to the drag / drop data from above. For example, the My-Edited-Contents App has a global key handler to paste from the clipboard.

    useEffect(() => {
      const onPaste = async (ev: KeyboardEvent) => {
        if (ev.key === "v" && (ev.ctrlKey || ev.metaKey)) {
          const clipboardItems = await clipboard._.read();
          const clipboardItem = clipboardItems.find((clipboardItem)
            => clipboardItem.types.includes(DataTransferTypes.CM_STUDIO_REST_URI_LIST));
          if (!clipboardItem) {
            return;
          }
          const restUrisItem = await clipboardItem
            .getType(DataTransferTypes.CM_STUDIO_REST_URI_LIST);
          const restUrisItemString = restUrisItem === "string"
            ? restUrisItem
            : await (restUrisItem as Blob).text();
          try {
            const restUris: Array<string> = JSON.parse(restUrisItemString);
            if (restUris) {
              const contents = restUris.map(beanFactory._
                .getRemoteBean).filter((bean) => is(bean, Content));
    
              contents
                && contents.length > 0
                && session._.getConnection().getCapListRepository()
                  .getEditedContents().addItems(contents);
            }
          } catch (e) {
            // ignore
          }
        }
      };
    
      window.addEventListener("keyup", onPaste, false);
    
      return () => {
        window.removeEventListener("keyup", onPaste, false);
      };
    }, []);
    

    The My-Edited-Contents App also has a similar global key handler to copy into the clipboard, using the selection of the edited contents list.

    const [selection, setSelection] = useState<GridSelectionModel>([]);
    
    ...
    
    useEffect(() => {
      if (!selection || selection.length === 0) {
        return;
      }
      const onCopy = async (ev: KeyboardEvent) => {
    
        if (ev.key === "c" && (ev.ctrlKey || ev.metaKey)) {
    
          try {
            const data: Record<string, any> = {};
            const remoteBeans = selection
              .map(selected => beanFactory._.getRemoteBean(selected.toString()))
              .filter(Boolean);
    
            if (!remoteBeans.every(RemoteBeanUtil.isAccessible)) {
              return;
            }
    
            const restUriBlob = new Blob(
              [
                JSON.stringify(remoteBeans.map((remoteBean) => remoteBean.getUriPath())),
              ],
              {type: DataTransferTypes.CM_STUDIO_REST_URI_LIST},
            );
            data[restUriBlob.type] = restUriBlob;
    
            await clipboard._.write([new ClipboardItemImpl(data)]);
    
          } catch (e) {
            // ignore
          }
        }
      };
    
      window.addEventListener("keyup", onCopy, false);
    
      return () => {
        window.removeEventListener("keyup", onCopy, false);
      };
    }, [selection]);
    

Feature Services Of Content And Workflow App

The Content App and the Workflow App offer some feature services out of the box. All of them are also listed under the cmServices property of the app manifests, so there exist automatic service runners for them that launch the apps if needed, cf. Section Section 9.36.4, “App Manifest and Apps Menu Entries”.

  • ContentFormService: This service and its descriptor factory are exported by the module @coremedia/studio-client.content-service-api. The service is offered by the Content App and allows to open content items in form tabs, track which content items are currently opened and which content item is the active one.
  • ProjectFormService: This service and its descriptor factory are exported by the module @coremedia/studio-client.project-services-api. The service is offered by the Content App and allows to open project items in form tabs, track which project items are currently opened and which project item is the active one.
  • CollectionViewService: This service and its descriptor factory are exported by the module @coremedia/studio-client.collection-view-services-api. The service is offered by the Content App and allows to display content items in the collection view (library) and to open the collection view in a specific content search state.
  • WorkflowFormService: This service and its descriptor factory are exported by the module @coremedia/studio-client.workflow-services-api. The service is offered by the Workflow App and allows to open workflow objects (processes and tasks) in forms, track which workflow objects are currently opened and which workflow object is the active one. In the case of the Workflow App, there is at most one workflow object opened but the API was intentionally kept similar to the above-mentioned form service APIs.

Search Results

Table Of Contents
warning

Your Internet Explorer is no longer supported.

Please use Mozilla Firefox, Google Chrome, or Microsoft Edge.