diff --git a/.bundlewatch.config.json b/.bundlewatch.config.json index 85607587b..ca77cb92f 100644 --- a/.bundlewatch.config.json +++ b/.bundlewatch.config.json @@ -2,11 +2,11 @@ "files": [ { "path": "./dist/css/adminlte.css", - "maxSize": "81.5 kB" + "maxSize": "82 kB" }, { "path": "./dist/css/adminlte.min.css", - "maxSize": "77.5 kB" + "maxSize": "77.8 kB" }, { "path": "./dist/css/alt/adminlte.components.css", @@ -14,7 +14,7 @@ }, { "path": "./dist/css/alt/adminlte.components.min.css", - "maxSize": "15 kB" + "maxSize": "15.4 kB" }, { "path": "./dist/css/alt/adminlte.core.css", @@ -46,15 +46,15 @@ }, { "path": "./dist/css/alt/adminlte.plugins.min.css", - "maxSize": "16 kB" + "maxSize": "16.6 kB" }, { "path": "./dist/js/adminlte.js", - "maxSize": "12 kB" + "maxSize": "15 kB" }, { "path": "./dist/js/adminlte.min.js", - "maxSize": "8 kB" + "maxSize": "10.5 kB" } ] } diff --git a/build/js/AdminLTE.js b/build/js/AdminLTE.js index 7f854ddf1..a6906ca98 100644 --- a/build/js/AdminLTE.js +++ b/build/js/AdminLTE.js @@ -5,6 +5,7 @@ import DirectChat from './DirectChat' import Dropdown from './Dropdown' import ExpandableTable from './ExpandableTable' import Fullscreen from './Fullscreen' +import IFrame from './IFrame' import Layout from './Layout' import PushMenu from './PushMenu' import SidebarSearch from './SidebarSearch' @@ -20,6 +21,7 @@ export { Dropdown, ExpandableTable, Fullscreen, + IFrame, Layout, PushMenu, SidebarSearch, diff --git a/build/js/IFrame.js b/build/js/IFrame.js new file mode 100644 index 000000000..de8c4a43e --- /dev/null +++ b/build/js/IFrame.js @@ -0,0 +1,356 @@ +/** + * -------------------------------------------- + * AdminLTE IFrame.js + * License MIT + * -------------------------------------------- + */ + +import $ from 'jquery' + +/** + * Constants + * ==================================================== + */ + +const NAME = 'IFrame' +const DATA_KEY = 'lte.iframe' +const JQUERY_NO_CONFLICT = $.fn[NAME] + +const SELECTOR_DATA_TOGGLE = '[data-widget="iframe"]' +const SELECTOR_DATA_TOGGLE_CLOSE = '[data-widget="iframe-close"]' +const SELECTOR_DATA_TOGGLE_SCROLL_LEFT = '[data-widget="iframe-scrollleft"]' +const SELECTOR_DATA_TOGGLE_SCROLL_RIGHT = '[data-widget="iframe-scrollright"]' +const SELECTOR_DATA_TOGGLE_FULLSCREEN = '[data-widget="iframe-fullscreen"]' +const SELECTOR_CONTENT_WRAPPER = '.content-wrapper' +const SELECTOR_CONTENT_IFRAME = `${SELECTOR_CONTENT_WRAPPER} iframe` +const SELECTOR_TAB_NAV = `${SELECTOR_DATA_TOGGLE}.iframe-mode .nav` +const SELECTOR_TAB_NAVBAR_NAV = `${SELECTOR_DATA_TOGGLE}.iframe-mode .navbar-nav` +const SELECTOR_TAB_NAVBAR_NAV_ITEM = `${SELECTOR_TAB_NAVBAR_NAV} .nav-item` +const SELECTOR_TAB_CONTENT = `${SELECTOR_DATA_TOGGLE}.iframe-mode .tab-content` +const SELECTOR_TAB_EMPTY = `${SELECTOR_TAB_CONTENT} .tab-empty` +const SELECTOR_TAB_LOADING = `${SELECTOR_TAB_CONTENT} .tab-loading` +const SELECTOR_SIDEBAR_MENU_ITEM = '.main-sidebar .nav-item > a.nav-link' +const SELECTOR_HEADER_MENU_ITEM = '.main-header .nav-item a.nav-link' +const SELECTOR_HEADER_DROPDOWN_ITEM = '.main-header a.dropdown-item' +const CLASS_NAME_IFRAME_MODE = 'iframe-mode' +const CLASS_NAME_FULLSCREEN_MODE = 'iframe-mode-fullscreen' + +const Default = { + onTabClick(item) { + return item + }, + onTabChanged(item) { + return item + }, + onTabCreated(item) { + return item + }, + autoIframeMode: true, + autoItemActive: true, + autoShowNewTab: true, + loadingScreen: true, + useNavbarItems: true, + scrollOffset: 40, + scrollBehaviorSwap: false, + iconMaximize: 'fa-expand', + iconMinimize: 'fa-compress' +} + +/** + * Class Definition + * ==================================================== + */ + +class IFrame { + constructor(element, config) { + this._config = config + this._element = element + + this._init() + } + + // Public + + onTabClick(item) { + this._config.onTabClick(item) + } + + onTabChanged(item) { + this._config.onTabChanged(item) + } + + onTabCreated(item) { + this._config.onTabCreated(item) + } + + createTab(title, link, uniqueName, autoOpen) { + const tabId = `panel-${uniqueName}-${Math.floor(Math.random() * 1000)}` + const navId = `tab-${uniqueName}-${Math.floor(Math.random() * 1000)}` + + const newNavItem = `` + $(SELECTOR_TAB_NAVBAR_NAV).append(newNavItem) + + const newTabItem = `
` + $(SELECTOR_TAB_CONTENT).append(newTabItem) + + if (autoOpen) { + if (this._config.loadingScreen) { + const $loadingScreen = $(SELECTOR_TAB_LOADING) + $loadingScreen.fadeIn() + $(`${tabId} iframe`).ready(() => { + if (typeof this._config.loadingScreen === 'number') { + this.switchTab(`#${navId}`, this._config.loadingScreen) + setTimeout(() => { + $loadingScreen.fadeOut() + }, this._config.loadingScreen) + } else { + this.switchTab(`#${navId}`, this._config.loadingScreen) + $loadingScreen.fadeOut() + } + }) + } else { + this.switchTab(`#${navId}`) + } + } + + this.onTabCreated($(`#${navId}`)) + } + + openTabSidebar(item, autoOpen = this._config.autoShowNewTab) { + let $item = $(item).clone() + if ($item.attr('href') === undefined) { + $item = $(item).parent('a').clone() + } + + $item.find('.right').remove() + let title = $item.find('p').text() + if (title === '') { + title = $item.text() + } + + const link = $item.attr('href') + if (link === '#' || link === '' || link === undefined) { + return + } + + this.createTab(title, link, link.replace('.html', '').replace('./', '').replaceAll('/', '-'), autoOpen) + } + + switchTab(item) { + const $item = $(item) + const tabId = $item.attr('href') + + $(SELECTOR_TAB_EMPTY).hide() + $(`${SELECTOR_TAB_NAVBAR_NAV} .active`).tab('dispose').removeClass('active') + this._fixHeight() + + $item.tab('show') + $item.parents('li').addClass('active') + this.onTabChanged($item) + + if (this._config.autoItemActive) { + this._setItemActive($(`${tabId} iframe`).attr('src')) + } + } + + removeActiveTab() { + const $navItem = $(`${SELECTOR_TAB_NAVBAR_NAV_ITEM}.active`) + const $navItemParent = $navItem.parent() + const navItemIndex = $navItem.index() + $navItem.remove() + $('.tab-pane.active').remove() + + if ($(SELECTOR_TAB_CONTENT).children().length == $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}`).length) { + $(SELECTOR_TAB_EMPTY).show() + } else { + const prevNavItemIndex = navItemIndex - 1 + this.switchTab($navItemParent.children().eq(prevNavItemIndex).find('a')) + } + } + + toggleFullscreen() { + if ($('body').hasClass(CLASS_NAME_FULLSCREEN_MODE)) { + $(`${SELECTOR_DATA_TOGGLE_FULLSCREEN} i`).removeClass(this._config.iconMinimize).addClass(this._config.iconMaximize) + $('body').removeClass(CLASS_NAME_FULLSCREEN_MODE) + $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}`).height('auto') + $(SELECTOR_CONTENT_WRAPPER).height('auto') + $(SELECTOR_CONTENT_IFRAME).height('auto') + } else { + $(`${SELECTOR_DATA_TOGGLE_FULLSCREEN} i`).removeClass(this._config.iconMaximize).addClass(this._config.iconMinimize) + $('body').addClass(CLASS_NAME_FULLSCREEN_MODE) + } + + $(window).trigger('resize') + this._fixHeight(true) + } + + // Private + + _init() { + if (window.frameElement && this._config.autoIframeMode) { + $('body').addClass(CLASS_NAME_IFRAME_MODE) + } else if ($(SELECTOR_CONTENT_WRAPPER).hasClass(CLASS_NAME_IFRAME_MODE)) { + this._setupListeners() + this._fixHeight(true) + } + } + + _navScroll(offset) { + const leftPos = $(SELECTOR_TAB_NAVBAR_NAV).scrollLeft() + $(SELECTOR_TAB_NAVBAR_NAV).animate({ scrollLeft: (leftPos + offset) }, 250, 'linear') + } + + _setupListeners() { + $(window).on('resize', () => { + setTimeout(() => { + this._fixHeight() + }, 1) + }) + $(document).on('click', SELECTOR_SIDEBAR_MENU_ITEM, e => { + e.preventDefault() + this.openTabSidebar(e.target) + }) + + if (this._config.useNavbarItems) { + $(document).on('click', `${SELECTOR_HEADER_MENU_ITEM}, ${SELECTOR_HEADER_DROPDOWN_ITEM}`, e => { + e.preventDefault() + this.openTabSidebar(e.target) + }) + } + + $(document).on('click', SELECTOR_TAB_NAVBAR_NAV_ITEM, e => { + e.preventDefault() + this.onTabClick(e.target) + this.switchTab(e.target) + }) + $(document).on('click', SELECTOR_DATA_TOGGLE_CLOSE, e => { + e.preventDefault() + this.removeActiveTab() + }) + $(document).on('click', SELECTOR_DATA_TOGGLE_FULLSCREEN, e => { + e.preventDefault() + this.toggleFullscreen() + }) + let mousedown = false + let mousedownInterval = null + $(document).on('mousedown', SELECTOR_DATA_TOGGLE_SCROLL_LEFT, e => { + e.preventDefault() + clearInterval(mousedownInterval) + + let { scrollOffset } = this._config + + if (!this._config.scrollBehaviorSwap) { + scrollOffset = -scrollOffset + } + + mousedown = true + this._navScroll(scrollOffset) + + mousedownInterval = setInterval(() => { + this._navScroll(scrollOffset) + }, 250) + }) + $(document).on('mousedown', SELECTOR_DATA_TOGGLE_SCROLL_RIGHT, e => { + e.preventDefault() + clearInterval(mousedownInterval) + + let { scrollOffset } = this._config + + if (this._config.scrollBehaviorSwap) { + scrollOffset = -scrollOffset + } + + mousedown = true + this._navScroll(scrollOffset) + + mousedownInterval = setInterval(() => { + this._navScroll(scrollOffset) + }, 250) + }) + $(document).on('mouseup', () => { + if (mousedown) { + mousedown = false + clearInterval(mousedownInterval) + mousedownInterval = null + } + }) + } + + _setItemActive(href) { + $(`${SELECTOR_SIDEBAR_MENU_ITEM}, ${SELECTOR_HEADER_DROPDOWN_ITEM}`).removeClass('active') + $(SELECTOR_HEADER_MENU_ITEM).parent().removeClass('active') + + const $headerMenuItem = $(`${SELECTOR_HEADER_MENU_ITEM}[href$="${href}"]`) + const $headerDropdownItem = $(`${SELECTOR_HEADER_DROPDOWN_ITEM}[href$="${href}"]`) + const $sidebarMenuItem = $(`${SELECTOR_SIDEBAR_MENU_ITEM}[href$="${href}"]`) + + $headerMenuItem.each((i, e) => { + $(e).parent().addClass('active') + }) + $headerDropdownItem.each((i, e) => { + $(e).addClass('active') + }) + $sidebarMenuItem.each((i, e) => { + $(e).addClass('active') + $(e).parents('.nav-treeview').prevAll('.nav-link').addClass('active') + }) + } + + _fixHeight(tabEmpty = false) { + if ($('body').hasClass(CLASS_NAME_FULLSCREEN_MODE)) { + const windowHeight = $(window).height() + $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}`).height(windowHeight) + $(SELECTOR_CONTENT_WRAPPER).height(windowHeight) + $(SELECTOR_CONTENT_IFRAME).height(windowHeight) + } else { + const contentWrapperHeight = parseFloat($(SELECTOR_CONTENT_WRAPPER).css('min-height')) + const navbarHeight = $(SELECTOR_TAB_NAV).outerHeight() + if (tabEmpty == true) { + setTimeout(() => { + $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}`).height(contentWrapperHeight - navbarHeight) + }, 50) + } else { + $(SELECTOR_CONTENT_IFRAME).height(contentWrapperHeight - navbarHeight) + } + } + } + + // Static + + static _jQueryInterface(operation, ...args) { + let data = $(this).data(DATA_KEY) + const _options = $.extend({}, Default, $(this).data()) + + if (!data) { + data = new IFrame(this, _options) + $(this).data(DATA_KEY, data) + } + + if (typeof operation === 'string' && operation.match(/createTab|openTabSidebar|switchTab|removeActiveTab/)) { + data[operation](...args) + } + } +} + +/** + * Data API + * ==================================================== + */ + +$(window).on('load', () => { + IFrame._jQueryInterface.call($(SELECTOR_DATA_TOGGLE)) +}) + +/** + * jQuery API + * ==================================================== + */ + +$.fn[NAME] = IFrame._jQueryInterface +$.fn[NAME].Constructor = IFrame +$.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return IFrame._jQueryInterface +} + +export default IFrame diff --git a/build/js/Layout.js b/build/js/Layout.js index 773c4eb22..88b442653 100644 --- a/build/js/Layout.js +++ b/build/js/Layout.js @@ -36,6 +36,7 @@ const Default = { scrollbarTheme: 'os-theme-light', scrollbarAutoHide: 'l', panelAutoHeight: true, + panelAutoHeightMode: 'min-height', loginRegisterAutoHeight: true } @@ -81,15 +82,15 @@ class Layout { if (offset !== false) { if (max === heights.controlSidebar) { - $contentSelector.css('min-height', (max + offset)) + $contentSelector.css(this._config.panelAutoHeightMode, (max + offset)) } else if (max === heights.window) { - $contentSelector.css('min-height', (max + offset) - heights.header - heights.footer) + $contentSelector.css(this._config.panelAutoHeightMode, (max + offset) - heights.header - heights.footer) } else { - $contentSelector.css('min-height', (max + offset) - heights.header) + $contentSelector.css(this._config.panelAutoHeightMode, (max + offset) - heights.header) } if (this._isFooterFixed()) { - $contentSelector.css('min-height', parseFloat($contentSelector.css('min-height')) + heights.footer) + $contentSelector.css(this._config.panelAutoHeightMode, parseFloat($contentSelector.css(this._config.panelAutoHeightMode)) + heights.footer) } } @@ -98,7 +99,7 @@ class Layout { } if (offset !== false) { - $contentSelector.css('min-height', (max + offset) - heights.header - heights.footer) + $contentSelector.css(this._config.panelAutoHeightMode, (max + offset) - heights.header - heights.footer) } if (typeof $.fn.overlayScrollbars !== 'undefined') { @@ -123,8 +124,8 @@ class Layout { } else { const boxHeight = $selector.height() - if ($body.css('min-height') !== boxHeight) { - $body.css('min-height', boxHeight) + if ($body.css(this._config.panelAutoHeightMode) !== boxHeight) { + $body.css(this._config.panelAutoHeightMode, boxHeight) } } } diff --git a/build/scss/pages/_iframe.scss b/build/scss/pages/_iframe.scss new file mode 100644 index 000000000..c0a2721f9 --- /dev/null +++ b/build/scss/pages/_iframe.scss @@ -0,0 +1,80 @@ +body.iframe-mode { + .main-sidebar { + display: none; + } + .content-wrapper { + margin-left: 0 !important; + margin-top: 0 !important; + padding-bottom: 0 !important; + } + .main-header, + .main-footer { + display: none; + } +} + +body.iframe-mode-fullscreen { + overflow: hidden; +} + +.content-wrapper { + height: 100%; + + &.iframe-mode { + .navbar-nav { + overflow-y: auto; + width: 100%; + + .nav-link { + white-space: nowrap; + } + } + .tab-content { + position: relative; + } + .tab-empty { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + } + .tab-loading { + position: absolute; + top: 0; + left: 0; + width: 100%; + display: none; + background-color: $main-bg; + + > div { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + } + } + + iframe { + border: 0; + width: 100%; + height: 100%; + + .content-wrapper { + padding-bottom: 0 !important; + } + } + + body.iframe-mode-fullscreen & { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + margin-left: 0 !important; + height: 100%; + min-height: 100%; + z-index: $zindex-main-sidebar + 10; + } + } +} diff --git a/build/scss/parts/_pages.scss b/build/scss/parts/_pages.scss index 383e49762..35b4918bc 100644 --- a/build/scss/parts/_pages.scss +++ b/build/scss/parts/_pages.scss @@ -10,4 +10,5 @@ @import "../pages/profile"; @import "../pages/e-commerce"; @import "../pages/projects"; +@import "../pages/iframe"; @import "../pages/kanban"; diff --git a/docs/_config.yml b/docs/_config.yml index 7bbe8e842..d2f98b4a1 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -69,6 +69,8 @@ navigation: url: javascript/sidebar-search.html - title: Expandable Tables url: javascript/expandable-tables.html + - title: IFrame + url: javascript/iframe.html - title: Browser Support url: browser-support.html icon: fab fa-chrome diff --git a/docs/javascript/iframe.md b/docs/javascript/iframe.md new file mode 100644 index 000000000..f959c1318 --- /dev/null +++ b/docs/javascript/iframe.md @@ -0,0 +1,91 @@ +--- +layout: page +title: IFrame Plugin +--- + +The iframe plugin provides the functionality to open sidebar & navbar items in a tabbed iframe. + +##### Required Markup +To get the iframe 100% working you need the following content-wrapper markup: + +```html +
+ +
+
+

No tab selected!

+
+
+
+

Tab is loading

+
+
+
+
+``` + +##### Usage +This plugin can be activated as a jQuery plugin or using the data api. + +###### Data API +{: .text-bold } +Activate the plugin by adding `data-widget="iframe"` to the `.content-wrapper`. If you need to provide onCheck and onUncheck methods, please use the jQuery API. + +###### jQuery +{: .text-bold } +The jQuery API provides more customizable options that allows the developer to handle checking and unchecking the todo list checkbox events. +```js +$('.content-wrapper').IFrame({ + onTabClick(item) { + return item + }, + onTabChanged(item) { + return item + }, + onTabCreated(item) { + return item + }, + autoIframeMode: true, + autoItemActive: true, + autoShowNewTab: true, + loadingScreen: 750, + useNavbarItems: true +}) +``` + + +##### Options +{: .mt-4} + +|--- +| Name | Type | Default | Description +|-|-|-|- +|onTabClick | Function | Anonymous Function | Handle tab click event. +|onTabChanged | Function | Anonymous Function | Handle tab changed event. +|onTabCreated | Function | Anonymous Function | Handle tab created event. +|autoIframeMode | Boolean | true | Whether to automatically add `.iframe-mode` to `body` if page is loaded via iframe. +|autoItemActive | Boolean | true | Whether to automatically set the sidebar menu item active based on the active iframe. +|autoShowNewTab | Boolean | true | Whether to automatically display created tab. +|loadingScreen | Boolean/Number | true | [Boolean] Whether to enable iframe loading screen; [Number] Set loading screen hide delay. +|useNavbarItems | Boolean | true | Whether to open navbar menu items, instead of open only sidebar menu items. + +|--- +{: .table .table-bordered .bg-light} + + +##### Methods +{: .mt-4} + +|--- +| Method | Description +|-|- +|createTab| Create tab by title, link & uniqueName. Available arguments: title `String`, link `String`, uniqueName `String`, autoOpen `Boolean/Optional`. +|openTabSidebar| Create tab by sidebar menu item. Available arguments: item `String|jQuery Object`, autoOpen `Boolean/Optional`. +|switchTab| Switch tab by iframe tab navbar item. Available arguments: item `String|jQuery Object`. +|removeActiveTab| Remove active iframe tab. +{: .table .table-bordered .bg-light} + +Example: `$('.content-wrapper').IFrame('createTab', 'Home', 'index.html, 'index', true)` diff --git a/docs/javascript/layout.md b/docs/javascript/layout.md index 1b1e248c3..289ae9e9f 100644 --- a/docs/javascript/layout.md +++ b/docs/javascript/layout.md @@ -14,9 +14,10 @@ This plugin is activated automatically upon window load. |--- | Name | Type | Default | Description |-|-|-|- -|scrollbarTheme | Boolean | `os-theme-light` | Scrollbar Theme used while SideBar Fixed -|scrollbarAutoHide | Boolean | `l` | Scrollbar auto-hide trigger +|scrollbarTheme | String | `os-theme-light` | Scrollbar Theme used while SideBar Fixed +|scrollbarAutoHide | String | `l` | Scrollbar auto-hide trigger |panelAutoHeight | Boolean\|Numeric | true | Panel Height Correction (`true` = default correction on load/resize; numeric = offset the correction on load/resize) +|panelAutoHeightMode | String | `min-height` | Panel Height Mode (`min-height` = sets the `min-height`-attribute to the content-wrapper; `height` = sets `height`-attribute to the content-wrapper) |loginRegisterAutoHeight | Boolean\|Integer | true | Login & Register Height Correction (`true` = single correction on load; integer = correction with a interval based on the interger) |--- {: .table .table-bordered .bg-light} diff --git a/iframe.html b/iframe.html new file mode 100644 index 000000000..f0836451d --- /dev/null +++ b/iframe.html @@ -0,0 +1,797 @@ + + + + + + AdminLTE 3 | Tabbed IFrames + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+
+

No tab selected!

+
+
+
+

Tab is loading

+
+
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + diff --git a/index.html b/index.html index ea2a6210b..99c517693 100644 --- a/index.html +++ b/index.html @@ -723,6 +723,12 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +