diff options
Diffstat (limited to 'ui/js/class.widget.js')
-rw-r--r-- | ui/js/class.widget.js | 983 |
1 files changed, 983 insertions, 0 deletions
diff --git a/ui/js/class.widget.js b/ui/js/class.widget.js new file mode 100644 index 00000000000..36abd91e9c2 --- /dev/null +++ b/ui/js/class.widget.js @@ -0,0 +1,983 @@ +/* +** Zabbix +** Copyright (C) 2001-2022 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + + +const ZBX_WIDGET_VIEW_MODE_NORMAL = 0; +const ZBX_WIDGET_VIEW_MODE_HIDDEN_HEADER = 1; + +const WIDGET_STATE_INITIAL = 'initial'; +const WIDGET_STATE_ACTIVE = 'active'; +const WIDGET_STATE_INACTIVE = 'inactive'; +const WIDGET_STATE_DESTROYED = 'destroyed'; + +const WIDGET_EVENT_EDIT = 'widget-edit'; +const WIDGET_EVENT_ACTIONS = 'widget-actions'; +const WIDGET_EVENT_ENTER = 'widget-enter'; +const WIDGET_EVENT_LEAVE = 'widget-leave'; +const WIDGET_EVENT_BEFORE_UPDATE = 'widget-before-update'; +const WIDGET_EVENT_AFTER_UPDATE = 'widget-after-update'; +const WIDGET_EVENT_COPY = 'widget-copy'; +const WIDGET_EVENT_PASTE = 'widget-paste'; +const WIDGET_EVENT_DELETE = 'widget-delete'; + +class CWidget extends CBaseComponent { + + static hasReferenceField() { + return false; + } + + static getForeignReferenceFields() { + return []; + } + + constructor({ + type, + name, + view_mode, + fields, + defaults, + widgetid = null, + pos = null, + is_new, + rf_rate, + dashboard, + dashboard_page, + cell_width, + cell_height, + min_rows, + is_editable, + is_edit_mode, + can_edit_dashboards, + time_period, + dynamic_hostid, + unique_id + }) { + super(document.createElement('div')); + + this._type = type; + this._name = name; + this._view_mode = view_mode; + this._fields = fields; + this._defaults = defaults; + this._widgetid = widgetid; + this._pos = pos; + this._is_new = is_new; + this._rf_rate = rf_rate; + + this._dashboard = { + templateid: dashboard.templateid, + dashboardid: dashboard.dashboardid + }; + + this._dashboard_page = { + unique_id: dashboard_page.unique_id + }; + + this._cell_width = cell_width; + this._cell_height = cell_height; + this._min_rows = min_rows; + this._is_editable = is_editable; + this._is_edit_mode = is_edit_mode; + this._can_edit_dashboards = can_edit_dashboards; + this._time_period = time_period; + this._dynamic_hostid = dynamic_hostid; + this._unique_id = unique_id; + + this._init(); + this._registerEvents(); + } + + _init() { + this._css_classes = { + actions: 'dashboard-grid-widget-actions', + container: 'dashboard-grid-widget-container', + content: 'dashboard-grid-widget-content', + focus: 'dashboard-grid-widget-focus', + head: 'dashboard-grid-widget-head', + hidden_header: 'dashboard-grid-widget-hidden-header', + mask: 'dashboard-grid-widget-mask', + root: 'dashboard-grid-widget', + resize_handle: 'ui-resizable-handle' + }; + + this._state = WIDGET_STATE_INITIAL; + + this._content_size = {}; + this._update_timeout_id = null; + this._update_interval_id = null; + this._update_abort_controller = null; + this._is_updating_paused = false; + this._update_retry_sec = 3; + this._show_preloader_asap = true; + this._resizable_handles = []; + + this._hide_preloader_animation_frame = null; + } + + // Logical state control methods. + + getState() { + return this._state; + } + + start() { + if (this._state !== WIDGET_STATE_INITIAL) { + throw new Error('Unsupported state change.'); + } + + this._state = WIDGET_STATE_INACTIVE; + + this._doStart(); + } + + _doStart() { + this._makeView(); + + if (this._pos !== null) { + this.setPos(this._pos); + } + } + + activate() { + if (this._state !== WIDGET_STATE_INACTIVE) { + throw new Error('Unsupported state change.'); + } + + this._state = WIDGET_STATE_ACTIVE; + + this._doActivate(); + } + + _doActivate() { + this._activateEvents(); + this._startUpdating(); + } + + deactivate() { + if (this._state !== WIDGET_STATE_ACTIVE) { + throw new Error('Unsupported state change.'); + } + + this._state = WIDGET_STATE_INACTIVE; + + this._doDeactivate(); + } + + _doDeactivate() { + if (this._is_new) { + this._is_new = false; + this._target.classList.remove('new-widget'); + } + + this._deactivateEvents(); + this._stopUpdating(); + } + + destroy() { + if (this._state === WIDGET_STATE_ACTIVE) { + this.deactivate(); + } + + if (this._state !== WIDGET_STATE_INACTIVE) { + throw new Error('Unsupported state change.'); + } + + this._state = WIDGET_STATE_DESTROYED; + + this._doDestroy(); + } + + _doDestroy() { + } + + // External events management methods. + + isEditMode() { + return this._is_edit_mode; + } + + setEditMode() { + this._is_edit_mode = true; + + if (this._state === WIDGET_STATE_ACTIVE) { + this._stopUpdating({do_abort: false}); + } + + this._target.classList.add('ui-draggable', 'ui-resizable'); + } + + supportsDynamicHosts() { + return this._fields.dynamic == 1; + } + + getDynamicHost() { + return this._dynamic_hostid; + } + + setDynamicHost(dynamic_hostid) { + this._dynamic_hostid = dynamic_hostid; + + if (this._state === WIDGET_STATE_ACTIVE) { + this._startUpdating(); + } + } + + setTimePeriod(time_period) { + this._time_period = time_period; + } + + isEntered() { + return this._target.classList.contains(this._css_classes.focus); + } + + enter() { + if (this._is_edit_mode) { + this._addResizeHandles(); + } + + this._target.classList.add(this._css_classes.focus); + } + + leave() { + if (this._is_edit_mode) { + this._removeResizeHandles(); + } + + if (this._content_header.contains(document.activeElement)) { + document.activeElement.blur(); + } + + this._target.classList.remove(this._css_classes.focus); + } + + getNumHeaderLines() { + return this._view_mode == ZBX_WIDGET_VIEW_MODE_HIDDEN_HEADER ? 1 : 0; + } + + _isResizing() { + return this._target.classList.contains('ui-resizable-resizing'); + } + + setResizing(is_resizing) { + this._target.classList.toggle('ui-resizable-resizing', is_resizing); + } + + _isDragging() { + return this._target.classList.contains('ui-draggable-dragging'); + } + + setDragging(is_dragging) { + this._target.classList.toggle('ui-draggable-dragging', is_dragging); + } + + isUserInteracting() { + return this._target.querySelectorAll('[data-expanded="true"], [aria-expanded="true"]').length > 0; + } + + announceWidgets(widgets) { + } + + resize() { + } + + // Data interface methods. + + getUniqueId() { + return this._unique_id; + } + + getType() { + return this._type; + } + + getName() { + return this._name; + } + + _setName(name) { + this._name = name; + this._setHeaderName(this._name !== '' ? this._name : this._defaults.name); + } + + getHeaderName() { + return this._name !== '' ? this._name : this._defaults.name; + } + + _setHeaderName(name) { + if (this._state !== WIDGET_STATE_INITIAL) { + this._content_header.querySelector('h4').textContent = name; + } + } + + getViewMode() { + return this._view_mode; + } + + _setViewMode(view_mode) { + if (this._view_mode !== view_mode) { + this._view_mode = view_mode; + this._target.classList.toggle(this._css_classes.hidden_header, + this._view_mode == ZBX_WIDGET_VIEW_MODE_HIDDEN_HEADER + ); + } + } + + getFields() { + return this._fields; + } + + _setFields(fields) { + this._fields = fields; + } + + getWidgetId() { + return this._widgetid; + } + + _hasPadding() { + return this._view_mode != ZBX_WIDGET_VIEW_MODE_HIDDEN_HEADER; + } + + _updatePadding() { + if (this._state !== WIDGET_STATE_INITIAL) { + this._content_body.classList.toggle('no-padding', !this._hasPadding()); + } + } + + updateProperties({name, view_mode, fields}) { + if (name !== undefined) { + this._setName(name); + } + + if (view_mode !== undefined) { + this._setViewMode(view_mode); + } + + if (fields !== undefined) { + this._setFields(fields); + } + + this._updatePadding(); + + this._show_preloader_asap = true; + + if (this._state === WIDGET_STATE_ACTIVE) { + this._startUpdating(); + } + } + + getRfRate() { + return this._rf_rate; + } + + _setRfRate(rf_rate) { + this._rf_rate = rf_rate; + + if (this._widgetid !== null) { + const curl = new Curl('zabbix.php'); + + curl.setArgument('action', 'dashboard.widget.rfrate'); + + fetch(curl.getUrl(), { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({widgetid: this._widgetid, rf_rate}) + }) + .then((response) => response.json()) + .then((response) => { + if ('error' in response) { + throw {error: response.error}; + } + }) + .catch((exception) => { + console.log('Could not update widget refresh rate:', exception); + }); + } + } + + getDataCopy({is_single_copy}) { + const data = { + type: this._type, + name: this._name, + view_mode: this._view_mode, + fields: this._fields, + pos: is_single_copy + ? { + width: this._pos.width, + height: this._pos.height + } + : this._pos, + rf_rate: this._rf_rate + }; + + if (is_single_copy) { + data.dashboard = { + templateid: this._dashboard.templateid + }; + } + + return data; + } + + save() { + return { + widgetid: this._widgetid ?? undefined, + pos: this._pos, + type: this._type, + name: this._name, + view_mode: this._view_mode, + fields: Object.keys(this._fields).length > 0 ? this._fields : undefined + }; + } + + getActionsContextMenu({can_paste_widget}) { + let menu = []; + let menu_actions = []; + + if (this._can_edit_dashboards && (this._dashboard.templateid === null || this._dynamic_hostid === null)) { + menu_actions.push({ + label: t('Copy'), + clickCallback: () => this.fire(WIDGET_EVENT_COPY) + }); + } + + if (this._is_edit_mode) { + menu_actions.push({ + label: t('Paste'), + disabled: can_paste_widget === false, + clickCallback: () => this.fire(WIDGET_EVENT_PASTE) + }); + + menu_actions.push({ + label: t('Delete'), + clickCallback: () => this.fire(WIDGET_EVENT_DELETE) + }); + } + + if (menu_actions.length) { + menu.push({ + label: t('Actions'), + items: menu_actions + }); + } + + if (!this._is_edit_mode) { + const refresh_interval_section = { + label: t('Refresh interval'), + items: [] + }; + + const rf_rates = { + 0: t('No refresh'), + 10: t('10 seconds'), + 30: t('30 seconds'), + 60: t('1 minute'), + 120: t('2 minutes'), + 600: t('10 minutes'), + 900: t('15 minutes') + }; + + for (const [rf_rate, label] of Object.entries(rf_rates)) { + refresh_interval_section.items.push({ + label: label, + selected: rf_rate == this._rf_rate, + clickCallback: () => { + this._setRfRate(rf_rate); + + if (this._state === WIDGET_STATE_ACTIVE) { + if (this._rf_rate > 0) { + this._startUpdating(); + } + else { + this._stopUpdating(); + } + } + } + }); + } + + menu.push(refresh_interval_section); + } + + return menu; + } + + // Content updating methods. + + _startUpdating(delay_sec = 0, {do_update_once = null} = {}) { + if (do_update_once === null) { + do_update_once = this._is_edit_mode; + } + + this._stopUpdating({do_abort: false}); + + if (delay_sec > 0) { + this._update_timeout_id = setTimeout(() => { + this._update_timeout_id = null; + this._startUpdating(0, {do_update_once}); + }, delay_sec * 1000); + } + else { + if (!do_update_once && this._rf_rate > 0) { + this._update_interval_id = setInterval(() => { + this._update(do_update_once); + }, this._rf_rate * 1000); + } + + this._update(do_update_once); + } + } + + _stopUpdating({do_abort = true} = {}) { + if (this._update_timeout_id !== null) { + clearTimeout(this._update_timeout_id); + this._update_timeout_id = null; + } + + if (this._update_interval_id !== null) { + clearInterval(this._update_interval_id); + this._update_interval_id = null; + } + + if (do_abort && this._update_abort_controller !== null) { + this._update_abort_controller.abort(); + } + } + + _pauseUpdating() { + this._is_updating_paused = true; + } + + _resumeUpdating() { + this._is_updating_paused = false; + } + + _update(do_update_once) { + if (this._update_abort_controller !== null || this._is_updating_paused || this.isUserInteracting()) { + this._startUpdating(1, {do_update_once}); + + return; + } + + this.fire(WIDGET_EVENT_BEFORE_UPDATE); + + this._content_size = this._getContentSize(); + + this._update_abort_controller = new AbortController(); + + if (this._show_preloader_asap) { + this._show_preloader_asap = false; + this._showPreloader(); + } + else { + this._schedulePreloader(); + } + + new Promise((resolve) => resolve(this._promiseUpdate())) + .then(() => this._hidePreloader()) + .catch((exception) => { + console.log('Could not update widget:', exception); + + if (this._update_abort_controller.signal.aborted) { + this._hidePreloader(); + } + else { + this._startUpdating(this._update_retry_sec, {do_update_once}); + } + }) + .finally(() => { + this._update_abort_controller = null; + + this.fire(WIDGET_EVENT_AFTER_UPDATE); + }); + } + + _promiseUpdate() { + const curl = new Curl('zabbix.php'); + + curl.setArgument('action', `widget.${this._type}.view`); + + return fetch(curl.getUrl(), { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(this._getUpdateRequestData()), + signal: this._update_abort_controller.signal + }) + .then((response) => response.json()) + .then((response) => { + if ('error' in response) { + this._processUpdateErrorResponse(response.error); + + return; + } + + this._processUpdateResponse(response); + }); + } + + _getUpdateRequestData() { + return { + templateid: this._dashboard.templateid ?? undefined, + dashboardid: this._dashboard.dashboardid ?? undefined, + widgetid: this._widgetid ?? undefined, + name: this._name !== '' ? this._name : undefined, + fields: Object.keys(this._fields).length > 0 ? this._fields : undefined, + view_mode: this._view_mode, + edit_mode: this._is_edit_mode ? 1 : 0, + dynamic_hostid: this._dashboard.templateid !== null || this.supportsDynamicHosts() + ? (this._dynamic_hostid ?? undefined) + : undefined, + ...this._content_size + }; + } + + _processUpdateResponse(response) { + this._setContents({ + name: response.name, + body: response.body, + messages: response.messages, + info: response.info, + debug: response.debug + }); + } + + _processUpdateErrorResponse(error) { + this._setErrorContents({error}); + } + + // Widget view methods. + + getView() { + return this._target; + } + + getCssClass(name) { + return this._css_classes[name]; + } + + getPos() { + return this._pos; + } + + setPos(pos, {is_managed = false} = {}) { + this._pos = pos; + + if (!is_managed) { + this._target.style.left = `${this._cell_width * this._pos.x}%`; + this._target.style.top = `${this._cell_height * this._pos.y}px`; + this._target.style.width = `${this._cell_width * this._pos.width}%`; + this._target.style.height = `${this._cell_height * this._pos.height}px`; + } + } + + getResizeHandleSides(resize_handle) { + return { + top: resize_handle.classList.contains('ui-resizable-nw') + || resize_handle.classList.contains('ui-resizable-n') + || resize_handle.classList.contains('ui-resizable-ne'), + right: resize_handle.classList.contains('ui-resizable-ne') + || resize_handle.classList.contains('ui-resizable-e') + || resize_handle.classList.contains('ui-resizable-se'), + bottom: resize_handle.classList.contains('ui-resizable-se') + || resize_handle.classList.contains('ui-resizable-s') + || resize_handle.classList.contains('ui-resizable-sw'), + left: resize_handle.classList.contains('ui-resizable-sw') + || resize_handle.classList.contains('ui-resizable-w') + || resize_handle.classList.contains('ui-resizable-nw') + }; + } + + _addResizeHandles() { + this._resizable_handles = {}; + + for (const direction of ['n', 'e', 's', 'w', 'ne', 'se', 'sw', 'nw']) { + const resizable_handle = document.createElement('div'); + + resizable_handle.classList.add('ui-resizable-handle', `ui-resizable-${direction}`); + + if (['n', 'e', 's', 'w'].includes(direction)) { + const ui_resize_dot = document.createElement('div'); + + ui_resize_dot.classList.add('ui-resize-dot'); + resizable_handle.appendChild(ui_resize_dot); + + const ui_resizable_border = document.createElement('div'); + + ui_resizable_border.classList.add(`ui-resizable-border-${direction}`); + resizable_handle.appendChild(ui_resizable_border); + } + + this._target.append(resizable_handle); + this._resizable_handles[direction] = resizable_handle; + } + } + + _removeResizeHandles() { + for (const resizable_handle of Object.values(this._resizable_handles)) { + resizable_handle.remove(); + } + + this._resizable_handles = {}; + } + + _getContentSize() { + const computed_style = getComputedStyle(this._content_body); + + const content_width = parseInt( + parseFloat(computed_style.width) + - parseFloat(computed_style.paddingLeft) - parseFloat(computed_style.paddingRight) + - parseFloat(computed_style.borderLeftWidth) - parseFloat(computed_style.borderRightWidth) + ); + + const content_height = parseInt( + parseFloat(computed_style.height) + - parseFloat(computed_style.paddingTop) - parseFloat(computed_style.paddingBottom) + - parseFloat(computed_style.borderTopWidth) - parseFloat(computed_style.borderBottomWidth) + ); + + return {content_width, content_height}; + } + + _setContents({name, body, messages, info, debug}) { + this._setHeaderName(name); + + this._content_body.innerHTML = ''; + + if (messages !== undefined) { + const message_box = makeMessageBox('bad', messages)[0]; + + this._content_body.appendChild(message_box); + } + + if (body !== undefined) { + this._content_body.insertAdjacentHTML('beforeend', body); + } + + if (debug !== undefined) { + this._content_body.insertAdjacentHTML('beforeend', debug); + } + + this._removeInfoButtons(); + + if (info !== undefined) { + this._addInfoButtons(info); + } + } + + _setErrorContents({error}) { + this._setHeaderName(this._defaults.name); + + const message_box = makeMessageBox('bad', error.messages, error.title)[0]; + + this._content_body.innerHTML = ''; + this._content_body.appendChild(message_box); + + this._removeInfoButtons(); + } + + _addInfoButtons(buttons) { + buttons.reverse(); + + for (const button of buttons) { + const li = document.createElement('li'); + + li.classList.add('widget-info-button'); + + const li_button = document.createElement('button'); + + li_button.type = 'button'; + li_button.setAttribute('data-hintbox', '1'); + li_button.setAttribute('data-hintbox-static', '1'); + li_button.classList.add(button.icon); + li.appendChild(li_button); + + const li_div = document.createElement('div'); + + li_div.innerHTML = button.hint; + li_div.classList.add('hint-box'); + li_div.style.display = 'none'; + li.appendChild(li_div); + + this._actions.prepend(li); + } + } + + _removeInfoButtons() { + for (const li of this._actions.querySelectorAll('.widget-info-button')) { + li.remove(); + } + } + + _showPreloader() { + // Fixed Safari 16 bug: removing preloader classes on animation frame to ensure removal of icons. + + if (this._hide_preloader_animation_frame !== null) { + cancelAnimationFrame(this._hide_preloader_animation_frame); + this._hide_preloader_animation_frame = null; + } + + this._content_body.classList.add('is-loading'); + this._content_body.classList.remove('is-loading-fadein', 'delayed-15s'); + } + + _hidePreloader() { + // Fixed Safari 16 bug: removing preloader classes on animation frame to ensure removal of icons. + + if (this._hide_preloader_animation_frame !== null) { + return; + } + + this._hide_preloader_animation_frame = requestAnimationFrame(() => { + this._content_body.classList.remove('is-loading', 'is-loading-fadein', 'delayed-15s'); + this._hide_preloader_animation_frame = null; + }); + } + + _schedulePreloader() { + // Fixed Safari 16 bug: removing preloader classes on animation frame to ensure removal of icons. + + if (this._hide_preloader_animation_frame !== null) { + cancelAnimationFrame(this._hide_preloader_animation_frame); + this._hide_preloader_animation_frame = null; + } + + this._content_body.classList.add('is-loading', 'is-loading-fadein', 'delayed-15s'); + } + + _makeView() { + this._container = document.createElement('div'); + this._container.classList.add(this._css_classes.container); + + this._content_header = document.createElement('div'); + this._content_header.classList.add(this._css_classes.head); + + const content_header_h4 = document.createElement('h4'); + + content_header_h4.textContent = this._name !== '' ? this._name : this._defaults.name; + this._content_header.appendChild(content_header_h4); + + this._actions = document.createElement('ul'); + this._actions.classList.add(this._css_classes.actions); + + if (this._is_editable) { + this._button_edit = document.createElement('button'); + this._button_edit.type = 'button'; + this._button_edit.title = t('Edit') + this._button_edit.classList.add('btn-widget-edit', 'js-widget-edit'); + + const li = document.createElement('li'); + + li.appendChild(this._button_edit); + this._actions.appendChild(li); + } + + this._button_actions = document.createElement('button'); + this._button_actions.type = 'button'; + this._button_actions.title = t('Actions'); + this._button_actions.setAttribute('aria-expanded', 'false'); + this._button_actions.setAttribute('aria-haspopup', 'true'); + this._button_actions.classList.add('btn-widget-action', 'js-widget-action'); + + const li = document.createElement('li'); + + li.appendChild(this._button_actions); + this._actions.appendChild(li); + + this._content_header.append(this._actions); + + this._container.appendChild(this._content_header); + + this._content_body = document.createElement('div'); + this._content_body.classList.add(this._css_classes.content); + this._content_body.classList.add(`dashboard-widget-${this._type}`); + this._content_body.classList.toggle('no-padding', !this._hasPadding()); + + this._container.appendChild(this._content_body); + + this._target.appendChild(this._container); + this._target.classList.add(this._css_classes.root); + this._target.classList.toggle('ui-draggable', this._is_edit_mode); + this._target.classList.toggle('ui-resizable', this._is_edit_mode); + this._target.classList.toggle(this._css_classes.hidden_header, + this._view_mode == ZBX_WIDGET_VIEW_MODE_HIDDEN_HEADER + ); + this._target.classList.toggle('new-widget', this._is_new); + + this._target.style.minWidth = `${this._cell_width}%`; + this._target.style.minHeight = `${this._cell_height}px`; + } + + // Internal events management methods. + + _registerEvents() { + this._events = { + actions: (e) => { + this.fire(WIDGET_EVENT_ACTIONS, {mouse_event: e}); + }, + + edit: () => { + this.fire(WIDGET_EVENT_EDIT); + }, + + focusin: () => { + this.fire(WIDGET_EVENT_ENTER); + }, + + focusout: (e) => { + if (!this._content_header.contains(e.relatedTarget)) { + this.fire(WIDGET_EVENT_LEAVE); + } + }, + + enter: () => { + this.fire(WIDGET_EVENT_ENTER); + }, + + leave: () => { + this.fire(WIDGET_EVENT_LEAVE); + } + }; + } + + _activateEvents() { + this._button_actions.addEventListener('click', this._events.actions); + + if (this._is_editable) { + this._button_edit.addEventListener('click', this._events.edit); + } + + this._target.addEventListener('mousemove', this._events.enter); + this._target.addEventListener('mouseleave', this._events.leave); + this._content_header.addEventListener('focusin', this._events.focusin); + this._content_header.addEventListener('focusout', this._events.focusout); + } + + _deactivateEvents() { + this._button_actions.removeEventListener('click', this._events.actions); + + if (this._is_editable) { + this._button_edit.removeEventListener('click', this._events.edit); + } + + this._target.removeEventListener('mousemove', this._events.enter); + this._target.removeEventListener('mouseleave', this._events.leave); + this._content_header.removeEventListener('focusin', this._events.focusin); + this._content_header.removeEventListener('focusout', this._events.focusout); + } +} |