Framework - EGroupware/egroupware GitHub Wiki
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.
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.
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.
Our client-side rendering system, it is not part of the "framework". It uses .xet XML files to build the UI out of widgets.
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
egw-framework wraps everything and provides the header bar.
From left to right
- Site logo
- App chooser
- Open applications
- Work timer
- Dark mode
- Add entry
- Notifications
- 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>
egw-app wraps the application and provides a consistent user interface across applications
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";
}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.
Implement the 'settings' hook and "Preferences" will show in the application menu
Implement the 'acl_rights' hook and "Access" will show in the application menu.
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.
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.
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>The main application content goes in the default slot by omitting the slot attribute. Most slots are hidden if they have no content.
- main-header - Top of app. Contains logo, application toolbar, search box etc.
- header-actions - Top right - filter, refresh, print & menu live here
- filter - Custom filter panel content, leave empty for auto-generated filters from nextmatch widget
- header - Top of main content
- footer - Very bottom of the main content.
- left - Optional content to the left. Use for application navigation.
- left-header - Top of left side
- left-top - Between left-header and Favourites
- left-footer - bottom of left side
- right - Optional content to the right. Use for application context details.
- right-header - Top of right side
- right-footer - bottom of right side
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:
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>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>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>