windowmanager
Version:
A framework to manage multiple dockable, HTML windows
477 lines (389 loc) • 14.8 kB
JavaScript
/**
* A library for laying out windowmanager windows.
*/
import Window from '../Window.js';
import windowmanager from '../global';
let ACTIVE_WINDOW_DIV_ID = 'active-window-container';
const TABBED_LAYOUT_DIV_ID = 'tabbed-layout-container';
const TAB_LIST_CONTAINER_ID = 'layout-tabs-list-container';
const TAB_LIST_ID = 'layout-tabs-list';
let TAB_DIV_HEIGHT = '25px'; // Can be overwritten by developer.
/**
* A Layout class, used to create a layout of {@link Window} objects.
*
* <h5>Example:</h5>
* ```javascript
* // Create window(s).
* let state = {
* width: 400,
* height: 400,
* url: 'child.html',
* title: 'Window X',
* frame: false
* };
*
* // Make an array of windows.
* let configs = [state, state, state];
*
* // Create the layout.
* let layout = new windowmanager.Layout('tabbed', 'layout-div', configs);
* ```
*/
class Layout {
/**
* Constructor for the layout class.
*
* <h5>Example:</h5>
* ```javascript
* // Create the layout.
* let layout = new windowmanager.Layout('tiled', 'layout-div', configs);
* ```
*
* @param {string} type - The type of layout, defaults to tiled
* @param {string} id - The id of the element to attach to, if none provided the layout will attach to the body
* @param {Array.Object} configs - The config objects to create the windows from
* @param {String} tabHeight - If in tabbed view, the height of the tab toolbar. Used to offset the active window div.
*/
constructor(type, id, configs, tabHeight) {
// If no type is defined let the user know that it will default to tiled.
if (!type) {
console.warn('Type not provided, defaulting to tiled view.');
}
// If no configs given, error out.
if (!configs || configs.length === 0) {
console.error('Must provide window configurations in constructor.');
return;
}
// If configs is only one object, convert it to an array.
if (!Array.isArray(configs)) {
console.warn('Parameter configs should be an array of window configuration objects.');
configs = [configs];
}
// Set the internal tab toolbar height if supplied.
if (tabHeight) {
TAB_DIV_HEIGHT = tabHeight.indexOf('px') > 0 ? tabHeight : tabHeight + 'px';
}
this._windows = [];
// Give the active window div a unique id.
ACTIVE_WINDOW_DIV_ID += '-' + type + '-' + windowmanager.Layout.getAll().length;
// Create the layout based on the type given.
switch (type) {
case 'tabbed':
this._tabbed(configs, id);
break;
default:
this._tiled(configs, id);
break;
}
this._layoutType = type;
// setAttribute('scrolling', 'no') ??? HTK
windowmanager._layouts.set(id, this);
window.addEventListener('resize', function (event) {
window.document.getElementById(TABBED_LAYOUT_DIV_ID).setAttribute('width', window.outerWidth);
windowmanager.Layout.getAllTabbed()[0]._windows.forEach(subWindow=> {
subWindow.resizeTo(window.outerWidth, window.outerHeight);
});
});
}
/**
* Function to retrieve all windows being managed.
*
* @returns {Array.Window}
*/
getWindows() {
return this._windows.slice();
}
/**
* Method to get a window by its id.
*
* @param {string} id - the id of the window to find.
*
* @returns {Window}
*/
getWindow(id) {
let ret;
if (!id) {
console.error('No id provided for window to find.');
return ret;
}
this.getWindows().some((window) => {
if (window._id === id) {
ret = window;
return true;
}
});
return ret;
}
/**
* Function to add a window to the layout scheme.
* @param {Object} config - The configuration object for the window
*
* @returns {Windowlo}
*/
addWindow(config) {
let newWindow;
if (!config) {
console.error('Must provide configuration object to create window from.');
return;
}
// Create a list element for each window.
if (this._layoutType === 'tiled') {
let layoutItem = this._createTiledLayoutItem();
config.container = layoutItem;
newWindow = new Window(config);
// Set up an onclose listener for the window.
let that = this;
newWindow.on('close', function () {
that.removeWindow(this._id);
});
this._windows.push(newWindow);
} else if (this._layoutType === 'tabbed') {
// Set up the config to have the active window as its container and to be hidden on start.
config.container = ACTIVE_WINDOW_DIV_ID;
config.show = false;
// Create the window.
newWindow = new Window(config);
// Create the tab for the window.
this._createTabbedLayoutItem(newWindow._title, newWindow._id);
newWindow.resizeTo(window.outerWidth, window.outerHeight);
// Set up an on close listener for the window.
let that = this;
newWindow.on('close', function () {
that.removeWindow(this._id);
});
this._windows.push(newWindow);
// Set the newly created window to be the active window.
this._changeActiveWindow(newWindow._id);
}
return newWindow;
}
/**
* Function to remove a window from the layout scheme.
* @param {String} id - The id of the window to remove.
*/
removeWindow(id) {
if (!id) {
console.error('Must provide id of window to remove.');
return;
}
this._windows.some((window, idx) => {
if (window._id === id) {
this._windows.splice(idx, 1);
// Call close on the window to ensure its removed from the DOM.
// If the window was removed via removeWindow programatically,
// it may not have been removed from the DOM yet.
window.close();
// If in tabbed view, remove the element from the tab list.
if (this._layoutType === 'tabbed') {
let tabElem = document.getElementById('tab-' + window._id);
this._list.removeChild(tabElem);
this._changeActiveWindow(this._windows[0]._id);
}
return true;
}
});
}
/**
* Returns a list of all {@link Layout} instances open.
* <h5>Example:</h5>
* ```javascript
* // Close all windows:
* let allWindows = windowmanager.Layout.getAll();
* ```
* @returns {Window[]}
*/
static getAll() {
return Array.from(windowmanager._layouts.values());
}
/**
* Returns all tab style {@link Layout} instances open.
* <h5>Example:</h5>
* ```javascript
* let allTabSets = windowmanager.Layout.getAllTabbed();
* ```
* @returns {Layout[]}
*/
static getAllTabbed() {
return Array.from(windowmanager._layouts.values().filter((item)=>{
return item._layoutType === 'tabbed';
})
);
}
/**
* Returns the {@link Layout} instance that has `id`.
* <h5>Example:</h5>
* ```javascript
* // Get layout with ID:
* let layout = windowmanager.Layout.getById(windowID);
* ```
* @param {String|Number}
* @returns {Layout|undefined}
*/
static getByID(id) {
return windowmanager._layouts.get(id);
}
/* Private methods */
/**
* Method for creating a tiled layout of windows.
* @param {Array.Object} configs - The config objects to create the windows from
* @param {string} id - The id of the element to attach to, if none provided the layout will attach to the body
*/
_tiled(configs, id) {
if (!configs || !Array.isArray(configs) || configs.length === 0) {
console.error('Must provide array of configuration objects to create windows from.');
return;
}
// Save the this context to be used when removing a window.
let that = this;
// Create the div to host the windows.
let layoutDiv = document.createElement('div');
let layoutList = document.createElement('ul');
layoutDiv.setAttribute('id', 'tiled-layout-container');
if (id) {
let container = document.getElementById(id);
container.appendChild(layoutDiv);
} else {
document.body.appendChild(layoutDiv);
}
layoutDiv.appendChild(layoutList);
// Keep a reference to the list for adding new windows.
this._list = layoutList;
// Keep a reference to the container.
this._container = layoutDiv;
// Create the windows.
configs.forEach((config) => {
// Create a list element for each window.
let layoutItem = this._createTiledLayoutItem();
// Set the windows container to be the list item.
config.container = layoutItem;
// Create the new window.
let newWindow = new Window(config);
// Set up an onclose listener for the window.
newWindow.on('close', function () {
that.removeWindow(this._id);
});
// Add to our windows store.
this._windows.push(newWindow);
});
}
/**
* Method for creating a tabbed layout.
* @param {Array.Object} configs - The config objects to create the windows from
* @param {string} id - The id of the element to attach to, if none provided the layout will attach to the body
*/
_tabbed(configs, id) {
if (!configs || !Array.isArray(configs) || configs.length === 0) {
console.error('Must provide array of configuration objects to create windows from.');
return;
}
// Create the outer container.
let layoutDiv = document.createElement('div');
// Create the tabs div.
let tabDiv = document.createElement('div');
// Create the window div.
let activeWindowDiv = document.createElement('div');
// Create the tabs ul.
let tabList = document.createElement('ul');
layoutDiv.setAttribute('id', TABBED_LAYOUT_DIV_ID);
activeWindowDiv.setAttribute('id', ACTIVE_WINDOW_DIV_ID);
tabDiv.setAttribute('id', TAB_LIST_CONTAINER_ID);
tabList.setAttribute('id', TAB_LIST_ID);
// Set up the fixed tab bar.
tabDiv.style.position = 'fixed';
tabDiv.style.top = 0;
tabDiv.style.zIndex = 1000;
activeWindowDiv.style.marginTop = TAB_DIV_HEIGHT;
this._list = tabList;
if (id) {
let container = document.getElementById(id);
container.appendChild(layoutDiv);
} else {
document.body.appendChild(layoutDiv);
}
layoutDiv.appendChild(tabDiv);
layoutDiv.appendChild(activeWindowDiv);
tabDiv.appendChild(tabList);
let that = this;
// Create the windows.
configs.forEach((config) => {
// Set up the config to have the active window as its container and to be hidden on start.
config.container = ACTIVE_WINDOW_DIV_ID;
config.show = false;
// Create the window.
let newWindow = new Window(config);
// Set up an onclose listener for the window.
newWindow.on('close', function () {
that.removeWindow(this._id);
});
// Create the tab for the window.
this._createTabbedLayoutItem(newWindow._title, newWindow._id);
// Add the window to the internal windows store.
this._windows.push(newWindow);
});
this._windows.forEach(subWindow=>{
subWindow.resizeTo(window.outerWidth, window.outerHeight);
});
// Change the active window.
this._changeActiveWindow(this._windows[0]._id);
}
/**
* Method for creating a list element to add a window to.
*/
_createTiledLayoutItem() {
let layoutItem = document.createElement('li');
layoutItem.style.display = 'inline-block';
layoutItem.style.padding = '10px';
this._list.appendChild(layoutItem);
return layoutItem;
}
/**
* Method for creating a tab in the tabbed layout.
*
* @param {string} title - The title of the window being created to display in the tab's text
* @param {string} id - The id of the window being created
*/
_createTabbedLayoutItem(title, id) {
let layoutItem = document.createElement('li');
layoutItem.style.display = 'inline-block';
layoutItem.style.padding = '10px';
layoutItem.style.border = '2px solid black';
layoutItem.innerText = title;
layoutItem.setAttribute('id', 'tab-' + id);
// Set up the onclick listener to load the window into the activeWindow tab.
layoutItem.onclick = () => {
this._changeActiveWindow.call(this, id);
};
this._list.appendChild(layoutItem);
return layoutItem;
}
/**
* Method for changing the active window.
*
* @param {string} id - The id of the window to show
*/
_changeActiveWindow(id) {
if (!id) {
console.error('Must provide id to change window to.');
return;
}
if (id === this._activeWindowId) { return; }
let newActiveWindow = this.getWindow(id);
if (this._activeWindowId) {
let oldActiveWindow = this.getWindow(this._activeWindowId);
if (oldActiveWindow) {
// Remove the active class from the old active tab.]
let oldActiveTab = document.getElementById('tab-' + oldActiveWindow._id);
oldActiveTab.classList.remove('active-tab');
oldActiveWindow.hide();
}
}
if (newActiveWindow) {
// Add the active class to the tab that is active.
let activeTab = document.getElementById('tab-' + id);
activeTab.classList.add('active-tab');
newActiveWindow.show();
this._activeWindowId = id;
}
}
};
export default Layout;