---
title: "eNEXT - tvorba vlastních pluginů"
description: "Popis a postup řešení k tvorbě vlastních pluginů do eNEXT"
url: https://navody.aktion.cz/cs/articles/92-enext-tvorba-vlastnich-pluginu
collection: "Konfigurační návody a FAQ"
updated: 2026-10-14
language: cs
alternates: [en]
---

# eNEXT - tvorba vlastních pluginů

Popis a postup řešení k tvorbě vlastních pluginů do eNEXT

## Úvod

Web pluginy umožňují vytvářet vlastní uživatelské přehledy s využitím registrovaných web komponent. Pluginy jsou načítány dynamicky ze složky `AktionNEXT\eNextWeb\assets\plugins` a mohou využívat autentizaci a další služby poskytované hlavní aplikací. V adresáři lze najít i vzorový plugin example-plugin.js.

## Pojmenování a registrace pluginu

### Název souboru

Do adresáře assets, vytvoříte javaScriptový soubor, například: nazevPluginu.js

### Vytvoření uživatelského přehledu

Po vytvoření souboru, musíte vytvořit v systému uživatelský přehled, ten najdete v agendě Číselníky → Uživatelské přehledy.

Dále vytvořte stejnojmenný přehled, aby plugin fungoval musíte nastavit:

-   Název a definici musí mít stejný název jako je název plugninu
    
-   Zašrktnuté “Plugin pro web”
    

![](https://staticfiles.gleap.io/gattachments/DJNFgj8UZQGqU2BfKXz5XGZltdwc7UjVvMI2vr15ZGXIDUBFSBse3RskblcHVyUYY2nai8UJoFP.png)

## Základní struktura pluginu

### Minimální šablona

````javascript
```javascript
window.PLUGIN = window.PLUGIN || {};

// (Volitelné) Načtení stylů
(function loadStyles() {
  const styleId = 'plugin-styles';
  if (document.getElementById(styleId)) {
    return;
  }

  const style = document.createElement('style');
  style.id = styleId;
  style.textContent = `
    /* Vaše CSS styly zde */
    .muj-plugin {
      padding: 20px;
    }
  `;

  document.head.appendChild(style);
})();

// Inicializační funkce pluginu
window.PLUGIN.init = function (containerId) {
  const container = document.getElementById(containerId || 'plugin-root');

  if (!container) {
    console.error('Container element not found');
    return;
  }

  // Vytvoření HTML obsahu s využitím web komponent
  const content = `
    <div class="muj-plugin">
      <app-card variant="outlined">
        <app-card-content>
          <app-typography variant="h4">Můj plugin</app-typography>
          <app-typography variant="body1">Hello World!</app-typography>
        </app-card-content>
      </app-card>
    </div>
  `;

  container.innerHTML = content;

  // (Volitelné) Registrace event listenerů
  container.addEventListener('click', (event) => {
    // Vaše handler logika
  });
};
```
````

### Povinné části

-   **Namespace**
    
    ```markdown
    window.PLUGIN = window.PLUGIN || {};
    ```
    
-   **Init funkce**
    
    ```markdown
    window.PLUGIN.init = function(containerId) { ... }
    ```
    
-   **Návratová hodnota -** funkce musí nastavit “container.innerHTML“ s html obsahem
    

## API dostupné na window objektu

### Objekt pro práci s autentizací

-   windows.authAPI
    

````javascript
```javascript
// Získání aktuálního tokenu
const token = window.authApi?.getToken();
// Vrací: string | null

// Získání tokenu a případné obnovení pokud expiroval
const token = await window.authApi?.getAndRefreshToken();
// Vrací: Promise<string | null>

// Získání informací o přihlášeném uživateli
const user = window.authApi?.getUser();
// Vrací: { userName, personName, ... } | null

// Kontrola, zda je token expirován
const expired = window.authApi?.isTokenExpired();
// Vrací: boolean

// Obnovení tokenu
const newToken = await window.authApi?.renewToken();
// Vrací: Promise<string | null>
```

#### Příklad použití:

```javascript
const user = window.authApi?.getUser();
const tokenExpired = window.authApi?.isTokenExpired();

container.innerHTML = `
  <app-alert severity="info">
    Uživatel: ${user ? `${user.personName || user.userName}` : 'nepřihlášen'}
    (${tokenExpired ? 'token expirován' : 'přihlášen'})
  </app-alert>
`;
```
````

### Funkce pro získání seznamu všech registrovaných web komponent

-   window.gerAvailableWebComponents()
    

````javascript
```javascript
const components = window.getAvailableWebComponents?.() || [];
// Vrací: string[] - pole názvů tagů, např. ["app-button", "app-card", ...]
```
````

## Dostupné komponenty

### MUI komponenty

Pro detailní dokumentaci vlastností a API jednotlivých MUI komponent navštivte: [MUI dokumentaci](https://mui.com/material-ui/getting-started/)

Příklady využití komponent:

````javascript
#### Alert

- **Tag**: `<app-alert>`
- **Atributy**: `severity` (error|warning|info|success), `variant` (standard|filled|outlined)
- **Dokumentace**: https://mui.com/material-ui/react-alert/

```html
<app-alert severity="warning">Varování!</app-alert>
<app-alert severity="success">Úspěch!</app-alert>
```

#### Avatar

- **Tag**: `<app-avatar>`
- **Atributy**: `alt`, `src`, `variant` (circular|rounded|square)
- **Dokumentace**: https://mui.com/material-ui/react-avatar/

```html
<app-avatar alt="John Doe" src="https://example.com/avatar.jpg"></app-avatar>
<app-avatar variant="rounded" src="/path/to/image.jpg"></app-avatar>
```

#### Badge

- **Tag**: `<app-badge>`
- **Atributy**: `badgecontent`, `color` (default|primary|secondary|error|info|success|warning), `variant`, `max`
- **Dokumentace**: https://mui.com/material-ui/react-badge/

```html
<app-badge badgecontent="4" color="primary">
  <app-avatar alt="User" src="/avatar.jpg"></app-avatar>
</app-badge>
```

**Poznámka**: Badge vyžaduje children element, který obalení. Můžete použít jiné web komponenty jako children.

#### Button

- **Tag**: `<app-button>`
- **Atributy**: `variant` (text|outlined|contained), `color` (primary|secondary|error|warning|info|success), `size` (small|medium|large), `disabled` (boolean), `fullwidth` (boolean)
- **Dokumentace**: https://mui.com/material-ui/react-button/

```html
<app-button variant="contained" color="primary">Klikni</app-button>
<app-button variant="outlined" disabled>Zakázáno</app-button>
<app-button variant="text" size="small">Malé tlačítko</app-button>
```

**Poznámka**: Pro boolean atributy stačí uvést atribut bez hodnoty:

```html
<app-button disabled>Zakázáno</app-button>
<!-- je stejné jako -->
<app-button disabled="true">Zakázáno</app-button>
```

#### Card a CardContent

- **Tag**: `<app-card>` a `<app-card-content>`
- **Card atributy**: `variant` (elevation|outlined), `elevation` (0-24)
- **Dokumentace**: https://mui.com/material-ui/react-card/

```html
<app-card variant="outlined">
  <app-card-content>
    <app-typography variant="h5">Nadpis karty</app-typography>
    <app-typography variant="body2">Obsah karty</app-typography>
  </app-card-content>
</app-card>
```

#### Chip

- **Tag**: `<app-chip>`
- **Atributy**: `label`, `color` (default|primary|secondary|error|info|success|warning), `variant` (filled|outlined), `size` (small|medium)
- **Dokumentace**: https://mui.com/material-ui/react-chip/

```html
<app-chip label="Aktivní" color="success"></app-chip>
<app-chip label="Tag" variant="outlined"></app-chip>
```

#### CircularProgress

- **Tag**: `<app-circular-progress>`
- **Atributy**: `color` (primary|secondary|error|info|success|warning|inherit), `size` (číslo), `variant` (determinate|indeterminate), `value` (0-100)
- **Dokumentace**: https://mui.com/material-ui/react-progress/

```html
<app-circular-progress color="primary"></app-circular-progress>
<app-circular-progress variant="determinate" value="75"></app-circular-progress>
```

#### Divider

- **Tag**: `<app-divider>`
- **Atributy**: `orientation` (horizontal|vertical), `variant` (fullWidth|inset|middle), `textalign` (left|center|right), `flexitem` (boolean)
- **Dokumentace**: https://mui.com/material-ui/react-divider/

```html
<app-divider></app-divider>
<app-divider orientation="horizontal" textalign="center">STŘED</app-divider>
```

#### LinearProgress

- **Tag**: `<app-linear-progress>`
- **Atributy**: `color` (primary|secondary|error|info|success|warning|inherit), `variant` (determinate|indeterminate|buffer|query), `value` (0-100), `valuebuffer` (0-100)
- **Dokumentace**: https://mui.com/material-ui/react-progress/

```html
<app-linear-progress color="primary"></app-linear-progress>
<app-linear-progress variant="determinate" value="50"></app-linear-progress>
```

#### Link

- **Tag**: `<app-link>`
- **Atributy**: `href`, `color` (primary|secondary|error|info|success|warning|inherit), `underline` (none|hover|always), `variant`
- **Dokumentace**: https://mui.com/material-ui/react-link/

```html
<app-link href="#" color="primary">Primární odkaz</app-link>
<app-link href="https://example.com" underline="always">Vždy podtržené</app-link>
```

#### Paper

- **Tag**: `<app-paper>`
- **Atributy**: `elevation` (0-24), `variant` (elevation|outlined), `square` (boolean), `sx` (JSON string pro styling)
- **Dokumentace**: https://mui.com/material-ui/react-paper/

```html
<app-paper elevation="3">
  <div style="padding: 20px;">Obsah</div>
</app-paper>

<!-- Použití sx prop pro pokročilé stylování -->
<app-paper sx='{"p": 2.5, "width": 200, "textAlign": "center"}'>
  <div>Centrovaný obsah</div>
</app-paper>
```

**Poznámka**: `sx` prop přijímá JSON string s MUI sx hodnotami.

#### Skeleton

- **Tag**: `<app-skeleton>`
- **Atributy**: `variant` (text|circular|rectangular|rounded), `animation` (pulse|wave|false), `width` (číslo), `height` (číslo)
- **Dokumentace**: https://mui.com/material-ui/react-skeleton/

```html
<app-skeleton variant="text" width="200" height="30"></app-skeleton>
<app-skeleton variant="circular" width="40" height="40"></app-skeleton>
<app-skeleton variant="rectangular" width="300" height="100"></app-skeleton>
```

#### Typography

- **Tag**: `<app-typography>`
- **Atributy**: `variant` (h1|h2|h3|h4|h5|h6|subtitle1|subtitle2|body1|body2|caption|button|overline), `color` (textPrimary|textSecondary|primary|secondary|error|...), `align` (left|center|right|justify)
- **Dokumentace**: https://mui.com/material-ui/react-typography/

```html
<app-typography variant="h3">Nadpis</app-typography>
<app-typography variant="body1" color="textSecondary">Sekundární text</app-typography>
<app-typography variant="caption">Malý text</app-typography>
```

### 5.2 Vlastní komponenty (2)

#### Loading

- **Tag**: `<app-loading>`
- **Atributy**: `message` (text), `height` (číslo v px)
- **Popis**: Zobrazí loading indikátor se zprávou

```html
<app-loading message="Načítám data..." height="100"></app-loading>
```

#### HtmlWhiteTooltip

- **Tag**: `<app-white-tooltip>`
- **Atributy**: `text` (HTML string), `placement` (top|bottom|left|right|top-start|top-end|...)
- **Popis**: Bílý tooltip s podporou HTML obsahu
- **Children**: Musí obsahovat jeden child element

```html
<!-- Jednoduchý tooltip -->
<app-white-tooltip text="Nápověda" placement="top">
  <app-button variant="outlined">Tlačítko s nápovědou</app-button>
</app-white-tooltip>

<!-- HTML formátování -->
<app-white-tooltip text="<b>Tučný</b> a <i>kurzíva</i>" placement="bottom">
  <app-chip label="Info" color="info"></app-chip>
</app-white-tooltip>

<!-- Komplexní data -->
<app-white-tooltip
  text="<div style='text-align: left;'><b>Uživatel:</b> Jan Novák<br><b>Email:</b> jan@example.com<br><b>Status:</b> <span style='color: green;'>Aktivní</span></div>"
  placement="right"
>
  <app-chip label="Zobraz info"></app-chip>
</app-white-tooltip>
````

## Event handling a interaktivita

### Evenet delegation

Pro lepší výkon používejte event delegation - poslouchejte na containeru místo jednotlivých elementů:

````javascript
```javascript
window.PLUGIN.init = function (containerId) {
  const container = document.getElementById(containerId || 'plugin-root');

  let clickCounter = 0;

  const content = `
    <div>
      <app-button variant="contained" data-action="increment">+1</app-button>
      <app-button variant="contained" data-action="decrement">-1</app-button>
      <app-typography variant="h5" id="counter">0</app-typography>
    </div>
  `;

  container.innerHTML = content;

  // Event listener na containeru
  container.addEventListener('click', (event) => {
    const target = event.target.closest('[data-action]');
    if (!target) return;

    const action = target.getAttribute('data-action');
    const counterEl = document.getElementById('counter');

    if (action === 'increment') {
      clickCounter++;
    } else if (action === 'decrement') {
      clickCounter--;
    }

    if (counterEl) {
      counterEl.textContent = clickCounter;
    }
  });
};
```
````

### Asynchronní operace

````javascript
```javascript
container.addEventListener('click', async (event) => {
  const target = event.target.closest('[data-action="refresh-token"]');
  if (!target) return;

  target.setAttribute('disabled', 'true');
  target.textContent = 'Obnovuji...';

  try {
    const newToken = await window.authApi?.renewToken();

    if (newToken) {
      target.textContent = 'Token obnoven!';
    } else {
      target.textContent = 'Chyba';
    }
  } finally {
    setTimeout(() => {
      target.removeAttribute('disabled');
      target.textContent = 'Obnovit Token';
    }, 2000);
  }
});
```
````

### Dynamické aktualizace

````javascript
```javascript
const dynamicContentId = 'dynamic-area';

const content = `
  <div>
    <app-button data-action="show-info">Zobraz info</app-button>
    <div id="${dynamicContentId}"></div>
  </div>
`;

container.innerHTML = content;

container.addEventListener('click', (event) => {
  if (event.target.closest('[data-action="show-info"]')) {
    const dynamicArea = document.getElementById(dynamicContentId);

    dynamicArea.innerHTML = `
      <app-alert severity="info">
        Dynamicky vložený obsah!
      </app-alert>
    `;
  }
});
```
````

## Stylování

### Vlastní CSS styly

````javascript
```javascript
(function loadStyles() {
  const styleId = 'muj-plugin-styles';
  if (document.getElementById(styleId)) return;

  const style = document.createElement('style');
  style.id = styleId;
  style.textContent = `
    .muj-plugin-wrapper {
      padding: 20px;
      max-width: 1200px;
      margin: 0 auto;
    }
    
    .muj-plugin-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      gap: 20px;
    }
    
    .muj-plugin-header {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 30px;
      border-radius: 12px;
      margin-bottom: 30px;
    }
  `;

  document.head.appendChild(style);
})();
```
````

### Inline styly

````javascript
```html
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
  <app-button variant="contained">Tlačítko 1</app-button>
  <app-button variant="outlined">Tlačítko 2</app-button>
</div>
```
````

### MUI sx prop

Pro komponenty podporující “sx” například “Paper“:

````javascript
```html
<app-paper sx='{"p": 3, "bgcolor": "background.paper", "borderRadius": 2}'>
  <div>Stylovaný Paper</div>
</app-paper>
```
````

## Best Practices

### Kontrola existence elementů

````javascript
```javascript
window.PLUGIN.init = function (containerId) {
  const container = document.getElementById(containerId || 'plugin-root');

  if (!container) {
    console.error('Plugin container not found:', containerId);
    return;
  }

  // Pokračování s inicializací...
};
```
````

### Čištění při re-inicializaci

````javascript
```javascript
// Vždy použijte optional chaining (?.)
const user = window.authApi?.getUser();
const components = window.getAvailableWebComponents?.() || [];

// Kontrola existence před použitím
if (window.authApi) {
  const token = window.authApi.getToken();
  // ...
}
```
````

### Debugování

````javascript
```javascript
window.PLUGIN.init = function (containerId) {
  console.log('Plugin inicializován s containerId:', containerId);

  const user = window.authApi?.getUser();
  console.log('Aktuální uživatel:', user);

  const components = window.getAvailableWebComponents?.();
  console.log('Dostupné komponenty:', components);

  // ... zbytek kódu
};
```
````

### Error handling

````javascript
```javascript
window.PLUGIN.init = function(containerId) {
  try {
    const container = document.getElementById(containerId || 'plugin-root');

    if (!container) {
      throw new Error(`Container ${containerId} not found`);
    }

    // Inicializace pluginu
    container.innerHTML = /* ... */;

  } catch (error) {
    console.error('Plugin initialization failed:', error);

    // Zobrazení chyby uživateli
    const errorContainer = document.getElementById(containerId);
    if (errorContainer) {
      errorContainer.innerHTML = `
        <app-alert severity="error">
          Plugin se nepodařilo načíst: ${error.message}
        </app-alert>
      `;
    }
  }
};
```
````

## Příklad zkráceného example-plugin

````javascript
```javascript
window.PLUGIN = window.PLUGIN || {};

// Načtení stylů
(function loadStyles() {
  const styleId = 'demo-plugin-styles';
  if (document.getElementById(styleId)) return;

  const style = document.createElement('style');
  style.id = styleId;
  style.textContent = `
    .demo-plugin {
      padding: 20px;
      max-width: 1200px;
      margin: 0 auto;
    }
    
    .demo-grid {
      display: grid;
      gap: 20px;
      margin-top: 20px;
    }
  `;

  document.head.appendChild(style);
})();

// Inicializace pluginu
window.PLUGIN.init = function (containerId) {
  const container = document.getElementById(containerId || 'plugin-root');

  if (!container) {
    console.error('Container not found');
    return;
  }

  // Získání dat z authApi
  const user = window.authApi?.getUser();
  const tokenExpired = window.authApi?.isTokenExpired();

  // Vytvoření obsahu
  const content = `
    <div class="demo-plugin">
      <!-- Header -->
      <app-card variant="outlined">
        <app-card-content>
          <app-typography variant="h4">Demo Plugin</app-typography>
          <app-typography variant="body2">
            Uživatel: ${user ? user.personName || user.userName : 'Nepřihlášen'}
            ${tokenExpired ? '(Token expirován)' : ''}
          </app-typography>
        </app-card-content>
      </app-card>

      <!-- Interaktivní část -->
      <div class="demo-grid">
        <app-card variant="outlined">
          <app-card-content>
            <app-typography variant="h6">Počítadlo kliknutí</app-typography>
            <div style="display: flex; gap: 10px; align-items: center; margin-top: 10px;">
              <app-button variant="contained" color="primary" data-action="increment">
                +1
              </app-button>
              <app-button variant="contained" color="secondary" data-action="decrement">
                -1
              </app-button>
              <app-button variant="outlined" data-action="reset">
                Reset
              </app-button>
              <app-typography variant="h5" id="counter">0</app-typography>
            </div>
          </app-card-content>
        </app-card>

        <app-card variant="outlined">
          <app-card-content>
            <app-typography variant="h6">Akce</app-typography>
            <div style="display: flex; gap: 10px; margin-top: 10px;">
              <app-button variant="contained" color="info" data-action="show-components">
                Seznam komponent
              </app-button>
              <app-button variant="contained" color="success" data-action="refresh-token">
                Obnovit Token
              </app-button>
            </div>
          </app-card-content>
        </app-card>

        <!-- Dynamický obsah -->
        <div id="dynamic-content"></div>
      </div>
    </div>
  `;

  container.innerHTML = content;

  // Event handling
  let counter = 0;

  container.addEventListener('click', async (event) => {
    const target = event.target.closest('[data-action]');
    if (!target) return;

    const action = target.getAttribute('data-action');
    const counterEl = document.getElementById('counter');
    const dynamicContent = document.getElementById('dynamic-content');

    switch (action) {
      case 'increment':
        counter++;
        if (counterEl) counterEl.textContent = counter;
        break;

      case 'decrement':
        counter--;
        if (counterEl) counterEl.textContent = counter;
        break;

      case 'reset':
        counter = 0;
        if (counterEl) counterEl.textContent = counter;
        break;

      case 'show-components':
        const components = window.getAvailableWebComponents?.() || [];
        if (dynamicContent) {
          dynamicContent.innerHTML = `
            <app-card variant="outlined">
              <app-card-content>
                <app-typography variant="h6">
                  Dostupné komponenty (${components.length})
                </app-typography>
                <ul style="margin: 10px 0;">
                  ${components.map((comp) => `<li><code>&lt;${comp}&gt;</code></li>`).join('')}
                </ul>
              </app-card-content>
            </app-card>
          `;
        }
        break;

      case 'refresh-token':
        target.setAttribute('disabled', 'true');
        const originalText = target.textContent;
        target.textContent = 'Obnovuji...';

        try {
          const newToken = await window.authApi?.renewToken();

          if (newToken) {
            if (dynamicContent) {
              dynamicContent.innerHTML = `
                <app-alert severity="success">Token úspěšně obnoven!</app-alert>
              `;
            }
          } else {
            if (dynamicContent) {
              dynamicContent.innerHTML = `
                <app-alert severity="error">Nepodařilo se obnovit token</app-alert>
              `;
            }
          }
        } catch (error) {
          console.error('Token refresh error:', error);
        } finally {
          setTimeout(() => {
            target.removeAttribute('disabled');
            target.textContent = originalText;
          }, 2000);
        }
        break;
    }
  });
};
```

## 10. Troubleshooting

### Problem: Plugin se nenačte

- Zkontrolujte, že název souboru přesně odpovídá `webPluginName` v záznamu Uživatelský přehled
- Zkontrolujte cestu: `public/assets/plugins/{NazevPluginu}.js`
- Zkontrolujte konzoli prohlížeče pro JavaScript chyby

### Problem: Komponenty se nezobrazují

- Zkontrolujte, že používáte správné názvy tagů (např. `app-button`, ne `button`)
- Zkontrolujte, že web komponenty jsou inicializovány (spustí se automaticky při načtení aplikace)
- Zkontrolujte atributy - některé vyžadují konkrétní hodnoty

### Problem: Event handlery nefungují

- Používejte event delegation na containeru
- Zkontrolujte, že element má atribut `data-action` nebo podobný identifikátor
- Zkontrolujte konzoli pro chyby v event handleru

### Problem: Styly nefungují

- Zkontrolujte, že `styleId` je unikátní a načítání stylů má guard (`if (document.getElementById(styleId))`)
- Zkontrolujte, že CSS selektory jsou správné
- Zkontrolujte specifičnost CSS

## 11. Checklist pro vytvoření pluginu

- [ ] Vytvořen soubor v `public/assets/plugins/{NazevPluginu}.js`
- [ ] Název souboru odpovídá `webPluginName` v systému
- [ ] Vytvořen záznam Uživatelský přehled s `isWebPlugin = true`
- [ ] Plugin má namespace `window.PLUGIN`
- [ ] Plugin má `init` funkci
- [ ] `init` funkce kontroluje existenci containeru
- [ ] Používají se pouze podporované web komponenty
- [ ] Event handling přes event delegation
- [ ] Bezpečné použití `window.authApi` (optional chaining)
- [ ] Styly načteny s guard pro duplicity
- [ ] Plugin testován v prohlížeči
- [ ] Kontrola konzole pro chyby

---
````
