User Interface Mixin
User Interface Mixin¶
The User Interface mixin class provides a set of methods to implement custom functionality for the InvenTree web interface.
Enable User Interface Mixin¶
To enable user interface plugins, the global setting ENABLE_PLUGINS_INTERFACE
must be enabled, in the plugin settings.
Plugin Context¶
When rendering certain content in the user interface, the rendering functions are passed a context
object which contains information about the current page being rendered. The type of the context
object is defined in the PluginContext
file:
Plugin Context
import {
MantineColorScheme,
MantineTheme,
useMantineColorScheme,
useMantineTheme
} from '@mantine/core';
import { AxiosInstance } from 'axios';
import { useMemo } from 'react';
import { NavigateFunction, useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { useLocalState } from '../../states/LocalState';
import {
SettingsStateProps,
useGlobalSettingsState,
useUserSettingsState
} from '../../states/SettingsState';
import { UserStateProps, useUserState } from '../../states/UserState';
/**
* A set of properties which are passed to a plugin,
* for rendering an element in the user interface.
*
* @param api - The Axios API instance (see ../states/ApiState.tsx)
* @param user - The current user instance (see ../states/UserState.tsx)
* @param userSettings - The current user settings (see ../states/SettingsState.tsx)
* @param globalSettings - The global settings (see ../states/SettingsState.tsx)
* @param navigate - The navigation function (see react-router-dom)
* @param theme - The current Mantine theme
* @param colorScheme - The current Mantine color scheme (e.g. 'light' / 'dark')
*/
export type InvenTreeContext = {
api: AxiosInstance;
user: UserStateProps;
userSettings: SettingsStateProps;
globalSettings: SettingsStateProps;
host: string;
navigate: NavigateFunction;
theme: MantineTheme;
colorScheme: MantineColorScheme;
};
export const useInvenTreeContext = () => {
const host = useLocalState((s) => s.host);
const navigate = useNavigate();
const user = useUserState();
const { colorScheme } = useMantineColorScheme();
const theme = useMantineTheme();
const globalSettings = useGlobalSettingsState();
const userSettings = useUserSettingsState();
const contextData = useMemo<InvenTreeContext>(() => {
return {
user: user,
host: host,
api: api,
navigate: navigate,
globalSettings: globalSettings,
userSettings: userSettings,
theme: theme,
colorScheme: colorScheme
};
}, [
user,
host,
api,
navigate,
globalSettings,
userSettings,
theme,
colorScheme
]);
return contextData;
};
Custom Panels¶
Many of the pages in the InvenTree web interface are built using a series of "panels" which are displayed on the page. Custom panels can be added to these pages, by implementing the get_ui_panels
method:
Return a list of custom panels to be injected into the UI.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
instance_type |
str
|
The type of object being viewed (e.g. 'part') |
required |
instance_id |
int
|
The ID of the object being viewed (e.g. 123) |
required |
request |
Request
|
HTTPRequest object (including user information) |
required |
Returns:
Name | Type | Description |
---|---|---|
list |
list[CustomPanel]
|
A list of custom panels to be injected into the UI |
- The returned list should contain a dict for each custom panel to be injected into the UI:
-
The following keys can be specified: { 'name': 'panel_name', # The name of the panel (required, must be unique) 'label': 'Panel Title', # The title of the panel (required, human readable) 'icon': 'icon-name', # Icon name (optional, must be a valid icon identifier) 'content': '
Panel content
', # HTML content to be rendered in the panel (optional) 'context': {'key': 'value'}, # Context data to be passed to the front-end rendering function (optional) 'source': 'static/plugin/panel.js', # Path to a JavaScript file to be loaded (optional) } -
Either 'source' or 'content' must be provided
Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
|
The custom panels can display content which is generated either on the server side, or on the client side (see below).
Server Side Rendering¶
The panel content can be generated on the server side, by returning a 'content' attribute in the response. This 'content' attribute is expected to be raw HTML, and is rendered directly into the page. This is particularly useful for displaying static content.
Server-side rendering is simple to implement, and can make use of the powerful Django templating system.
Refer to the sample plugin for an example of how to implement server side rendering for custom panels.
Advantages:
- Simple to implement
- Can use Django templates to render content
- Has access to the full InvenTree database, and content not available on the client side (via the API)
Disadvantages:
- Content is rendered on the server side, and cannot be updated without a page refresh
- Content is not interactive
Client Side Rendering¶
The panel content can also be generated on the client side, by returning a 'source' attribute in the response. This 'source' attribute is expected to be a URL which points to a JavaScript file which will be loaded by the client.
Refer to the sample plugin for an example of how to implement client side rendering for custom panels.
Panel Render Function¶
The JavaScript file must implement a renderPanel
function, which is called by the client when the panel is rendered. This function is passed two parameters:
target
: The HTML element which the panel content should be rendered intocontext
: A dictionary of context data which can be used to render the panel content
Example
export function renderPanel(target, context) {
target.innerHTML = "<h1>Hello, world!</h1>";
}
Panel Visibility Function¶
The JavaScript file can also implement a isPanelHidden
function, which is called by the client to determine if the panel is displayed. This function is passed a single parameter, context - which is the same as the context data passed to the renderPanel
function.
The isPanelHidden
function should return a boolean value, which determines if the panel is displayed or not, based on the context data.
If the isPanelHidden
function is not implemented, the panel will be displayed by default.
Example
export function isPanelHidden(context) {
// Only visible for active parts
return context.model == 'part' && context.instance?.active;
}
Custom UI Functions¶
User interface plugins can also provide additional user interface functions. These functions can be provided via the get_ui_features
method:
Return a list of custom features to be injected into the UI.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
feature_type |
FeatureType
|
The type of feature being requested |
required |
context |
dict
|
Additional context data provided by the UI |
required |
request |
Request
|
HTTPRequest object (including user information) |
required |
Returns:
Name | Type | Description |
---|---|---|
list |
list[UIFeature]
|
A list of custom UIFeature dicts to be injected into the UI |
Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
|
Return a list of custom features to be injected into the UI.
Source code in src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
|
Currently the following functions can be extended:
Template editors¶
The template_editor
feature type can be used to provide custom template editors.
Example:
sample_template.js
export function getTemplateEditor({ featureContext, pluginContext }) {
const { ref } = featureContext;
console.log("Template editor feature was called with", featureContext, pluginContext);
const t = document.createElement("textarea");
t.id = 'sample-template-editor-textarea';
t.rows = 25;
t.cols = 60;
featureContext.registerHandlers({
setCode: (code) => {
t.value = code;
},
getCode: () => {
return t.value;
}
});
ref.innerHTML = "";
ref.appendChild(t);
}
export function getTemplatePreview({ featureContext, pluginContext }) {
const { ref } = featureContext;
console.log("Template preview feature was called with", featureContext, pluginContext);
featureContext.registerHandlers({
updatePreview: (...args) => {
console.log("updatePreview", args);
}
});
ref.innerHTML = "<h1>Hello world</h1>";
}
Template previews¶
The template_preview
feature type can be used to provide custom template previews. For an example see:
Example:
sample_template.js
export function getTemplateEditor({ featureContext, pluginContext }) {
const { ref } = featureContext;
console.log("Template editor feature was called with", featureContext, pluginContext);
const t = document.createElement("textarea");
t.id = 'sample-template-editor-textarea';
t.rows = 25;
t.cols = 60;
featureContext.registerHandlers({
setCode: (code) => {
t.value = code;
},
getCode: () => {
return t.value;
}
});
ref.innerHTML = "";
ref.appendChild(t);
}
export function getTemplatePreview({ featureContext, pluginContext }) {
const { ref } = featureContext;
console.log("Template preview feature was called with", featureContext, pluginContext);
featureContext.registerHandlers({
updatePreview: (...args) => {
console.log("updatePreview", args);
}
});
ref.innerHTML = "<h1>Hello world</h1>";
}
Sample Plugin¶
A sample plugin which implements custom user interface functionality is provided in the InvenTree source code:
A sample plugin which demonstrates user interface integrations.
Source code in src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
|