/*
* This file is part of AUX.
*
* AUX 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 3 of the License, or (at your option) any later version.
*
* AUX 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
*/
/* jshint -W014 */
/* jshint -W079 */
import { defineChildWidget } from '../child_widget.js';
import { Container } from './container.js';
import { Icon } from './icon.js';
import { Label } from './label.js';
import { Button } from './button.js';
import { Drag } from '../modules/drag.js';
import { Resize } from '../modules/resize.js';
import { setGlobalCursor, unsetGlobalCursor } from '../utils/global_cursor.js';
import { translateAnchor } from '../utils/anchor.js';
import {
addClass,
removeClass,
outerWidth,
outerHeight,
positionLeft,
positionTop,
width as viewportWidth,
innerWidth,
innerHeight,
height as viewportHeight,
toggleClass,
setContent,
} from '../utils/dom.js';
import { defineRender, defineMeasure, deferRender } from '../renderer.js';
function headerAction() {
const that = this.parent;
switch (that.options.header_action) {
case 'shrink':
that.toggleShrink();
break;
case 'maximize':
that.toggleMaximize();
break;
case 'maximizehorizontal':
that.toggleMaximizeHorizontal();
break;
case 'maximizevertical':
that.toggleMaximizeVertical();
break;
case 'minimize':
that.toggleMinimize();
break;
case 'close':
that.destroyAndRemove();
break;
}
/**
* The user double-clicked on the header.
* @event Window.headeraction
* @param {string} action - The function which was executed, e.g. <code>shrink</code>, <code>maximize</code> or <code>close</code>.
*/
that.emit('headeraction', that.options.header_action);
}
function mout() {
if (this.options.auto_active && !this.dragging && !this.resizing)
removeClass(this.element, 'aux-active');
}
function mover() {
if (this.options.auto_active) addClass(this.element, 'aux-active');
}
function maxHeight() {
// returns the max height of the window
return this.options.max_height < 0
? Number.MAX_SAFE_INTEGER
: this.options.max_height;
}
function maxWidth() {
// returns the max width of the window
return this.options.max_width < 0
? Number.MAX_SAFE_INTEGER
: this.options.max_width;
}
function maxSize(size) {
return size < 0 ? Number.MAX_SAFE_INTEGER : size;
}
function clipSize(size, min, max) {
return Math.min(maxSize(max), Math.max(size, min));
}
function close() {
/**
* The user clicked the close button.
* @event Window.closeclicked
*/
this.emit('closeclicked');
if (this.options.auto_close) {
this.destroyAndRemove();
}
}
function maximize() {
if (this.options.auto_maximize) this.toggleMaximize();
/**
* The user clicked the maximize button.
* @event Window.maximizeclicked
* @param {Object} maximize - The maximize option.
*/
this.emit('maximizeclicked', this.options.maximize);
}
function maximizeVertical() {
if (this.options.auto_maximize) this.toggleMaximizeVertical();
/**
* The user clicked the maximize-vertical button.
* @event Window.maximizeverticalclicked
* @param {Object} maximize - The maximize option.
*/
this.emit('maximizeverticalclicked', this.options.maximize.y);
}
function maximizeHorizontal() {
if (this.options.auto_maximize) this.toggleMaximizeHorizontal();
/**
* The user clicked the maximize-horizontal button.
* @event Window.maximizehorizontalclicked
* @param {Object} maximize - The maximize option.
*/
this.emit('maximizehorizontalclicked', this.options.maximize.x);
}
function minimize() {
if (this.options.auto_minimize) this.toggleMinimize();
/**
* The user clicked the minimize button.
* @event Window.minimizeclicked
* @param {Object} minimize - The minimize option.
*/
this.emit('minimizeclicked', this.options.minimize);
}
function shrink() {
if (this.options.auto_shrink) this.toggleShrink();
/**
* The user clicked the shrink button.
* @event Window.shrinkclicked
* @param {Object} shrink - The shrink option.
*/
this.emit('shrinkclicked', this.options.shrink);
}
function startResize(el, ev) {
setGlobalCursor('se-resize');
this.resizing = true;
addClass(this.element, 'aux-resizing');
/**
* The user starts resizing the window.
* @event Window.startresize
* @param {DOMEvent} event - The DOM event.
*/
this.emit('startresize', ev);
}
function stopResize(el, ev) {
unsetGlobalCursor('se-resize');
this.resizing = false;
removeClass(this.element, 'aux-resizing');
this.triggerResizeChildren();
calculateDimensions.call(this);
/**
* The user stops resizing the window.
* @event Window.stopresize
* @param {DOMEvent} event - The DOM event.
*/
this.emit('stopresize', ev);
}
function resizing(el, ev) {
if (this.options.resizing === 'continuous') {
this.triggerResizeChildren();
calculateDimensions.call(this);
}
/**
* The user resizes the window.
* @event Window.resizing
* @param {DOMEvent} event - The DOM event.
*/
this.emit('resizing', ev);
}
function calculateDimensions() {
const x = outerWidth(this.element, true);
const y = outerHeight(this.element, true);
this.dimensions.width = this.options.width = x;
this.dimensions.height = this.options.height = y;
this.dimensions.x2 = x + this.dimensions.x1;
this.dimensions.y2 = y + this.dimensions.y1;
}
function calculatePosition() {
const posx = positionLeft(this.element);
const posy = positionTop(this.element);
const pos1 = translateAnchor(
this.options.anchor,
posx,
posy,
this.options.width,
this.options.height
);
this.dimensions.x = this.options.x = pos1.x;
this.dimensions.y = this.options.y = pos1.y;
this.dimensions.x1 = posx;
this.dimensions.y1 = posy;
this.dimensions.x2 = posx + this.dimensions.width;
this.dimensions.y2 = posy + this.dimensions.height;
}
function horizMax() {
// returns true if maximized horizontally
return this.options.maximize.x;
}
function vertMax() {
// returns if maximized vertically
return this.options.maximize.y;
}
function startDrag(ev) {
setGlobalCursor('move');
addClass(this.element, 'aux-dragging');
// if window is maximized, we have to replace the window according
// to the position of the mouse
let y = 0,
x = 0;
if (vertMax.call(this)) {
y = !this.options.fixed ? window.scrollY : 0;
}
if (horizMax.call(this)) {
x = ev.clientX - (ev.clientX / viewportWidth()) * this.options.width;
x += !this.options.fixed ? window.scrollX : 0;
}
const pos = translateAnchor(
this.options.anchor,
x,
y,
this.options.width,
this.options.height
);
if (horizMax.call(this)) this.options.x = pos.x;
if (vertMax.call(this)) this.options.y = pos.y;
this.drag._xpos += x;
this.drag._ypos += y;
/**
* The user starts dragging the window.
* @event Window.startdrag
* @param {DOMEvent} event - The DOM event.
*/
this.emit('startdrag', ev);
}
function stopDrag(ev) {
this.dragging = false;
calculatePosition.call(this);
unsetGlobalCursor('move');
/**
* The user stops dragging the window.
* @event Window.stopdrag
* @param {DOMEvent} event - The DOM event.
*/
this.emit('stopdrag', ev);
}
function dragging(ev) {
if (!this.dragging) {
this.dragging = true;
// un-maximize
if (horizMax.call(this)) {
this.set('maximize', { x: false });
}
if (vertMax.call(this)) {
this.set('maximize', { y: false });
}
}
calculatePosition.call(this);
/**
* The user is dragging the window.
* @event Window.dragging
* @param {DOMEvent} event - The DOM event.
*/
this.emit('dragging', ev);
}
function buildHeader() {
buildFromConst.call(this, 'header');
if (!this.drag) {
this.drag = new Drag({
node: this.element,
handle: this.header.element,
onStartdrag: startDrag.bind(this),
onStopdrag: stopDrag.bind(this),
onDragging: dragging.bind(this),
min: { x: 0 - this.options.width + 20, y: 0 },
max: { x: viewportWidth() - 20, y: viewportHeight() - 20 },
});
//this.header.on("dblclick", headerAction.bind(this));
}
/**
* The header changed.
* @event Window.headerchanged
*/
this.emit('headerchanged');
}
function buildFooter() {
buildFromConst.call(this, 'footer');
/**
* The footer changed.
* @event Window.footerchanged
*/
this.emit('footerchanged');
}
function buildFromConst(element) {
const E = this[element].element;
const L = this.options[element];
const O = this.options;
while (E.firstChild) E.firstChild.remove();
if (!L) return;
for (let i = 0; i < L.length; i++) {
if (L[i] !== 'spacer') {
this.set('show_' + L[i], true);
E.appendChild(this[L[i]].element);
if (L[i] === 'size' && !this.resize && this.size) {
this.resize = new Resize({
node: this.element,
handle: this.size.element,
min: { x: O.min_width, y: O.min_height },
max: { x: maxWidth.call(this), y: maxHeight.call(this) },
onResizestart: startResize.bind(this),
onResizestop: stopResize.bind(this),
onResizing: resizing.bind(this),
active: O.resizable,
});
}
} else {
E.appendChild(element('div', 'aux-spacer'));
}
}
}
function statusTimeout() {
const O = this.options;
if (this.__status_to !== false) window.clearTimeout(this.__status_to);
if (!O.hide_status) return;
if (O.status)
this.__status_to = window.setTimeout(
function () {
this.set('status', '');
this.__status_to = false;
}.bind(this),
O.hide_status
);
}
/**
* This widget is a flexible overlay window.
*
* @class Window
*
* @extends Container
*
* @param {Object} [options={ }] - An object containing initial options.
*
* @property {Number} [options.width=500] - Initial width, can be a CSS length or an integer (pixels).
* @property {Number} [options.height=200] - Initial height, can be a CSS length or an integer (pixels).
* @property {Number} [options.x=0] - X position of the window.
* @property {Number} [options.y=0] - Y position of the window.
* @property {Number} [options.min_width=64] - Minimum width of the window.
* @property {Number} [options.max_width=-1] - Maximum width of the window, -1 ~ infinite.
* @property {Number} [options.min_height=64] - Minimum height of the window.
* @property {Number} [options.max_height=-1] - Maximum height of the window, -1 ~ infinite.
* @property {String} [options.anchor="top-left"] - Anchor of the window, can be one out of
* `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right`
* @property {Boolean} [options.modal=false] - If modal window blocks all other elements
* @property {String} [options.dock=false] - Docking of the window, can be one out of
* `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right`
* @property {Object|Boolean} [options.maximize=false] - Boolean or object with members <code>x</code> and <code>y</code> as boolean to determine the maximized state.
* @property {Boolean} [options.minimize=false] - Minimize window (does only make sense with a
* window manager application to keep track of it)
* @property {Boolean} [options.shrink=false] - Shrink rolls the window up into the title bar.
* @property {String|HTMLElement|Container} [options.content=""] - The content of the window.
* Can be either a string, a HTMLElement or a {@link Container} to append to the content area.
* @property {String} [options.open="center"] - initial position of the window, can be one out of
* `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right`
* @property {Integer} [options.z_index=10000] - Z index for piling windows. does make more sense
* when used together with a window manager
* @property {String|Array<String>} [options.header=["title", "maximize", "close"]] - Single element or array of
* `title`, `icon`, `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal`, `status`, `resize`, `spacer`.
* @property {String|Array<String>} [options.footer=false] - Single element or array of
* `title`, `icon`, `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal`, `status`, `resize`, `spacer`.
* @property {String} [options.title=false] - Window title.
* @property {String} [options.status=false] Window status.
* @property {String} [options.icon=false] URL to window icon.
* @property {Boolean} [options.fixed=true] - Whether the window sticks to the viewport rather than the document
* @property {Boolean} [options.auto_active=false] - Auto-toggle the active-class when mouseovered
* @property {Boolean} [options.auto_close=true] - Set whether close destroys the window or not
* @property {Boolean} [options.auto_maximize=true] - Set whether maximize toggles the window or not
* @property {Boolean} [options.auto_minimize=true] - Set whether minimize toggles the window or not
* @property {Boolean} [options.auto_shrink=true] - Set whether shrink toggles the window or not
* @property {Boolean} [options.draggable=true] - Set whether the window is draggable
* @property {Boolean} [options.resizable=true] - Set whether the window is resizable
* @property {String} [options.resizing="continuous"] - Resizing policy, `continuous` or `stop`.
* The first one resizes all children continuously while resizing.
* @property {String} [options.header_action="maximize"] - Action for double clicking the window header, one out of
* `close`, `minimize`, `shrink`, `maximize`, `maximizevertical`, `maximizehorizontal`
* @property {Boolean} [options.active=true] - Active state of the window.
* @property {Integer} [options.hide_status=0] - If set to !0 status message hides after [n] milliseconds.
*/
/**
* @member {Drag} Window#drag - The {@link Drag} module.
*/
/**
* @member {Resize} Window#resize - The {@link Resize} module.
*/
export class Window extends Container {
static get _options() {
return {
width: 'number',
height: 'number',
x: 'number',
y: 'number',
min_width: 'number',
max_width: 'number',
min_height: 'number',
max_height: 'number',
anchor: 'string',
modal: 'boolean',
dock: 'boolean',
maximize: 'boolean',
minimize: 'boolean',
shrink: 'boolean',
open: 'int',
z_index: 'int',
header: 'array',
footer: 'array',
title: 'string',
status: 'string',
icon: 'string',
fixed: 'boolean',
auto_active: 'boolean',
auto_close: 'boolean',
auto_maximize: 'boolean',
auto_minimize: 'boolean',
auto_shrink: 'boolean',
draggable: 'boolean',
resizable: 'boolean',
resizing: 'int',
header_action: 'string',
active: 'boolean',
hide_status: 'int',
};
}
static get options() {
return {
width: 500,
height: 200,
x: 0,
y: 0,
min_width: 64,
max_width: -1,
min_height: 64,
max_height: -1,
anchor: 'top-left',
modal: false,
dock: false,
maximize: false,
minimize: false,
shrink: false,
content: '',
open: 'center',
z_index: 10000,
header: ['title', 'maximize', 'close'],
footer: false,
title: false,
status: false,
icon: false,
fixed: true,
auto_active: false,
auto_close: true,
auto_maximize: true,
auto_minimize: true,
auto_shrink: true,
draggable: true,
resizable: true,
resizing: 'continuous',
header_action: 'maximize',
active: true,
hide_status: 0,
role: 'dialog',
};
}
static get static_events() {
return {
mouseenter: mover,
mouseleave: mout,
};
}
static get renderers() {
return [
defineMeasure(
[
'width',
'height',
'min_width',
'min_height',
'max_width',
'max_height',
'maximize',
],
function (
width,
height,
min_width,
min_height,
max_width,
max_height,
maximize
) {
const { dimensions, element } = this;
let setWidth, setHeight;
if (width >= 0) {
width = clipSize(width, min_width, max_width);
this.set('width', width);
if (maximize.x) {
dimensions.width = viewportWidth();
} else {
dimensions.width = width;
}
setWidth = true;
} else {
dimensions.width = outerWidth(element);
}
if (height >= 0) {
height = clipSize(height, min_height, max_height);
if (maximize.y) {
dimensions.height = viewportHeight();
} else {
dimensions.height = height;
}
setHeight = true;
} else {
dimensions.height = outerHeight(element, true);
}
dimensions.x2 = dimensions.x1 + dimensions.width;
dimensions.y2 = dimensions.y1 + dimensions.height;
/**
* The dimensions of the window changed.
* @event Window.dimensionschanged
* @param {Object} event - The {@link Window#dimensions} dimensions object.
*/
this.emit('dimensionschanged', this.dimensions);
if (!setWidth && !setHeight) return null;
return deferRender(() => {
if (setWidth) outerWidth(element, true, dimensions.width);
if (setHeight) outerHeight(element, true, dimensions.height);
this.triggerResize();
});
}
),
defineMeasure(
[
'anchor',
'x',
'y',
'_inner_width',
'_inner_height',
'maximize',
'fixed',
],
function (anchor, x, y, _inner_width, _inner_height, maximize, fixed) {
const { element, dimensions } = this;
const pos = translateAnchor(
anchor,
x,
y,
-_inner_width,
-_inner_height
);
const left = maximize.x ? (fixed ? 0 : window.scrollX) : pos.x;
const top = maximize.y ? (fixed ? 0 : window.scrollY) : pos.y;
Object.assign(dimensions, {
x,
y,
x1: pos.x,
y1: pos.y,
x2: pos.x + dimensions.width,
y2: pos.y + dimensions.height,
});
/**
* The position of the window changed.
* @event Window.positionchanged
* @param {Object} event - The {@link Window#dimensions} dimensions object.
*/
this.emit('positionchanged', dimensions);
return deferRender(() => {
element.style.left = left + 'px';
element.style.top = top + 'px';
});
}
),
defineRender('maximize', function (maximize) {
toggleClass(this.element, 'aux-maximized-horizontal', maximize.x);
toggleClass(this.element, 'aux-maximized-vertical', maximize.y);
}),
defineRender('z_index', function (z_index) {
this.element.style.zIndex = z_index;
}),
defineRender(['header', 'show_header'], function (header, show_header) {
if (header && show_header) buildHeader.call(this);
}),
defineRender(['footer', 'show_footer'], function (footer, show_footer) {
if (footer && show_footer) buildFooter.call(this);
}),
defineMeasure(['status', 'hide_status'], function (status, hide_status) {
statusTimeout.call(this);
}),
defineRender('fixed', function (fixed) {
this.element.style.position = fixed ? 'fixed' : 'absolute';
}),
defineRender('active', function (active) {
toggleClass(this.element, 'aux-active', active);
}),
defineRender('shrink', function (shrink) {
toggleClass(this.element, 'aux-shrinked', shrink);
}),
defineRender('draggable', function (draggable) {
toggleClass(this.element, 'aux-draggable', draggable);
}),
defineRender('resizable', function (resizable) {
toggleClass(this.element, 'aux-resizable', resizable);
}),
defineRender('content', function (content) {
if (content) {
if (
Object.prototype.isPrototypeOf.call(Container.prototype, content)
) {
setContent(this.content.element, '');
this.appendChild(content);
} else {
setContent(this.content.element, content);
}
}
this.triggerResize();
}),
];
}
initialize(options) {
this.dimensions = {
anchor: 'top-left',
x: 0,
x1: 0,
x2: 0,
y: 0,
y1: 0,
y2: 0,
width: 0,
height: 0,
};
super.initialize(options);
this.__status_to = false;
this.set('maximize', this.options.maximize);
this.set('minimize', this.options.minimize);
}
/**
* Appends a new child to the window content area.
* @method Window#appendChild
* @param {Widget} child - The child widget to add to the windows content area.
*/
appendChild(child) {
this.content.appendChild(child.element);
this.addChild(child);
}
/**
* Toggles the overall maximize state of the window.
* @method Window#toggleMaximize
* @param {Boolean} maximize - State of maximization. If window is already
* maximized in one or both directions it is un-maximized, otherwise maximized.
*/
toggleMaximize() {
if (!vertMax.call(this) || !horizMax.call(this))
this.set('maximize', { x: true, y: true });
else this.set('maximize', { x: false, y: false });
}
/**
* Toggles the vertical maximize state of the window.
* @method Window#toggleMaximizeVertical
* @param {Boolean} maximize - The new vertical maximization.
*/
toggleMaximizeVertical() {
this.set('maximize', { y: !this.options.maximize.y });
}
/**
* Toggles the horizontal maximize state of the window.
* @method Window#toggleMaximizeHorizontal
* @param {Boolean} maximize - The new horizontal maximization.
*/
toggleMaximizeHorizontal() {
this.set('maximize', { x: !this.options.maximize.x });
}
/**
* Toggles the minimize state of the window.
* @method Window#toggleMinimize
* @param {Boolean} minimize - The new minimization.
*/
toggleMinimize() {
this.set('minimize', !this.options.minimize);
}
/**
* Toggles the shrink state of the window.
* @method Window#toggleShrink
* @param {Boolean} shrink - The new shrink state.
*/
toggleShrink() {
this.set('shrink', !this.options.shrink);
}
getResizeTargets() {
return [this.element];
}
resize() {
this.drag.set('min', { x: 0 - this.options.width + 20, y: 0 });
this.drag.set('max', { x: viewportWidth() - 20, y: viewportHeight() - 20 });
this.set('_inner_width', innerWidth(this.element));
this.set('_inner_height', innerHeight(this.element));
super.resize();
}
draw(O, element) {
addClass(element, 'aux-window');
super.draw(O, element);
const { open, width, height, fixed, anchor } = O;
if (open) {
const x0 = fixed ? 0 : window.scrollX;
const y0 = fixed ? 0 : window.scrollY;
const pos1 = translateAnchor(
open,
x0,
y0,
window.innerWidth - width,
window.innerHeight - height
);
const pos2 = translateAnchor(anchor, pos1.x, pos1.y, width, height);
this.set('x', pos2.x);
this.set('y', pos2.y);
}
}
set(key, value) {
if (key === 'maximize') {
if (value === false) value = { x: false, y: false };
else if (value === true) value = { x: true, y: true };
else value = Object.assign({}, this.get('maximize'), value);
}
value = super.set(key, value);
switch (key) {
case 'maximize':
if ((value.y || value.x) && this.get('shrink')) {
this.set('shrink', false);
}
break;
case 'shrink': {
const maximize = this.get('maximize');
if (value && maximize.y) {
this.set('maximize', { y: false });
}
break;
}
case 'minimize':
this.set('visible', !value);
break;
case 'resizable':
this.resize.set('active', value);
break;
}
return value;
}
}
/**
* @member {Icon} Window#icon - A {@link Icon} widget to display the window icon.
*/
defineChildWidget(Window, 'icon', {
create: Icon,
map_options: { icon: 'icon' },
toggle_class: true,
});
/**
* @member {Label} Window#title - A {@link Label} to display the window title.
*/
defineChildWidget(Window, 'title', {
create: Label,
default_options: { class: 'aux-title' },
map_options: { title: 'label' },
toggle_class: true,
});
/**
* @member {Label} Window#status - A {@link Label} to display the window status.
*/
defineChildWidget(Window, 'status', {
create: Label,
default_options: { class: 'aux-status' },
map_options: { status: 'label' },
toggle_class: true,
});
/**
* @member {Button} Window#close - The close button.
*/
/**
* @member {Button} Window#minimize - The minimize button.
*/
/**
* @member {Button} Window#maximize - The maximize button.
*/
/**
* @member {Button} Window#maximizevertical - The maximizevertical button.
*/
/**
* @member {Button} Window#maximizehorizontal - The maximizehorizontal button.
*/
/**
* @member {Button} Window#shrink - The shrink button.
*/
function bFactory(name, handler) {
defineChildWidget(Window, name, {
create: Button,
default_options: {
class: 'aux-' + name,
icon: 'window' + name,
},
static_events: {
click: function (e) {
handler.call(this.parent, e);
},
mousedown: function (e) {
e.stopPropagation();
},
},
});
}
bFactory('close', close);
bFactory('minimize', minimize);
bFactory('maximize', maximize);
bFactory('maximizevertical', maximizeVertical);
bFactory('maximizehorizontal', maximizeHorizontal);
bFactory('shrink', shrink);
/**
* @member {Icon} Window#size - A {@link Icon} acting as handle for window resize.
*/
defineChildWidget(Window, 'size', {
create: Icon,
default_options: { icon: 'windowresize', class: 'aux-size' },
});
/**
* @member {Container} Window#content - A {@link Container} for the window content.
*/
defineChildWidget(Window, 'content', {
create: Container,
toggle_class: true,
show: true,
default_options: { class: 'aux-content' },
});
/**
* @member {Container} Window#header - The top header bar.
*/
defineChildWidget(Window, 'header', {
create: Container,
toggle_class: true,
show: true,
default_options: { class: 'aux-header' },
static_events: {
dblclick: headerAction,
},
append: function () {
buildHeader.call(this);
this.element.appendChild(this.header.element);
},
});
/**
* @member {Container} Window#footer - The bottom footer bar.
*/
defineChildWidget(Window, 'footer', {
create: Container,
toggle_class: true,
show: false,
default_options: { class: 'aux-footer' },
append: function () {
buildFooter.call(this);
this.element.appendChild(this.footer.element);
},
});