Skip to content

CodeCoupler UI Application

Overview

An application is a class derived from the CodeCoupler base class Application. An application is a container that, in addition to a content area, optionally also has a title bar, a footer or other framing elements. Each component can start an application, even an application itself.

If not maximized the application can be resized and dragged within a certain container. Depending on what component started the application.

An application will trap the focus inside the focusable elements of the application. If there is no focusable element in the application panel, the panel itself gets focusable and receive the focus.

An application provides after initialization a stage in which different widgets can be started. Alternatively, a stage can be subdivided into smaller stages before widgets are started.

Start an Application

Instantiate

You cannot initialize an Application class directly. An application can be started over different application factory methods which are available in all components.

Component Method Description Forced Header
Stageable (System, Application) app(appStart) The Application will be started in current stage of the component as soon as the component has finished initializing. In fact this is will call the `app()` method of the current stage instance. No
forceApp(appStart) The Application will be started in current stage of the system like in `app()` but immediatley, not waiting the initialization finishing. This should be used carefully. No
rootApp(appStart) The Application will be started in the root stage of the component (System: Container, Application: Conten Area). Yes
Stage app(appStart) The Application will be started in the *new* stage, provided by the instance. Only if not the last stage
stageApp(appStart) The Application will be started in the container of the instance. In a chain of stages this is in fact the previous stage. Yes
Widget app(appStart) The Application will be started in the container of the widget. Yes

Applications started with "Forced Header" will started by default normalized and with a titlebar. In all other cases applications will not have explicit any default values for titlebar and start status. With this you can define in the system wide settings to open all stage applications maximized and without a titlebar.

Using the factory method inside the initialization methods init, boot or start is problematic. Basically you should start an application during initialization inside of in start. In a stageable component you should furthermore never use await. Read below more details:

Component Possible Problems
Stageable (System, Application) Calling `await this.app(appStart)` inside the `init`, `boot` or `start` method of a stageable component will not work because the method will wait for the component initialization and you will wait for the application initialization. Everyone is waiting and nothing happens. You should use `this.app(appStart)` (without await) which will start the app after the system initialization. Or you can use `this.forceApp(appStart)` (very careful and only if really needed).
System You should avoid starting an application with `this.forceApp(appStart)` inside the `init` or `boot` method of a system. A stage may be started after the `boot` method, which would lead to an error because the current stage is not empty anymore.
Application Calling `this.app(appStart)` inside the `init` method of an application instance will throw an exception because the application panel is not ready yet. If needed you should use the method inside the `start` method.
Stage Calling `this.app(appStart)` inside the `init` method of an stage instance will throw an exception because the new stage is not ready yet. If needed you should use the method inside the `start` method. Basically you should always pay attention if you start an application inside a stage instance. In case that after this stage an additional stage will be initialized would throw an error because the new stage you created is not empty anymore.

Error Handling

If any error occurs the initializing process will stop and an error will be thrown. The application panel will be blocked with a message. If the panel was not created yet, an error hint will be showed.

The error message can be modified in the system wide setting messages.APP.ERROR_INIT.

Note: In case that the error occurs during the system initialization, the parent System will catch the and handle this error. Please read the chapter "Error Handling" in the documentation of System for furher informations.

Start Definition Object

The application start definition object appStart is optional. If you omit this a generic appilcation will be started. If specified it have to be an object or an Application class.

Setting appStart to Application class is just a shortcut for { app: ApplicationClass }.

If appStart is an object it can have the following properties:

id [ String ] (Default: Random String)

If you define an id, no other application with the same id can be started. However, this is only valid in relation to the starting component. Each system, widget, stage or application can start and use their own id's.

If you specify an id which already exists, the method app() will return the running app.

Instead of using the method app() to get an already running application instance, you can use the getter apps which provide each component to access all applications of the component.

component [ CodeCoupler Application ] (Default: undefined)

The CodeCoupler application class to start. If you omit this just a generic appilcation will be started.

modal [ Boolean ] (Default: undefined)

If set to true, the application will be started modal.

options [ Object ] (Default: undefined)

The options for the application.

panel [ Object ] (Default: undefined)

Configuration for the jsPanel creation, norally returned by the init method of the application. The values here override most of the values defined in the system wide settings and the values returned by the init method of the application. More details will be explained later here.

ui [ Object ] (Default: undefined)

Options that should override the application default value defined in the static ui property, for representing the app in the UI.

splash [ Object | Boolean ] (Default: undefined)

The both splash screen while initializing and while starting the application can be disabled setting the following properties to false:

1
2
3
4
{
  init: false,
  start: false
}

To disable both you can also set splash directly to false.

settings [ Object ] (Default: undefined)

Override here the system wide settings for this application and all child components.

► Read more about system wide settings

on [ Object ] (Default: undefined)

Early bind callbacks to events. In some cases the application fires events within the initialization phase. Instead of using instance.on("eventname",callback) after initialization (what would be too late) you can define here callbacks which will be early binded to be able to react to this events.

Define just an object with the eventnames as properties and callback functions as values:

1
2
3
on: {
  eventname: callback
}
container [ Internally used ]

This property cannot be set and will be used and internally. container is the parent HTMLElement of the panel.

Implement an Application

Basics

To implement a new application you have to define a which extends the CodeCoupler class Application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { Application } from "@codecoupler/cc-ui";
export default class extends Application {
  //All members here are optional
  static ui = {
    //Define here representation in UI
  };
  async splash(mode) {
    //Define Splash Screens here
  }
  async init() {
    //Init process here
    //Return always an application configuration object
    return config;
  }
  async start() {
    //Finalize process here
    //this.panel is now ready and can be used
  }
}

Initialization and Starting Process

flowchart TB

  subgraph S1 [ ]
    S1A[Start `init` Splash Screen]
  end

  S1A --> A
  A[Execute `init` Method] --> S2A
  style A color:#000,fill:#ffc929,stroke:#333,stroke-width:4px

  subgraph S2 [ ]
    S2A[Create Panel] --> S2AA
    S2AA[Block Panel] --> S2B
    S2B[Close `init` Splash Screen] --> S2C
    S2C[Start `start` Splash Screen]
  end

  S2C --> G
  G[Execute `start` Method] --> S3A
  style G color:#000,fill:#ffc929,stroke:#333,stroke-width:4px

  subgraph S3 [ ]
    S3A[Close `start` Splash Screen] --> S3B
    S3B[Unblock Panel]
  end

  S3B --> I
  I[Application is initialized]

DOM Layout

An application creates an element which is basically structured like this:

┌───────────────────────────────────────────────────────────────────┐
│  Header                                                           │
│  Accesible over this.panel.header                                 │
├───────────────────────────────────────────────────────────────────┤
│                                                                   │
│  Content                                                          │
│  Accesible over this.panel.content                                │
│                                                                   │
│  position: absolute                                               │
│  isolation: isolate                                               │
│  overflow: hidden                                                 │
│                                                                   │
├───────────────────────────────────────────────────────────────────┤
│  Footer                                                           │
│  Accesible over this.panel.footer                                 │
└───────────────────────────────────────────────────────────────────┘

Differences to the original library jsPanel

In contrast to the origin jsPanel library which provide the panel control, some changes were made to the styling:

The content area have stage styles and is not scrollable.

The height of the titlebar will be controlled via font-size. Set the desired font size directly on the panel or in the sub-element with the class .jsPanel-headerbar.

All font sizes in all panel elements will be inherited from the parent DOM settings. Changing the font size in the document should change the font-size in all elements of the panel normally.

What's next

You can acces the panel through the variable this.panel. But changes to this element should only be made with great care.

An Application is not intended to provide the user with an interface or to make changes to it. An Application is normally filled via stages and widgets.

Implementation Details

The Panel Configuration Object

The init method have to return a panel configuration object which will be be used to create the application panel. Basically you can use almost all options for creating a jsPanel with some modifications explained here.

Default Values and Precedence

The default values of all options will be taken from the system wide settings defined in the property panel. The return value of the init method can override these option values, but the start definition object can in turn overwrite those from the init function.

This generally rule does not apply to all options. In the case of the array based callback options, the return values of the init function have priority. Specifically, these are the following options:

Array based callback options that can be used

onbeforeminimize, onbeforemaximize, onbeforenormalize, onbeforesmallify, onbeforeunsmallify, onfronted, onminimized, onmaximized, onnormalized, onsmallified, onunsmallified, onstatuschange, onclosed

Array based callback options will be merged and their functions executed in the following order:

  1. Callback defined in the return value of the init method.
  2. Callback defined in the start defintion object.
  3. Callback defined in the system wide settings.

Any of these callbacks can return false to prevent executing any following callback.

The idea behind of this order, is that an application developer should use these functions to implement relevant logic and leave all other options mostly untouched. All the other options mainly concern the appearance of the application. And this should be left to the user of your application.

Special handled Options

The following options can not be set or will be handled in a special way:

setStatus

You can use the shortcut fullsize here, which will set the setStatus to maximized and header to false.

dragit

Will be set to false if setStatus is set to maximized and header to false.

resizeit

Will be set to false if setStatus is set to maximized and header to false. If this is not the case and the merged value is not false, all callbacks resizeit.resize will be merged into an array (start definition object, return value of the init method and system wide settings).

onbeforeclose

Cannot be used. Use instead the component method async destroy().

callback, minimizeTo, container, syncMargins, onwindowresize and
id

Cannot be used!

headerTitle

Will be set to the name of the application unless otherwise defined. The name will be taken from the ui property of the application or the starting definition object.

footerToolbar

If you specify the property footerToolbar as a string, or a function that returns a string you can use shortcuts to create predifined buttons. Read more about this below in "The Footer Toolbar Buttons".

The Panel Object

In the start method you have access to the panel via this.panel. This is basically a jsPanel object and you can use all the functions provided by the library jsPanel.

Over this.panel you have access to the library methods however, it is also the DOM element at the same time. With this.$panel you can get this DOM element as a jQuery object.

In contrast to the origin jsPanel library the panel content area is styled among other with overflow: hidden. The content area is not intended to be filled with content. Content should only be provided via widgets and stages. The most simple way is to initialize one widget in the container with this.widget() and implement the interface in the widget.

Splash Screens

The splash screen will be started twice. The first splash screen starts centered in the container of the application (the current stage for example). The second splash screen starts centered in the application panel and at the same time blocks the panel.

If an inline application starts a splash screen for the init phase while the parent application shows also a splash screen, the splash screen of the parent application will temporarly hidden. It will unhidden as soon as the inline application close its splash screen.

You can modify or disable the splash screens with the method async splash(mode, options). The mode argument can have the values init and start and indicates which of the two screens should be started. The options argument contain the option with which the application was started.

The method have to return an object which is basically just a config object of jsPanel hint extension. You can set all options from the documentation except the following:

1
2
3
4
5
6
7
{
  autoclose: false,
  container: container_of_application,
  position: "center center",
  contentSize: "auto auto",
  header: false
}

The most simple splash modification is to return an object with one property content which only modify the content of the splash screen. The property callback may also be helpful if you want to change the content after it has been generated.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
async splash(mode, options) {
  return {
    content: '<h1>My Splash</h1>',
    callback: (panel) => {
      //Modify content via panel.content
      //If you modify here the content which manipulates the size
      //consider to call "panel.resize(); panel.reposition();"
      //afterwards.
    }
  };
}

To disable a splash screen return false:

1
2
3
async splash(mode, options) {
  return false;
}

A splash screen works as a "sticky blocking layer". Any other blocking layer will not overlay a splash screen.

If you disable the start splash screen the application will be blocked using the message from the system wide setting messages.APP.LOADING. This blocking works as any other "normal blocking layer". Any other blocking will be placed over this.

Default Buttons

The string in footerToolbar of the panel configuration returned by the init method can contain one of the following placeholders which will be replaced by buttons:

[refresh]

Calling refreshDataUI(). The defult text of the button is defined as a function and retrieve the value from system wide setting messages.BUTTONS.REFRESH.

[cancel]

Call cancelDataUI() and close the application on success. The defult text of the button is defined as a function and retrieve the value from system wide setting messages.BUTTONS.CANCEL.

[reset]

Like [cancel] without closing the application. The defult text of the button is defined as a function and retrieve the value from system wide setting messages.BUTTONS.RESET.

[saveclose]

Call saveDataUI() and close the application on success. The defult text of the button is defined as a function and retrieve the value from system wide setting messages.BUTTONS.SAVECLOSE.

[save]

Like [saveclose] without closing the application. The defult text of the button is defined as a function and retrieve the value from system wide setting messages.BUTTONS.SAVE.

[close]

A button which will close the panel. IMPORTANT: Button will just close the panel! There is no check if data is modified!

The defult text of the button is defined as a function and retrieve the value from system wide setting messages.BUTTONS.CLOSE.

[data]

An alias for [refresh][cancel][reset][save][saveclose]

Button Configuration

You can define own button shortcuts or modify the existing in the system wide setting buttons. This is an object with two properties shortcuts and templates. Over this.settings you modify only the settings which only affect this component.

The buttons that can be used are defined as properties of the object shortcuts. Use as property only the name without the bracets. Example: The button that can be used in the footer toolbar as [refresh] is defined in the system wide settings under buttons.shortcuts.refresh.

1
2
3
4
5
6
7
8
9
//Example how to define a new button "create":
this.settings.buttons.shortcuts.create = {
  class: "success",
  html: '<i class="fas fa-plus mr-1"></i> {0}',
  text: "Add",
  click: () => {
    //Implement you logic
  },
};

If you want to reuse default functions but want to add some functionality to them you can "extend" them by the following way:

1
2
3
4
5
//Example how to "extend" the default function of the "save" button
this.settings.buttons.shortcuts.save.click = () => {
  //Implement your logic. Then call the function of the parent settings:
  this.env.parent.settings.buttons.shortcuts.save.click.call(this, this);
};

A button definition have to be an object with the following properties:

class [ String | Function ] (Default: undefined)

Specify here one of the Bootstrap color theme tokens primary, success, warning, danger etc. The default button template use this string as suffix to btn- for a class name that will be assigned to the button.

The property can be a function that returns the value that have to be used. The function will be executed in the context of the application instance and additionally the instance will be used as first argument.

html [ String | Function ] (Default: undefined)

The inner HTML of the button. The string can contain placeholders like {0}, {1} etc. The string of html will be merged with text like in printf functions. If you have defined more than one placeholder you can use an array in text. You can also omit html completely.

The property can be a function that returns the value that have to be used. The function will be executed in the context of the application instance and additionally the instance will be used as first argument.

text [ String | Array | Function ] (Mandatory)

You have to specify text or replace to make the shortcut working. If you specify both only text is taken into account.

If you have specified html this will be the replacement of the placeholders defined in html. In this case it can be an array to define multiple text replacements.

If you did not defined html you can specify here a simple string that will be used as label for the button.

The property can be a function that returns the value that have to be used. The function will be executed in the context of the application instance and additionally the instance will be used as first argument.

click [ async Function(component) ] (Default: undefined)

Specify an async function to call on button click. The function must be async and the application will be blocked while the function is executing. You can disable the blocking with the property block.

The function will be executed in the context of the application instance and additionally the instance will be used as first argument.

replace [ String ] (Default: undefined)

You have to specify text or replace to make the shortcut working. If you specify both only text is taken into account.

If you specify this property the button shortcut will just replaced with with string. The string can contain itself button shortcuts that will be interpreted.

This is usefull to define a group of buttons like in the data shortcut:

1
2
3
4
5
6
{
  data:
    {
      replace: "[refresh][cancel][reset][save][saveclose]"
    }
}
template [ String ] (Default: "default")

You can define multiple templates which produce from a button configuration a button. If you do not specify a template default will be used. See next chapter for more details.

block: [ String | False ] (Default: undefined)

If you set this to false the application will not be blocked while executing the function defined in click.

Otherwise you can define here the message that will be shown while blocking the application.

If you do not specify block the message from the system wide settings messages.BUTTONS.BLOCK will be used.

Button Templates

You can define own templates or modify the existing in the system wide setting buttons.templates.

The default template that is included is named default and looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  templates: {
    default:
      `<button
        type="button"
        title="{3}"
        class="btn btn-{0} btn-sm my-1 mx-2 text-nowrap text-truncate jsPanel-ftr-btn"
        data-role="{1}">{2}</button>`
  }
}

As you can see a template is a simple string with the placeholders {0} to {2} which will be used as follows:

{0}

The string defined in class.

{1}

The name of the button shortcut which have to be used at least in a data-role attribute of an element that is clickable. Based on this attribute the click handler will be attached to the element.

{2}

The content for the button build from html and text. The text will be HTML escaped, even all elements of text if it is an array.

{3}

HTML escaped text. If text is an array all escaped elements joined with a comma.

Some remarks:

  • The class jsPanel-ftr-btn should be used on a button to avoid acting as drag handle.
  • The buttons shortcuts will be replaced without any additional spaces around them. Keep this in mind and define eventual margins (like in the default template with my-1 mx-2).

Inline Applications of Stages

If you use inline applications in stages you have to pay attention that minimized panels will be attached to the bottom of the stage element. In some cases this could match with the bottom of the current stage. In the bottom area of the current stage will be attached the minimized system applications. The minimized stage applications would thus overlap the minimized system applications.

To avoid this you could define in your stage an explicit area in where the panels should be minimized and make sure that this area do not overlap the bottom of the current stage.

1
2
3
4
5
6
7
┌───────────────────────────┐
│ Stage Element             │
│ ┌─────────────────────────┤
│ │ Current Stage           │
│ │                         │
│ │                         │ <- Both Elements share the same bottom border and minimized
└─┴─────────────────────────┘   applications of stage and system would overlap.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
┌───────────────────────────┐
│ Stage Element             │
│ ┌─────────────────────────┤
│ │ Current Stage           │
│ │                         │
│ │                         │
│ └─────────────────────────┤
┝┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤
│                           │ <- Add an div into your stage to define the target area
└───────────────────────────┘    for minimized stage applications.

This newly defined area must not have an height defined. If no application is minized the area will collapse and will not be visible.

To define this area you have to insert just a <div> with the following attributes:

1
<div class="position-relative jsPanel-minimized-container"></div>

Reference

In addition to the common instance members described in the base classes Component and Stageable an application has the following:

Instance Events

fronted, unfronted

Will be fired if the panel is set in front of other application panels. In contrast to the "onfronted" event of the panel it will only fired once. The "onfronted" event of the panel will be fired everytime you click inside the panel for example, even if it was allready in front.

As argument the event will have the application instance.

The first "fronted" event will be fired inside of the initialization phase. To catch this first event you have to specify your callback in the start definition object in the property on.

Each component can have only one application which is fronted. If one application gets fronted all the other applications of this component fire the event unfronted.

Instance Methods

close(), closeForce()

Calls the destroy method and closes the panel. These methods are in contrast to the destroy method not asynchronous.

The close method will ask the user if the method hasChanges do not return false if he really want to close the application and loose all changes. The message came from the system wide setting messages.APP.CONFIRM_CLOSE.

The application component provide the following ways to close an application:

app = Instance of an application component

app.close() ┌─► app.closeForce() ──► app.panel.close() ┌─► app.destroy()
  │         │                        or click on close │         │
  ▼         │                        handle in header  │         ▼
Confirm     │                                │         │   Execute app.destroy()
Data Loss   │                                ▼         │   Asynchronous and wait
if changes ─┘                        Interrupt closing │         │
detected                             the panel        ─┘         ▼
                                                          Trigger "destroyed"
                                                          no matter if destroy()
                                                          success or fail

To detect if an application have been closed you should watch for the event "destroyed". Another method would be to use the "onclosed" callback of the application panel, but this is not the preffered method.

Instance Getter

panel

Returns the current panel of the application.

$panel

Returns the current panel of the application as a jsPanel object.

fronted

Returns true if panel is currently fronted, otherwise false.