IFrame.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. // noinspection EqualityComparisonWithCoercionJS
  2. /**
  3. * --------------------------------------------
  4. * AdminLTE IFrame.js
  5. * License MIT
  6. * --------------------------------------------
  7. */
  8. import $ from 'jquery'
  9. /**
  10. * Constants
  11. * ====================================================
  12. */
  13. const NAME = 'IFrame'
  14. const DATA_KEY = 'lte.iframe'
  15. const JQUERY_NO_CONFLICT = $.fn[NAME]
  16. const SELECTOR_DATA_TOGGLE = '[data-widget="iframe"]'
  17. const SELECTOR_DATA_TOGGLE_CLOSE = '[data-widget="iframe-close"]'
  18. const SELECTOR_DATA_TOGGLE_SCROLL_LEFT = '[data-widget="iframe-scrollleft"]'
  19. const SELECTOR_DATA_TOGGLE_SCROLL_RIGHT = '[data-widget="iframe-scrollright"]'
  20. const SELECTOR_DATA_TOGGLE_FULLSCREEN = '[data-widget="iframe-fullscreen"]'
  21. const SELECTOR_CONTENT_WRAPPER = '.content-wrapper'
  22. const SELECTOR_CONTENT_IFRAME = `${SELECTOR_CONTENT_WRAPPER} iframe`
  23. const SELECTOR_TAB_NAV = `${SELECTOR_CONTENT_WRAPPER}.iframe-mode .nav`
  24. const SELECTOR_TAB_NAVBAR_NAV = `${SELECTOR_CONTENT_WRAPPER}.iframe-mode .navbar-nav`
  25. const SELECTOR_TAB_NAVBAR_NAV_ITEM = `${SELECTOR_TAB_NAVBAR_NAV} .nav-item`
  26. const SELECTOR_TAB_NAVBAR_NAV_LINK = `${SELECTOR_TAB_NAVBAR_NAV} .nav-link`
  27. const SELECTOR_TAB_CONTENT = `${SELECTOR_CONTENT_WRAPPER}.iframe-mode .tab-content`
  28. const SELECTOR_TAB_EMPTY = `${SELECTOR_TAB_CONTENT} .tab-empty`
  29. const SELECTOR_TAB_LOADING = `${SELECTOR_TAB_CONTENT} .tab-loading`
  30. const SELECTOR_TAB_PANE = `${SELECTOR_TAB_CONTENT} .tab-pane`
  31. const SELECTOR_SIDEBAR_MENU_ITEM = '.main-sidebar .nav-item > a.nav-link'
  32. const SELECTOR_SIDEBAR_SEARCH_ITEM = '.sidebar-search-results .list-group-item'
  33. const SELECTOR_HEADER_MENU_ITEM = '.main-header .nav-item a.nav-link'
  34. const SELECTOR_HEADER_DROPDOWN_ITEM = '.main-header a.dropdown-item'
  35. const CLASS_NAME_IFRAME_MODE = 'iframe-mode'
  36. const CLASS_NAME_FULLSCREEN_MODE = 'iframe-mode-fullscreen'
  37. const Default = {
  38. onTabClick(item) {
  39. return item
  40. },
  41. onTabChanged(item) {
  42. return item
  43. },
  44. onTabCreated(item) {
  45. return item
  46. },
  47. autoIframeMode: true,
  48. autoItemActive: true,
  49. autoShowNewTab: true,
  50. autoDarkMode: false,
  51. allowDuplicates: false,
  52. allowReload: true,
  53. loadingScreen: true,
  54. useNavbarItems: true,
  55. scrollOffset: 40,
  56. scrollBehaviorSwap: false,
  57. iconMaximize: 'fa-expand',
  58. iconMinimize: 'fa-compress'
  59. }
  60. /**
  61. * Class Definition
  62. * ====================================================
  63. */
  64. class IFrame {
  65. constructor(element, config) {
  66. this._config = config
  67. this._element = element
  68. this._init()
  69. }
  70. // Public
  71. onTabClick(item) {
  72. this._config.onTabClick(item)
  73. }
  74. onTabChanged(item) {
  75. this._config.onTabChanged(item)
  76. }
  77. onTabCreated(item) {
  78. this._config.onTabCreated(item)
  79. }
  80. createTab(title, link, uniqueName, autoOpen) {
  81. let tabId = `panel-${uniqueName}`
  82. let navId = `tab-${uniqueName}`
  83. if (this._config.allowDuplicates) {
  84. tabId += `-${Math.floor(Math.random() * 1000)}`
  85. navId += `-${Math.floor(Math.random() * 1000)}`
  86. }
  87. const newNavItem = `<li class="nav-item" role="presentation"><a href="#" class="btn-iframe-close" data-widget="iframe-close" data-type="only-this"><i class="fas fa-times"></i></a><a class="nav-link" data-toggle="row" id="${navId}" href="#${tabId}" role="tab" aria-controls="${tabId}" aria-selected="false">${title}</a></li>`
  88. $(SELECTOR_TAB_NAVBAR_NAV).append(unescape(escape(newNavItem)))
  89. const newTabItem = `<div class="tab-pane fade" id="${tabId}" role="tabpanel" aria-labelledby="${navId}"><iframe src="${link}"></iframe></div>`
  90. $(SELECTOR_TAB_CONTENT).append(unescape(escape(newTabItem)))
  91. if (autoOpen) {
  92. if (this._config.loadingScreen) {
  93. const $loadingScreen = $(SELECTOR_TAB_LOADING)
  94. if (!$loadingScreen.is(':animated')) {
  95. $loadingScreen.fadeIn()
  96. }
  97. $(`${tabId} iframe`).ready(() => {
  98. if (typeof this._config.loadingScreen === 'number') {
  99. this.switchTab(`#${navId}`)
  100. setTimeout(() => {
  101. $loadingScreen.fadeOut()
  102. }, this._config.loadingScreen)
  103. } else {
  104. this.switchTab(`#${navId}`)
  105. $loadingScreen.fadeOut()
  106. }
  107. })
  108. } else {
  109. this.switchTab(`#${navId}`)
  110. }
  111. }
  112. this.onTabCreated($(`#${navId}`))
  113. }
  114. openTabSidebar(item, autoOpen = this._config.autoShowNewTab) {
  115. let $item = $(item).clone()
  116. if ($item.attr('href') === undefined) {
  117. $item = $(item).parent('a').clone()
  118. }
  119. $item.find('.right, .search-path').remove()
  120. let title = $item.find('p').text()
  121. if (title === '') {
  122. title = $item.text()
  123. }
  124. const link = $item.attr('href')
  125. if (link === '#' || link === '' || link === undefined) {
  126. return
  127. }
  128. const uniqueName = unescape(link).replace('./', '').replace(/["#&'./:=?[\]]/gi, '-').replace(/(--)/gi, '')
  129. const navId = `tab-${uniqueName}`
  130. if (!this._config.allowDuplicates && $(`#${navId}`).length > 0) {
  131. return this.switchTab(`#${navId}`, this._config.allowReload)
  132. }
  133. if ((!this._config.allowDuplicates && $(`#${navId}`).length === 0) || this._config.allowDuplicates) {
  134. this.createTab(title, link, uniqueName, autoOpen)
  135. }
  136. }
  137. switchTab(item, reload = false) {
  138. const $item = $(item)
  139. const tabId = $item.attr('href')
  140. $(SELECTOR_TAB_EMPTY).hide()
  141. if (reload) {
  142. const $loadingScreen = $(SELECTOR_TAB_LOADING)
  143. if (this._config.loadingScreen) {
  144. $loadingScreen.show(0, () => {
  145. $(`${tabId} iframe`).attr('src', $(`${tabId} iframe`).attr('src')).ready(() => {
  146. if (this._config.loadingScreen) {
  147. if (typeof this._config.loadingScreen === 'number') {
  148. setTimeout(() => {
  149. $loadingScreen.fadeOut()
  150. }, this._config.loadingScreen)
  151. } else {
  152. $loadingScreen.fadeOut()
  153. }
  154. }
  155. })
  156. })
  157. } else {
  158. $(`${tabId} iframe`).attr('src', $(`${tabId} iframe`).attr('src'))
  159. }
  160. }
  161. $(`${SELECTOR_TAB_NAVBAR_NAV} .active`).tab('dispose').removeClass('active')
  162. this._fixHeight()
  163. $item.tab('show')
  164. $item.parents('li').addClass('active')
  165. this.onTabChanged($item)
  166. if (this._config.autoItemActive) {
  167. this._setItemActive($(`${tabId} iframe`).attr('src'))
  168. }
  169. }
  170. removeActiveTab(type, element) {
  171. if (type == 'all') {
  172. $(SELECTOR_TAB_NAVBAR_NAV_ITEM).remove()
  173. $(SELECTOR_TAB_PANE).remove()
  174. $(SELECTOR_TAB_EMPTY).show()
  175. } else if (type == 'all-other') {
  176. $(`${SELECTOR_TAB_NAVBAR_NAV_ITEM}:not(.active)`).remove()
  177. $(`${SELECTOR_TAB_PANE}:not(.active)`).remove()
  178. } else if (type == 'only-this') {
  179. const $navClose = $(element)
  180. const $navItem = $navClose.parent('.nav-item')
  181. const $navItemParent = $navItem.parent()
  182. const navItemIndex = $navItem.index()
  183. const tabId = $navClose.siblings('.nav-link').attr('aria-controls')
  184. $navItem.remove()
  185. $(`#${tabId}`).remove()
  186. if ($(SELECTOR_TAB_CONTENT).children().length == $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}`).length) {
  187. $(SELECTOR_TAB_EMPTY).show()
  188. } else {
  189. const prevNavItemIndex = navItemIndex - 1
  190. this.switchTab($navItemParent.children().eq(prevNavItemIndex).find('a.nav-link'))
  191. }
  192. } else {
  193. const $navItem = $(`${SELECTOR_TAB_NAVBAR_NAV_ITEM}.active`)
  194. const $navItemParent = $navItem.parent()
  195. const navItemIndex = $navItem.index()
  196. $navItem.remove()
  197. $(`${SELECTOR_TAB_PANE}.active`).remove()
  198. if ($(SELECTOR_TAB_CONTENT).children().length == $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}`).length) {
  199. $(SELECTOR_TAB_EMPTY).show()
  200. } else {
  201. const prevNavItemIndex = navItemIndex - 1
  202. this.switchTab($navItemParent.children().eq(prevNavItemIndex).find('a.nav-link'))
  203. }
  204. }
  205. }
  206. toggleFullscreen() {
  207. if ($('body').hasClass(CLASS_NAME_FULLSCREEN_MODE)) {
  208. $(`${SELECTOR_DATA_TOGGLE_FULLSCREEN} i`).removeClass(this._config.iconMinimize).addClass(this._config.iconMaximize)
  209. $('body').removeClass(CLASS_NAME_FULLSCREEN_MODE)
  210. $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}`).height('100%')
  211. $(SELECTOR_CONTENT_WRAPPER).height('100%')
  212. $(SELECTOR_CONTENT_IFRAME).height('100%')
  213. } else {
  214. $(`${SELECTOR_DATA_TOGGLE_FULLSCREEN} i`).removeClass(this._config.iconMaximize).addClass(this._config.iconMinimize)
  215. $('body').addClass(CLASS_NAME_FULLSCREEN_MODE)
  216. }
  217. $(window).trigger('resize')
  218. this._fixHeight(true)
  219. }
  220. // Private
  221. _init() {
  222. const usingDefTab = ($(SELECTOR_TAB_CONTENT).children().length > 2)
  223. this._setupListeners()
  224. this._fixHeight(true)
  225. if (usingDefTab) {
  226. const $el = $(`${SELECTOR_TAB_PANE}`).first()
  227. const uniqueName = $el.attr('id').replace('panel-', '')
  228. const navId = `#tab-${uniqueName}`
  229. this.switchTab(navId, true)
  230. }
  231. }
  232. _initFrameElement() {
  233. if (window.frameElement && this._config?.autoIframeMode) {
  234. const $body = $('body')
  235. $body.addClass(CLASS_NAME_IFRAME_MODE)
  236. if (this._config.autoDarkMode) {
  237. $body.addClass('dark-mode')
  238. }
  239. }
  240. }
  241. _navScroll(offset) {
  242. const leftPos = $(SELECTOR_TAB_NAVBAR_NAV).scrollLeft()
  243. $(SELECTOR_TAB_NAVBAR_NAV).animate({ scrollLeft: (leftPos + offset) }, 250, 'linear')
  244. }
  245. _setupListeners() {
  246. $(window).on('resize', () => {
  247. setTimeout(() => {
  248. this._fixHeight()
  249. }, 1)
  250. })
  251. if ($(SELECTOR_CONTENT_WRAPPER).hasClass(CLASS_NAME_IFRAME_MODE)) {
  252. $(document).on('click', `${SELECTOR_SIDEBAR_MENU_ITEM}, ${SELECTOR_SIDEBAR_SEARCH_ITEM}`, e => {
  253. e.preventDefault()
  254. this.openTabSidebar(e.target)
  255. })
  256. if (this._config.useNavbarItems) {
  257. $(document).on('click', `${SELECTOR_HEADER_MENU_ITEM}, ${SELECTOR_HEADER_DROPDOWN_ITEM}`, e => {
  258. e.preventDefault()
  259. this.openTabSidebar(e.target)
  260. })
  261. }
  262. }
  263. $(document).on('click', SELECTOR_TAB_NAVBAR_NAV_LINK, e => {
  264. e.preventDefault()
  265. this.onTabClick(e.target)
  266. this.switchTab(e.target)
  267. })
  268. $(document).on('click', SELECTOR_TAB_NAVBAR_NAV_LINK, e => {
  269. e.preventDefault()
  270. this.onTabClick(e.target)
  271. this.switchTab(e.target)
  272. })
  273. $(document).on('click', SELECTOR_DATA_TOGGLE_CLOSE, e => {
  274. e.preventDefault()
  275. let { target } = e
  276. if (target.nodeName === 'I') {
  277. target = e.target.offsetParent
  278. }
  279. this.removeActiveTab(target.attributes['data-type'] ? target.attributes['data-type'].nodeValue : null, target)
  280. })
  281. $(document).on('click', SELECTOR_DATA_TOGGLE_FULLSCREEN, e => {
  282. e.preventDefault()
  283. this.toggleFullscreen()
  284. })
  285. let mousedown = false
  286. let mousedownInterval = null
  287. $(document).on('mousedown', SELECTOR_DATA_TOGGLE_SCROLL_LEFT, e => {
  288. e.preventDefault()
  289. clearInterval(mousedownInterval)
  290. let { scrollOffset } = this._config
  291. if (!this._config.scrollBehaviorSwap) {
  292. scrollOffset = -scrollOffset
  293. }
  294. mousedown = true
  295. this._navScroll(scrollOffset)
  296. mousedownInterval = setInterval(() => {
  297. this._navScroll(scrollOffset)
  298. }, 250)
  299. })
  300. $(document).on('mousedown', SELECTOR_DATA_TOGGLE_SCROLL_RIGHT, e => {
  301. e.preventDefault()
  302. clearInterval(mousedownInterval)
  303. let { scrollOffset } = this._config
  304. if (this._config.scrollBehaviorSwap) {
  305. scrollOffset = -scrollOffset
  306. }
  307. mousedown = true
  308. this._navScroll(scrollOffset)
  309. mousedownInterval = setInterval(() => {
  310. this._navScroll(scrollOffset)
  311. }, 250)
  312. })
  313. $(document).on('mouseup', () => {
  314. if (mousedown) {
  315. mousedown = false
  316. clearInterval(mousedownInterval)
  317. mousedownInterval = null
  318. }
  319. })
  320. }
  321. _setItemActive(href) {
  322. $(`${SELECTOR_SIDEBAR_MENU_ITEM}, ${SELECTOR_HEADER_DROPDOWN_ITEM}`).removeClass('active')
  323. $(SELECTOR_HEADER_MENU_ITEM).parent().removeClass('active')
  324. const $headerMenuItem = $(`${SELECTOR_HEADER_MENU_ITEM}[href$="${href}"]`)
  325. const $headerDropdownItem = $(`${SELECTOR_HEADER_DROPDOWN_ITEM}[href$="${href}"]`)
  326. const $sidebarMenuItem = $(`${SELECTOR_SIDEBAR_MENU_ITEM}[href$="${href}"]`)
  327. $headerMenuItem.each((i, e) => {
  328. $(e).parent().addClass('active')
  329. })
  330. $headerDropdownItem.each((i, e) => {
  331. $(e).addClass('active')
  332. })
  333. $sidebarMenuItem.each((i, e) => {
  334. $(e).addClass('active')
  335. $(e).parents('.nav-treeview').prevAll('.nav-link').addClass('active')
  336. })
  337. }
  338. _fixHeight(tabEmpty = false) {
  339. if ($('body').hasClass(CLASS_NAME_FULLSCREEN_MODE)) {
  340. const windowHeight = $(window).height()
  341. const navbarHeight = $(SELECTOR_TAB_NAV).outerHeight()
  342. $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}, ${SELECTOR_CONTENT_IFRAME}`).height(windowHeight - navbarHeight)
  343. $(SELECTOR_CONTENT_WRAPPER).height(windowHeight)
  344. } else {
  345. const contentWrapperHeight = parseFloat($(SELECTOR_CONTENT_WRAPPER).css('height'))
  346. const navbarHeight = $(SELECTOR_TAB_NAV).outerHeight()
  347. if (tabEmpty == true) {
  348. setTimeout(() => {
  349. $(`${SELECTOR_TAB_EMPTY}, ${SELECTOR_TAB_LOADING}`).height(contentWrapperHeight - navbarHeight)
  350. }, 50)
  351. } else {
  352. $(SELECTOR_CONTENT_IFRAME).height(contentWrapperHeight - navbarHeight)
  353. }
  354. }
  355. }
  356. // Static
  357. // eslint-disable-next-line max-params
  358. static _jQueryInterface(config, name, link, id, reload) {
  359. if ($(SELECTOR_DATA_TOGGLE).length > 0) {
  360. let data = $(this).data(DATA_KEY)
  361. if (!data) {
  362. data = $(this).data()
  363. }
  364. const _options = $.extend({}, Default, typeof config === 'object' ? config : data)
  365. localStorage.setItem('AdminLTE:IFrame:Options', JSON.stringify(_options))
  366. const plugin = new IFrame($(this), _options)
  367. window.iFrameInstance = plugin
  368. $(this).data(DATA_KEY, typeof config === 'object' ? config : { link, name, id, reload, ...data })
  369. if (typeof config === 'string' && /createTab|openTabSidebar|switchTab|removeActiveTab/.test(config)) {
  370. plugin[config](name, link, id, reload)
  371. }
  372. } else {
  373. window.iFrameInstance = new IFrame($(this), JSON.parse(localStorage.getItem('AdminLTE:IFrame:Options')))._initFrameElement()
  374. }
  375. }
  376. }
  377. /**
  378. * Data API
  379. * ====================================================
  380. */
  381. $(window).on('load', () => {
  382. IFrame._jQueryInterface.call($(SELECTOR_DATA_TOGGLE))
  383. })
  384. /**
  385. * jQuery API
  386. * ====================================================
  387. */
  388. $.fn[NAME] = IFrame._jQueryInterface
  389. $.fn[NAME].Constructor = IFrame
  390. $.fn[NAME].noConflict = function () {
  391. $.fn[NAME] = JQUERY_NO_CONFLICT
  392. return IFrame._jQueryInterface
  393. }
  394. export default IFrame