Framework - EGroupware/egroupware GitHub Wiki

Introduction

The EGroupware client-side framework provides a base in the browser for us to build applications on. Comprised of several parts, it provides a consistent user experience across applications, and reduces code repetition across applications.

jsapi

The jsapi provides many JavaScript utilities for interacting with EGroupware. Site configuration, cached data, user preferences, JSON, link system, utilities and more are accessed through the global, window, or application egw object.

EgwApp

A common base class for application JavaScript, each app should extend as needed. By inheriting from the base EgwApp, an application can easily participate in EGroupware features. Push updates, favourites, document merge and entry sharing all have default client-side implementations and overwriting as needed can customise the behaviour.

JavaScript in EGroupware apps

Etemplate

Our client-side rendering system, it is not part of the "framework". It uses .xet XML files to build the UI out of widgets.

Etemplate Docs

KDots

We use kdots to manage how applications are presented to users and to manage features provided by EGroupware as a whole, and to each application by EGroupware. It's two main pieces are webComponents: egw-framework which wraps the whole page, and an egw-app for each application

EGroupware Framework

Screenshot from 2025-09-17 14-24-22

egw-framework wraps everything and provides the header bar.

From left to right

  1. Site logo
  2. App chooser
  3. Open applications
  4. Work timer
  5. Dark mode
  6. Add entry
  7. Notifications
  8. User menu (preferences, access, password, logout)

Applications provide their information, but the framework controls all of this.

The framework also provides optional banner and footer slots where an administrator can add site-wide content by editing head.tpl:

<egw-framework ...>
	...
	<et2-hbox slot="banner" style=" width: 100%; background-color: var(--sl-color-warning-300);	padding: 0.5em;">
		Framework banner
		<span align="center">System maintenance tonight 20:00 - 23:00</span>
	</et2-hbox>
</egw-framework>
Screenshot from 2025-09-17 15-02-21

Application Framework

egw-app wraps the application and provides a consistent user interface across applications

Layout

Screenshot from 2025-11-14 13-49-51

Styling

When styling an application, use a LESS file named <appname>/templates/default/app.less. Styling details

/* Wrap your CSS to avoid style bleed */
egw-app[name="<appname>"] {
	/* Start with application-wide styles */
	.highPriority {
		/* Use CSS variables to match the rest of the system, and handle dark mode and theming automatically */
		color: var(--warning-color)
	}

	/* Template specific customisations */
	#<appname>-index {
		...
	}
	#<appname>-edit {
	}
}

It is acceptable and encouraged to break up your styles into multiple files for the usual reasons of clarity and maintainability.

egw-app[name="<appname>"] {
  @import "app-custom.less";
  @import "list.less";
  @import "edit.less";
}

Features

The framework application object provides access to several optional features with a common interface. These features are enabled when the application supports them. Only Categories can be fully customised, the others use a standard interface with data provided by the application.

Screenshot from 2025-11-14 14-53-02

Preferences

Implement the 'settings' hook and "Preferences" will show in the application menu

ACL Rights

Implement the 'acl_rights' hook and "Access" will show in the application menu.

Categories

Implement the 'categories' hook and "Categories" will show in the application menu. If you return a URL instead of true, you can customise the categories UI.

Favorites

Implement the 'sidebox_menu' hook, and send sidebox data with a title of 'Favorites'

// Magic favorites menu
$GLOBALS['egw']->framework->sidebox(static::APPNAME, lang('Favorites'), Api\Framework\Favorites::list_favorites(static::APPNAME));

Favorites will show in the sidebox and filter drawer.

Slots

The egw-app element has many slots where content can be added into different places. To add content to a particular slot, set the slot element on a top-level element.

<!-- This entire template will be slotted into the egw-app, to the left above the favorites -->
<template id="calendar.sidebox" slot="left-top">
	<calendar-date id="date" aria-labelledby="overviewHeader">
		<!-- 
		This widget is a child of the <calendar-date>, and will be be ignored by the framework.
		It will be slotted into its parent element.
		-->
		<et2-image name="edit" slot="prefix"></et2-image>
	</calendar-date>
	<et2-calendar-owner id="owner"...></et2-calendar-owner>
</template>

<!-- No slot attribute, so this goes in the main content area -->
<template id="addressbook.index">
	<!-- 
	TOP LEVEL widgets inside a <template> will be found and moved in the DOM if they have the slot attribute.
	This sub-template will be slotted into the egw-app's application header.
	-->
	<template template="addressbook.index.header" slot="main-header"></template>

	<!-- No slot attribute, so this stays in its template as normal -->
	<nextmatch id="nm" template="addressbook.index.rows" span="all"/>
</template>

Slot list

The main application content goes in the default slot by omitting the slot attribute. Most slots are hidden if they have no content.

  1. main-header - Top of app. Contains logo, application toolbar, search box etc.
  2. header-actions - Top right - filter, refresh, print & menu live here
  3. filter - Custom filter panel content, leave empty for auto-generated filters from nextmatch widget
  4. header - Top of main content
  5. footer - Very bottom of the main content.
  6. left - Optional content to the left. Use for application navigation.
  7. left-header - Top of left side
  8. left-top - Between left-header and Favourites
  9. left-footer - bottom of left side
  10. right - Optional content to the right. Use for application context details.
  11. right-header - Top of right side
  12. right-footer - bottom of right side

Filter drawer

Active filters

Screenshot from 2025-11-21 15-00-04

If there are any filters with non-empty values, we show that there are filters set. If you need to customise what counts as "clear", define getFilterInfo() in your app.ts:

	/**
	 * Since infolog always has filter2 set ("details" or "no-details"), ignore that for determining if there are filters set
	 *
	 */
	getFilterInfo(filterValues, fwApp : EgwFrameworkApp)
	{
		delete filterValues.filter2;

		// Use the EgwFrameworkApp's filterInfo() method to get the filter info
		return fwApp.filterInfo(filterValues);
	}

It is best to design your filters so they can be empty or have a value of "" and still be valid.

The contents of the filter drawer can come from 3 places:

Slotted content

Using the "filter" slot allows custom content, and overrides any automatically generated filter content. Calendar does this as calendar state spans multiple different views, not just a list of entries. This overrides any event handling, so doing something with the filters must also be added. EgwApp.changeNmFilter() is a good place to start, but by using custom slotted content you can do anything you need.

Any nextmatch you load should have its filter_template set to an empty string to avoid conflicts.

<template id="myapp.filter">
	<et2-searchbox id="search" label="Search" class="et2-label-fixed" onchange="app.myapp.changeNmFilter"></et2-searchbox>
	<!-- Other filters -->
</template>
<template id="myapp.index">
	<template template="myapp.filter" slot="filter"/>
	<nextmatch id="nm" template="myapp.index.rows" filter_template=""/>
</template>

Filter template

The nextmatch widget supports a filter_template attribute to load a customised template.

The given template will be loaded and slotted, along with event listeners to update the nextmatch's appliedFilters when values change. This allows custom contents while letting the framework deal with the events and updating the nextmatch.

<et2-template id="myapp.index.filter">
	<et2-searchbox id="search" label="Search" class="et2-label-fixed"/>
	<!-- Add sorting for accessibility, but hide normally -->
	<et2-visually-hidden>
            <et2-select id="sort[id]" label="Sorting" ariaLabel="Ordering" class="et2-label-fixed">
                <option value="id">Id</option>
                <option value="start">Start</option>
                <option value="title">Title</option>
            </et2-select>
            <et2-button-toggle ariaLabel="Sorting" id="sort[asc]" onIcon="carret-down-fill" offIcon="carret-up-fill"/>
        </et2-visually-hidden>
        <et2-select id="filter" label="Filter" class="et2-label-fixed"/>
        <et2-select id="filter2" label="Category" class="et2-label-fixed"/>
        <et2-details summary="Column Filters" id="col_filters" open="true">
		<!-- ... -->
        </et2-details>
</et2-template>
<template id="myapp.index">
	<nextmatch id="nm" template="myapp.index.rows" filter_template="myapp.index.filter"/>
</template>

Automatic

Leaving nextmatch's filter_template unset is the easiest. EGroupware will automatically generate and slot filters from the row template's column headers.

<et2-template id="myapp.index.rows">
	<!-- ... -->
	<!-- Plain or sort headers are ignored for filtering -->
	<et2-nextmatch-header id="id" label="ID"/>
	<et2-nextmatch-sort-header id="start" label="Start date"/>
	<!-- Most other headers can filter -->
	<et2-nextmatch-header-filter ariaLabel="Type" id="tid" emptyLabel="All types"/>
	<!-- ... -->
</et2-template>
<template id="myapp.index">
	<nextmatch id="nm" template="myapp.index.rows"/>
</template>
⚠️ **GitHub.com Fallback** ⚠️