Skip to content

CodeCoupler UI Application

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 elements. Every 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, the container is different:

  • If a stage starts an inline application, the container ist the new stage that this instance has created.
  • If an application starts an inline application, the container is the content area of the application.
  • If a widget starts an inline application, the container is the widget container (not the element).
  • If the system starts a stage application, the container can be the current stage or the system container.

Applications started inside the system container, a widget container or inside of another application will started by default normalized and with a titlebar. Stage 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 and this will not affect only stage applications.

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.

Start an Application

You cannot initialize an Application class directly. An application can be started over the application factory method app(appStart) which is available in all components.

A system have more than one method to start an application. The default method app(appStart) is just a shortcut to start an application in the current stage, using the app method of the associated stage instance. Furthermore the method waits for the initialization of the system before the application is started. This method should be generally used to start an application in the current stage. The method is accessible from all components with this.env.system.app(appStart).

If you do not need to wait for the initialization of the system you can use another method of a system forceApp(appStart). This should be used carefully and will be explained later.

Another method of a system to start systemApp(appStart) which starts the application inwith the system container and not the current stage.

Using the factory method in the following situations is problematic:

  • 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.
  • 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.
  • Calling await this.app(appStart) inside the boot method of a system instance will not work because the method will wait for the system initialization and you will wait for the application initialization. Everyone is waiting and nothing happens. You can use await this.forceApp(appStart) if really needed.
  • You should avoid calling this.app(appStart) inside the init and start methods of a system. In case that after the start method a stage initialization would throw an error because the current stage is not empty anymore.

The application start definition object appStart used as argument is optional. If you omit this just a generic appilcation will be started. The start definition object must be an object and can have the following properties:

id: String (Optional)

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 every component to access all applications of the component.

app: Application (Optional)

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

modal: Boolean (Optional)

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

options: Any (Optional)

The options for the application.

panel: Object (Optional)

Configuratin 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 (Optional)

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

splash: Object | Boolean (Optional)

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 (Optional)

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

on: Object (Optional)

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.

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.

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 [./system] for furher informations.

Implement an Application

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
import { Application } from "@codecoupler/cc-ui";
export default class extends Application {
  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
  }
}

The methods you can provide (all are optional) are splash, init and start.

Initialization and Starting Process

The application will perform the following steps after it has started:

  1. Start splash screen for the init phase.
  2. Call init method and wait for the panel configuration object.
  3. Create panel and block it.
  4. Close first splash screen and start splash screen for the start phase.
  5. Call start method and wait for the result.
  6. Close second splash screen and unblock panel.

An application is considered initialized once the start method has been successfully executed.

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 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:

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

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: [https://jspanel.de/?#global/overview].

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.

Some modificatons to the default panel was implemented:

The panel content area is styled with overflow: hidden; isolation: isolate;. The content area is not intended to be filled with content. At most, a rigid structure should be built here, otherwise content should only be provided via widgets. The most simple way ist to initialize one widget in the container `this.panel.content.

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.

The UI Representation

An application class can provide a static field ui which contains properties of how the application should be presented. This is used in for displaying the app in titles, launching buttons or other UI elements. Because these informations are needed before the application is created it is a static property.

For now only name and iconHtml is supported:

1
2
3
4
{
  name: "The Name of the App",
  iconHtml: "<i>Something</i>"
}
name: String

The name of the application.

iconHtml: String

The html element should inherit the font-size. If you use FontAwesome, you should always add the fa-fw class.

Because an application developer maybe do not specify all properties in its application you cannot just read the static property ui to get all informations. All applications have a static function $ui() which will merge the values of all parent classes. To get the ui informations of an application use ApplcationClass.$ui() instead ApplicationClass.ui.

The Splash Screen

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 documentation except for 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

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

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: (Mandatory) String | Array | Function

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)

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

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:

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

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).

Reference

In addition to the common instance members described in [./component.md] 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.

Every 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
```text
app = Instance of an application component

app.close() +-> app.closeForce() --> app.panel.close() +-> app.destroy()
  |         |                        or click on close |         |
  V         |                        handle in header  |         V
Confirm     |                                |         |   Execute app.destroy()
Data Loss   |                                V         |   Asynchronous and wait
if changes -+                        Interrupt closing |         |
detected                             the panel        -+         V
                                                          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.

Further Topics

Styling

TODO

  • Content has "overflow: hidden; isolation: isolate"

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>