mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-21 01:28:47 +02:00
N°2847 - Rework iTopWebPage layout (WIP Part IV)
- iTopWebPage: Clean up some commented sections - iTopWebPage: Marked some new methods as @internal - iTopWebPage: Restore page content to allow // developments - Quick create: Add quick object creation box to the top bar - Global search: Improve cinematic with other widgets - Components / Layouts: Move JS parts to iTopWebPage, will be put in dedicated PHP helpers later
This commit is contained in:
@@ -22,6 +22,7 @@ require_once(APPROOT."/application/applicationcontext.class.inc.php");
|
||||
require_once(APPROOT."/application/user.preferences.class.inc.php");
|
||||
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\Application\QuickCreate\QuickCreateHelper;
|
||||
use Combodo\iTop\Application\GlobalSearch\GlobalSearchHelper;
|
||||
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
|
||||
|
||||
@@ -175,24 +176,6 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
*/
|
||||
protected function PrepareLayout()
|
||||
{
|
||||
// TODO: Move this to the menu renderer
|
||||
// if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
// {
|
||||
// // No pin button
|
||||
// $sConfigureWestPane = '';
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// $sConfigureWestPane =
|
||||
// <<<EOF
|
||||
// if (typeof myLayout !== "undefined")
|
||||
// {
|
||||
// myLayout.addPinBtn( "#tPinMenu", "west" );
|
||||
// }
|
||||
//EOF;
|
||||
// }
|
||||
// $sInitClosed = $this->IsMenuPaneVisible() ? '' : 'initClosed: true,';
|
||||
|
||||
$sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage'));
|
||||
$sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle'));
|
||||
$sJSLoginAgain = json_encode(Dict::S('UI:LoginAgain'));
|
||||
@@ -305,6 +288,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
JS
|
||||
);
|
||||
|
||||
// TODO: This is for tag sets, refactor the attribute markup so it contains the necessary
|
||||
// TODO: data-tooltip-* attributes to activate the tooltips automatically (see /js/pages/backoffice.js)
|
||||
// Attribute set tooltip on items
|
||||
$this->add_ready_script(
|
||||
<<<JS
|
||||
@@ -336,6 +321,8 @@ JS
|
||||
});
|
||||
JS
|
||||
);
|
||||
|
||||
// TODO: Change CSS class and extract this in backoffice.js
|
||||
// Make image attributes zoomable
|
||||
$this->add_ready_script(
|
||||
<<<JS
|
||||
@@ -345,7 +332,8 @@ JS
|
||||
.magnificPopup({type: 'image', closeOnContentClick: true });
|
||||
JS
|
||||
);
|
||||
|
||||
|
||||
// TODO: Change CSS class and extract this in backoffice.js
|
||||
// Highlight code content created with CKEditor
|
||||
$this->add_ready_script(
|
||||
<<<JS
|
||||
@@ -852,12 +840,13 @@ JS
|
||||
* @throws \Exception
|
||||
* @throws \DictExceptionMissingString
|
||||
* @since 2.8.0
|
||||
* @internal
|
||||
*/
|
||||
protected function GetNavigationMenuData()
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
return [
|
||||
$aData = [
|
||||
'sId' => 'ibo-navigation-menu',
|
||||
'sAppRevisionNumber' => $this->GetApplicationRevisionNumber(),
|
||||
'sAppSquareIconUrl' => Branding::GetSquareMainLogoAbsoluteUrl(),
|
||||
@@ -866,6 +855,16 @@ JS
|
||||
'aMenuGroups' => ApplicationMenu::GetMenuGroups($oAppContext->GetAsHash()),
|
||||
'bIsExpanded' => $this->IsMenuPaneVisible(),
|
||||
];
|
||||
|
||||
// TODO: Move this in the PHP component when designed
|
||||
$this->add_linked_script('../js/layouts/navigation-menu.js');
|
||||
// ... and this in a dedicated JS TWIG
|
||||
$this->add_ready_script(<<<JS
|
||||
$('#{$aData['sId']}').navigation_menu();
|
||||
JS
|
||||
);
|
||||
|
||||
return $aData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -874,13 +873,16 @@ JS
|
||||
* @return array
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
* @since 2.8.0
|
||||
* @internal
|
||||
*/
|
||||
protected function GetTopBarData()
|
||||
{
|
||||
$aData = [
|
||||
'sId' => 'ibo-top-bar',
|
||||
'aComponents' => [
|
||||
'aQuickCreate' => $this->GetQuickCreateData(),
|
||||
'aGlobalSearch' => $this->GetGlobalSearchData(),
|
||||
'aBreadCrumbs' => $this->GetBreadCrumbsData(),
|
||||
],
|
||||
@@ -889,12 +891,43 @@ JS
|
||||
return $aData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the quick create data (last classes)
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @since 2.8.0
|
||||
* @internal
|
||||
*/
|
||||
protected function GetQuickCreateData()
|
||||
{
|
||||
$aData = [
|
||||
'sId' => 'ibo-quick-create',
|
||||
'sEndpoint' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php',
|
||||
'aAvailableClasses' => UserRights::GetAllowedClasses(UR_ACTION_CREATE, array('bizmodel'), true),
|
||||
'aLastClasses' => QuickCreateHelper::GetLastClasses(),
|
||||
];
|
||||
|
||||
// TODO: Move this in the PHP component when designed
|
||||
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/selectize.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/components/quick-create.js');
|
||||
// ... and this in a dedicated JS TWIG
|
||||
$this->add_ready_script(<<<JS
|
||||
$('#{$aData['sId']}').quick_create();
|
||||
JS
|
||||
);
|
||||
|
||||
return $aData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the global search data (last queries)
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @since 2.8.0
|
||||
* @internal
|
||||
*/
|
||||
protected function GetGlobalSearchData()
|
||||
{
|
||||
@@ -904,6 +937,14 @@ JS
|
||||
'aLastQueries' => GlobalSearchHelper::GetLastQueries(),
|
||||
];
|
||||
|
||||
// TODO: Move this in the PHP component when designed
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/components/global-search.js');
|
||||
// ... and this in a dedicated JS TWIG
|
||||
$this->add_ready_script(<<<JS
|
||||
$('#{$aData['sId']}').global_search();
|
||||
JS
|
||||
);
|
||||
|
||||
return $aData;
|
||||
}
|
||||
|
||||
@@ -914,11 +955,13 @@ JS
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @since 2.8.0
|
||||
* @internal
|
||||
*/
|
||||
protected function GetBreadCrumbsData()
|
||||
{
|
||||
$aData = [
|
||||
'sId' => 'ibo-breadcrumbs',
|
||||
'aWidgetOptions' => [],
|
||||
];
|
||||
|
||||
$iBreadCrumbMaxCount = utils::GetConfig()->Get('breadcrumb.max_count');
|
||||
@@ -961,6 +1004,16 @@ JS
|
||||
];
|
||||
}
|
||||
|
||||
// TODO: Move this in the PHP component when designed
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/components/breadcrumbs.js');
|
||||
// ... and this in a dedicated JS TWIG
|
||||
$sWidgetOptionsAsJson = json_encode($aData['aWidgetOptions']);
|
||||
$this->add_ready_script(<<<JS
|
||||
// Note: When refactor in the JS TWIG, use {{ aBreadCrumbs.aWidgetOptions|json_encode|raw }}
|
||||
$('#{$aData['sId']}').breadcrumbs($sWidgetOptionsAsJson);
|
||||
JS
|
||||
);
|
||||
|
||||
return $aData;
|
||||
}
|
||||
|
||||
@@ -1029,6 +1082,7 @@ EOF
|
||||
* Render the banner HTML which can come from both iTop itself and from extensions
|
||||
*
|
||||
* @see \iPageUIExtension::GetBannerHtml()
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
* @since 2.8.0
|
||||
@@ -1050,6 +1104,7 @@ EOF
|
||||
* Render the header HTML which can come from both iTop itself and from extensions
|
||||
*
|
||||
* @see \iPageUIExtension::GetNorthPaneHtml()
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
* @since 2.8.0
|
||||
@@ -1077,6 +1132,7 @@ EOF
|
||||
* Render the footer HTML which can come from both iTop itself and from extensions
|
||||
*
|
||||
* @see \iPageUIExtension::GetSouthPaneHtml()
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
* @since 2.8.0
|
||||
@@ -1110,6 +1166,10 @@ EOF
|
||||
// - Generate necessary dict. files
|
||||
$this->output_dict_entries();
|
||||
|
||||
// TODO: Check if we can keep this as is
|
||||
// Render the tabs in the page (if any)
|
||||
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
|
||||
|
||||
// Base structure of data to pass to the TWIG template
|
||||
$aData['aPage'] = [
|
||||
'sAbsoluteUrlAppRoot' => $sAbsoluteUrlAppRoot,
|
||||
@@ -1119,14 +1179,6 @@ EOF
|
||||
'sCharset' => static::PAGES_CHARSET,
|
||||
'sLang' => $sMetadataLanguage,
|
||||
],
|
||||
'aCssFiles' => $this->a_linked_stylesheets,
|
||||
'aCssInline' => $this->a_styles,
|
||||
'aJsFiles' => $this->a_linked_scripts,
|
||||
'aJsInlineOnInit' => $this->m_aInitScript,
|
||||
'aJsInlineOnDomReady' => $this->m_aReadyScripts,
|
||||
'aJsInlineLive' => $this->a_scripts,
|
||||
// TODO: TEMP, used while developping, remove it.
|
||||
'aSanitizedContent' => self::FilterXSS($this->s_content),
|
||||
];
|
||||
|
||||
// Base tag
|
||||
@@ -1149,6 +1201,21 @@ EOF
|
||||
// - Top bar
|
||||
$aData['aLayouts']['aTopBar'] = $this->GetTopBarData();
|
||||
|
||||
// Variable content of the page
|
||||
$aData['aPage'] = array_merge(
|
||||
$aData['aPage'],
|
||||
[
|
||||
'aCssFiles' => $this->a_linked_stylesheets,
|
||||
'aCssInline' => $this->a_styles,
|
||||
'aJsFiles' => $this->a_linked_scripts,
|
||||
'aJsInlineOnInit' => $this->m_aInitScript,
|
||||
'aJsInlineOnDomReady' => $this->m_aReadyScripts,
|
||||
'aJsInlineLive' => $this->a_scripts,
|
||||
// TODO: TEMP, used while developping, remove it.
|
||||
'aSanitizedContent' => self::FilterXSS($this->s_content),
|
||||
]
|
||||
);
|
||||
|
||||
$oTwigEnv = TwigHelper::GetTwigEnvironment(APPROOT.'templates/');
|
||||
$sTemplateRelPath = 'pages/backoffice/layout';
|
||||
|
||||
|
||||
@@ -17,4 +17,5 @@
|
||||
*/
|
||||
|
||||
@import "breadcrumbs";
|
||||
@import "quick-create";
|
||||
@import "global-search";
|
||||
|
||||
222
css/backoffice/components/_quick-create.scss
Normal file
222
css/backoffice/components/_quick-create.scss
Normal file
@@ -0,0 +1,222 @@
|
||||
/*!
|
||||
* Copyright (C) 2013-2020 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
/* SCSS variables */
|
||||
$ibo-quick-create--head--background-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-quick-create--icon-padding-x: 16px !default;
|
||||
$ibo-quick-create--icon-padding-y: 0 !default;
|
||||
|
||||
$ibo-quick-create--input--padding: 0 default;
|
||||
$ibo-quick-create--input--padding-x--is-opened: 8px !default;
|
||||
$ibo-quick-create--input--padding-y--is-opened: 8px !default;
|
||||
$ibo-quick-create--input--width: 0 !default;
|
||||
$ibo-quick-create--input--width--is-opened: 245px !default;
|
||||
$ibo-quick-create--input--text-color: $ibo-color-grey-800 !default;
|
||||
$ibo-quick-create--input--placeholder-color: $ibo-color-grey-600 !default;
|
||||
|
||||
$ibo-quick-create--input-options--background-color: $ibo-quick-create--head--background-color !default;
|
||||
$ibo-quick-create--input-options--border: none !default;
|
||||
$ibo-quick-create--input-options--border-radius: 0 !default;
|
||||
|
||||
$ibo-quick-create--drawer--max-height: 200px !default;
|
||||
$ibo-quick-create--drawer--padding-x: $ibo-quick-create--icon-padding-x !default;
|
||||
$ibo-quick-create--drawer--padding-y: 16px !default;
|
||||
$ibo-quick-create--drawer--top: -1 * ($ibo-quick-create--drawer--max-height) !default;
|
||||
$ibo-quick-create--drawer--top--is-opened: 100% !default;
|
||||
$ibo-quick-create--drawer--background-color: $ibo-color-white-100 !default;
|
||||
|
||||
$ibo-quick-create--compartment-title--margin-bottom: 8px !default;
|
||||
$ibo-quick-create--compartment-title--padding-left: 32px !default;
|
||||
$ibo-quick-create--compartment-title--text-color: $ibo-color-grey-600 !default;
|
||||
$ibo-quick-create--compartment-title--line-spacing: 8px !default;
|
||||
|
||||
$ibo-quick-create--compartment-content--text-color: $ibo-color-grey-900 !default;
|
||||
|
||||
$ibo-quick-create--compartment-element--margin-bottom: 8px !default;
|
||||
|
||||
$ibo-quick-create--compartment-element-image--margin-right: 8px !default;
|
||||
$ibo-quick-create--compartment-element-image--width: 20px !default;
|
||||
|
||||
$ibo-quick-create--compartment--placeholder-image--margin-top: 24px !default;
|
||||
$ibo-quick-create--compartment--placeholder-image--margin-bottom: 16px !default;
|
||||
$ibo-quick-create--compartment--placeholder-image--width: 66% !default;
|
||||
|
||||
$ibo-quick-create--compartment--placeholder-hint--padding-x: 8px !default;
|
||||
$ibo-quick-create--compartment--placeholder-hint--padding-y: 0 !default;
|
||||
$ibo-quick-create--compartment--placeholder-hint--text-color: $ibo-color-grey-700 !default;
|
||||
|
||||
/* Animations*/
|
||||
@keyframes ibo-quick-create--drawer--opening{
|
||||
from {
|
||||
top: $ibo-quick-create--drawer--top;
|
||||
box-shadow: none;
|
||||
}
|
||||
to {
|
||||
top: $ibo-quick-create--drawer--top--is-opened;
|
||||
box-shadow: $ibo-elevation-300;
|
||||
}
|
||||
}
|
||||
|
||||
/* SCSS rules */
|
||||
.ibo-quick-create{
|
||||
position: relative;
|
||||
@extend %ibo-vertically-centered-content;
|
||||
|
||||
&.ibo-quick-create--is-opened{
|
||||
.ibo-quick-create--input{
|
||||
width: $ibo-quick-create--input--width--is-opened;
|
||||
}
|
||||
.ibo-quick-create--drawer{
|
||||
animation-name: ibo-quick-create--drawer--opening;
|
||||
animation-delay: 0.1s;
|
||||
animation-duration: 0.2s;
|
||||
animation-direction: normal;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ibo-quick-create--head{
|
||||
@extend %ibo-vertically-centered-content;
|
||||
background-color: $ibo-quick-create--head--background-color;
|
||||
}
|
||||
.ibo-quick-create--icon{
|
||||
align-self: center;
|
||||
padding: $ibo-quick-create--icon-padding-y $ibo-quick-create--icon-padding-x;
|
||||
@extend %ibo-font-ral-nor-400;
|
||||
}
|
||||
.ibo-quick-create--input{
|
||||
width: $ibo-quick-create--input--width;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
/* Remove selectize.js theme and apply our own */
|
||||
&.selectize-control.single{
|
||||
position: sticky;
|
||||
display: flex;
|
||||
|
||||
.selectize-input{
|
||||
display: flex;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
|
||||
&.input-active{
|
||||
@extend .selectize-input;
|
||||
}
|
||||
|
||||
> input{
|
||||
color: $ibo-quick-create--input--text-color;
|
||||
@extend %ibo-font-ral-nor-300;
|
||||
|
||||
outline: none;
|
||||
border: none;
|
||||
|
||||
&::placeholder{
|
||||
color: $ibo-quick-create--input--placeholder-color;
|
||||
}
|
||||
/* This rule is duplicated otherwise Chrome won't be able to parse it. */
|
||||
&:-ms-input-placeholder,
|
||||
&::-ms-input-placeholder{
|
||||
color: $ibo-quick-create--input--placeholder-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.selectize-dropdown{
|
||||
background-color: $ibo-quick-create--input-options--background-color;
|
||||
border: $ibo-quick-create--input-options--border;
|
||||
border-radius: $ibo-quick-create--input-options--border-radius;
|
||||
@extend %ibo-elevation-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* TODO: Make drawer appear below the top bar so its shadow is cast on the drawer */
|
||||
.ibo-quick-create--drawer{
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: $ibo-quick-create--drawer--top;
|
||||
padding: $ibo-quick-create--drawer--padding-y $ibo-quick-create--drawer--padding-x;
|
||||
background-color: $ibo-quick-create--drawer--background-color;
|
||||
box-shadow: none;
|
||||
@extend %ibo-font-ral-nor-100;
|
||||
}
|
||||
.ibo-quick-create--compartment-title{
|
||||
margin-bottom: $ibo-quick-create--compartment-title--margin-bottom;
|
||||
padding-left: $ibo-quick-create--compartment-title--padding-left;
|
||||
overflow-x: hidden;
|
||||
color: $ibo-quick-create--compartment-title--text-color;
|
||||
|
||||
> span{
|
||||
position: relative;
|
||||
|
||||
&::before,
|
||||
&::after{
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
height: 1px;
|
||||
width: 600px;
|
||||
border-top: 1px solid $ibo-quick-create--compartment-title--text-color;
|
||||
}
|
||||
&::before{
|
||||
right: 100%;
|
||||
margin-right: $ibo-quick-create--compartment-title--line-spacing;
|
||||
}
|
||||
&::after{
|
||||
left: 100%;
|
||||
margin-left: $ibo-quick-create--compartment-title--line-spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ibo-quick-create--compartment-content{
|
||||
color: $ibo-quick-create--compartment-content--text-color;
|
||||
}
|
||||
.ibo-quick-create--compartment-element{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
|
||||
&:not(:last-child){
|
||||
margin-bottom: $ibo-quick-create--compartment-element--margin-bottom;
|
||||
}
|
||||
}
|
||||
.ibo-quick-create--compartment-element-image{
|
||||
margin-right: $ibo-quick-create--compartment-element-image--margin-right;
|
||||
width: $ibo-quick-create--compartment-element-image--width;
|
||||
}
|
||||
.ibo-quick-create--compartment--placeholder{
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.ibo-quick-create--compartment--placeholder-image{
|
||||
width: $ibo-quick-create--compartment--placeholder-image--width;
|
||||
margin-top: $ibo-quick-create--compartment--placeholder-image--margin-top;
|
||||
margin-bottom: $ibo-quick-create--compartment--placeholder-image--margin-bottom;
|
||||
}
|
||||
.ibo-quick-create--compartment--placeholder-hint{
|
||||
text-align: justify;
|
||||
padding: $ibo-quick-create--compartment--placeholder-hint--padding-y $ibo-quick-create--compartment--placeholder-hint--padding-x;
|
||||
color: $ibo-quick-create--compartment--placeholder-hint--text-color;
|
||||
@extend %ibo-font-ral-ita-100;
|
||||
}
|
||||
@@ -17,6 +17,10 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
// Global create
|
||||
// Quick create
|
||||
Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Component:QuickCreate:Tooltip' => 'Quickly create any type of object',
|
||||
'UI:Component:QuickCreate:Input:Placeholder' => 'Select object type...',
|
||||
'UI:Component:QuickCreate:Recents:Title' => 'Recents',
|
||||
'UI:Component:QuickCreate:LastClasses:NoClass:Placeholder' => 'You haven\'t create any object yet',
|
||||
));
|
||||
1
images/illustrations/undraw-collection/duplicate.svg
Normal file
1
images/illustrations/undraw-collection/duplicate.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.2 KiB |
@@ -55,6 +55,7 @@ $(function()
|
||||
_bindEvents: function()
|
||||
{
|
||||
const me = this;
|
||||
const oBodyElem = $('body');
|
||||
|
||||
this.element.find(this.js_selectors.icon).on('click', function(oEvent){
|
||||
me._onIconClick(oEvent);
|
||||
@@ -65,19 +66,30 @@ $(function()
|
||||
this.element.find(this.js_selectors.compartment_element).on('click', function(oEvent){
|
||||
me._onCompartmentElementClick(oEvent, $(this));
|
||||
});
|
||||
$('body').on('click', function(oEvent){
|
||||
// Mostly for outside clicks that should close elements
|
||||
oBodyElem.on('click', function(oEvent){
|
||||
me._onBodyClick(oEvent);
|
||||
});
|
||||
// Mostly for hotkeys
|
||||
oBodyElem.on('keyup', function(oEvent){
|
||||
me._onBodyKeyUp(oEvent);
|
||||
});
|
||||
},
|
||||
_onIconClick: function(oEvent)
|
||||
{
|
||||
// Avoid anchor glitch
|
||||
oEvent.preventDefault();
|
||||
|
||||
// Open drawer
|
||||
this.element.toggleClass(this.css_classes.opened);
|
||||
// Focus in the input for a better UX
|
||||
this.element.find(this.js_selectors.input).trigger('focus');
|
||||
if(this._isDrawerOpened())
|
||||
{
|
||||
this._closeDrawer();
|
||||
}
|
||||
else
|
||||
{
|
||||
this._openDrawer();
|
||||
// Focus in the input for a better UX
|
||||
this._setFocusOnInput();
|
||||
}
|
||||
},
|
||||
_onFormSubmit: function(oEvent)
|
||||
{
|
||||
@@ -103,8 +115,43 @@ $(function()
|
||||
{
|
||||
if($(oEvent.target.closest('.ibo-global-search')).length === 0)
|
||||
{
|
||||
this.element.removeClass(this.css_classes.opened);
|
||||
this._closeDrawer();
|
||||
}
|
||||
},
|
||||
_onBodyKeyUp: function(oEvent)
|
||||
{
|
||||
// Note: We thought about extracting the oEvent.key in a variable to lower case it, but this would be done
|
||||
// on every single key up in the application, which might not be what we want... (time consuming)
|
||||
if((oEvent.altKey === true) && (oEvent.key === 'h' || oEvent.key === 'H'))
|
||||
{
|
||||
if(this._isDrawerOpened())
|
||||
{
|
||||
this._setFocusOnInput();
|
||||
}
|
||||
// If drawer is closed, we trigger the click on the icon in order for the other widget to behave like they should (eg. close themselves)
|
||||
else
|
||||
{
|
||||
this.element.find(this.js_selectors.icon).trigger('click');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Methods
|
||||
_isDrawerOpened: function()
|
||||
{
|
||||
return this.element.hasClass(this.css_classes.opened);
|
||||
},
|
||||
_openDrawer: function()
|
||||
{
|
||||
this.element.addClass(this.css_classes.opened);
|
||||
},
|
||||
_closeDrawer: function()
|
||||
{
|
||||
this.element.removeClass(this.css_classes.opened);
|
||||
},
|
||||
_setFocusOnInput: function()
|
||||
{
|
||||
this.element.find(this.js_selectors.input).trigger('focus');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
166
js/components/quick-create.js
Normal file
166
js/components/quick-create.js
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2020 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
;
|
||||
$(function()
|
||||
{
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'breadcrumbs' the widget name
|
||||
$.widget( 'itop.quick_create',
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
|
||||
},
|
||||
css_classes:
|
||||
{
|
||||
opened: 'ibo-quick-create--is-opened',
|
||||
},
|
||||
js_selectors:
|
||||
{
|
||||
icon: '[data-role="ibo-quick-create--icon"]',
|
||||
form: '[data-role="ibo-quick-create--head"]',
|
||||
input: '[data-role="ibo-quick-create--input"]',
|
||||
compartment_element: '[data-role="ibo-quick-create--compartment-element"]',
|
||||
},
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
this.element.addClass('ibo-quick-create');
|
||||
this._initializeMarkup();
|
||||
this._bindEvents();
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
this.element.removeClass('ibo-quick-create');
|
||||
},
|
||||
_initializeMarkup: function()
|
||||
{
|
||||
const me = this;
|
||||
|
||||
// Instantiate selectize.js on input
|
||||
this.element.find(this.js_selectors.input).selectize({
|
||||
openOnFocus: false,
|
||||
maxItems: 1
|
||||
});
|
||||
|
||||
// Remove some inline styling from the widget
|
||||
this.element.find('.selectize-input > input').css('width', '');
|
||||
},
|
||||
_bindEvents: function()
|
||||
{
|
||||
const me = this;
|
||||
const oBodyElem = $('body');
|
||||
|
||||
this.element.find(this.js_selectors.icon).on('click', function(oEvent){
|
||||
me._onIconClick(oEvent);
|
||||
});
|
||||
this.element.find(this.js_selectors.form).on('submit', function(oEvent){
|
||||
me._onFormSubmit(oEvent);
|
||||
});
|
||||
this.element.find(this.js_selectors.input).on('change', function(oEvent){
|
||||
me._onInputOptionSelected(oEvent, $(this));
|
||||
});
|
||||
// Mostly for outside clicks that should close elements
|
||||
oBodyElem.on('click', function(oEvent){
|
||||
me._onBodyClick(oEvent);
|
||||
});
|
||||
// Mostly for hotkeys
|
||||
oBodyElem.on('keyup', function(oEvent){
|
||||
me._onBodyKeyUp(oEvent);
|
||||
});
|
||||
},
|
||||
_onIconClick: function(oEvent)
|
||||
{
|
||||
// Avoid anchor glitch
|
||||
oEvent.preventDefault();
|
||||
|
||||
if(this._isDrawerOpened())
|
||||
{
|
||||
this._closeDrawer();
|
||||
}
|
||||
else
|
||||
{
|
||||
this._openDrawer();
|
||||
// Focus in the input for a better UX
|
||||
this._setFocusOnInput();
|
||||
}
|
||||
},
|
||||
_onFormSubmit: function(oEvent)
|
||||
{
|
||||
const sSearchValue = this.element.find(this.js_selectors.input).val();
|
||||
|
||||
// Submit form only if something in the input
|
||||
if(sSearchValue === '')
|
||||
{
|
||||
oEvent.preventDefault();
|
||||
}
|
||||
},
|
||||
_onInputOptionSelected: function(oEvent, oInputElem)
|
||||
{
|
||||
// Submit form directly on change
|
||||
this.element.find(this.js_selectors.form).trigger('submit');
|
||||
},
|
||||
_onBodyClick: function(oEvent)
|
||||
{
|
||||
if($(oEvent.target.closest('.ibo-quick-create')).length === 0)
|
||||
{
|
||||
this._closeDrawer();
|
||||
}
|
||||
},
|
||||
_onBodyKeyUp: function(oEvent)
|
||||
{
|
||||
// Note: We thought about extracting the oEvent.key in a variable to lower case it, but this would be done
|
||||
// on every single key up in the application, which might not be what we want... (time consuming)
|
||||
if((oEvent.altKey === true) && (oEvent.key === 'n' || oEvent.key === 'N'))
|
||||
{
|
||||
if(this._isDrawerOpened())
|
||||
{
|
||||
this._setFocusOnInput();
|
||||
}
|
||||
// If drawer is closed, we trigger the click on the icon in order for the other widget to behave like they should (eg. close themselves)
|
||||
else
|
||||
{
|
||||
this.element.find(this.js_selectors.icon).trigger('click');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Methods
|
||||
_isDrawerOpened: function()
|
||||
{
|
||||
return this.element.hasClass(this.css_classes.opened);
|
||||
},
|
||||
_openDrawer: function()
|
||||
{
|
||||
this.element.addClass(this.css_classes.opened);
|
||||
},
|
||||
_closeDrawer: function()
|
||||
{
|
||||
this.element.removeClass(this.css_classes.opened);
|
||||
},
|
||||
_setFocusOnInput: function()
|
||||
{
|
||||
this.element.find('.selectize-input > input').trigger('focus');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -60,8 +60,8 @@ $(function()
|
||||
},
|
||||
_bindEvents: function()
|
||||
{
|
||||
var me = this;
|
||||
var oBodyElem = $('body');
|
||||
const me = this;
|
||||
const oBodyElem = $('body');
|
||||
|
||||
// Click on collapse/expand toggler
|
||||
this.element.find(this.js_selectors.menu_toggler).on('click', function(oEvent){
|
||||
@@ -241,7 +241,9 @@ $(function()
|
||||
// Menus filter methods
|
||||
_focusFilter: function()
|
||||
{
|
||||
this.element.find(this.js_selectors.menu_filter_input).trigger('focus');
|
||||
this.element.find(this.js_selectors.menu_filter_input)
|
||||
.trigger('click')
|
||||
.trigger('focus');
|
||||
},
|
||||
/**
|
||||
* Remove the current filter value and reset the menu nodes display
|
||||
|
||||
@@ -139,6 +139,7 @@ return array(
|
||||
'CheckableExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
|
||||
'Combodo\\iTop\\Application\\Branding' => $baseDir . '/sources/application/Branding.php',
|
||||
'Combodo\\iTop\\Application\\GlobalSearch\\GlobalSearchHelper' => $baseDir . '/sources/application/GlobalSearch/GlobalSearchHelper.php',
|
||||
'Combodo\\iTop\\Application\\QuickCreate\\QuickCreateHelper' => $baseDir . '/sources/application/QuickCreate/QuickCreateHelper.php',
|
||||
'Combodo\\iTop\\Application\\Search\\AjaxSearchException' => $baseDir . '/sources/application/search/ajaxsearchexception.class.inc.php',
|
||||
'Combodo\\iTop\\Application\\Search\\CriterionConversionAbstract' => $baseDir . '/sources/application/search/criterionconversionabstract.class.inc.php',
|
||||
'Combodo\\iTop\\Application\\Search\\CriterionConversion\\CriterionToOQL' => $baseDir . '/sources/application/search/criterionconversion/criteriontooql.class.inc.php',
|
||||
|
||||
@@ -369,6 +369,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'CheckableExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
|
||||
'Combodo\\iTop\\Application\\Branding' => __DIR__ . '/../..' . '/sources/application/Branding.php',
|
||||
'Combodo\\iTop\\Application\\GlobalSearch\\GlobalSearchHelper' => __DIR__ . '/../..' . '/sources/application/GlobalSearch/GlobalSearchHelper.php',
|
||||
'Combodo\\iTop\\Application\\QuickCreate\\QuickCreateHelper' => __DIR__ . '/../..' . '/sources/application/QuickCreate/QuickCreateHelper.php',
|
||||
'Combodo\\iTop\\Application\\Search\\AjaxSearchException' => __DIR__ . '/../..' . '/sources/application/search/ajaxsearchexception.class.inc.php',
|
||||
'Combodo\\iTop\\Application\\Search\\CriterionConversionAbstract' => __DIR__ . '/../..' . '/sources/application/search/criterionconversionabstract.class.inc.php',
|
||||
'Combodo\\iTop\\Application\\Search\\CriterionConversion\\CriterionToOQL' => __DIR__ . '/../..' . '/sources/application/search/criterionconversion/criteriontooql.class.inc.php',
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\GlobalSearch\GlobalSearchHelper;
|
||||
use Combodo\iTop\Application\QuickCreate\QuickCreateHelper;
|
||||
|
||||
/**
|
||||
* Displays a popup welcome message, once per session at maximum
|
||||
@@ -1266,6 +1267,7 @@ HTML
|
||||
|
||||
utils::RemoveTransaction($sTransactionId);
|
||||
$oP->set_title(Dict::S('UI:PageTitle:ObjectCreated'));
|
||||
QuickCreateHelper::AddClassToHistory($sClass);
|
||||
|
||||
// Compute the name, by reloading the object, even if it disappeared from the silo
|
||||
$oObj = MetaModel::GetObject($sClass, $oObj->GetKey(), true /* Must be found */, true /* Allow All Data*/);
|
||||
|
||||
123
sources/application/QuickCreate/QuickCreateHelper.php
Normal file
123
sources/application/QuickCreate/QuickCreateHelper.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2020 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Application\QuickCreate;
|
||||
|
||||
|
||||
use appUserPreferences;
|
||||
use DBObject;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* Class QuickCreateHelper
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
* @package Combodo\iTop\Application\QuickCreate
|
||||
* @since 2.8.0
|
||||
*/
|
||||
class QuickCreateHelper
|
||||
{
|
||||
const MAX_HISTORY_SIZE = 10;
|
||||
const USER_PREF_CODE = 'quick_create_history';
|
||||
|
||||
/**
|
||||
* Add $sQuery to the history. History is limited to the static::MAX_HISTORY_SIZE last classes.
|
||||
*
|
||||
* @param string $Class Class of the created object
|
||||
*
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function AddClassToHistory($Class)
|
||||
{
|
||||
$aNewEntry = [
|
||||
'class' => $Class,
|
||||
];
|
||||
|
||||
/** @var array $aHistoryEntries */
|
||||
$aHistoryEntries = appUserPreferences::GetPref(static::USER_PREF_CODE, []);
|
||||
|
||||
// Remove same entry from history to avoid duplicates
|
||||
for($iIdx = 0; $iIdx < count($aHistoryEntries); $iIdx++)
|
||||
{
|
||||
if($aHistoryEntries[$iIdx]['class'] === $Class)
|
||||
{
|
||||
unset($aHistoryEntries[$iIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new entry
|
||||
array_unshift($aHistoryEntries, $aNewEntry);
|
||||
|
||||
// Truncate history
|
||||
if(count($aHistoryEntries) > static::MAX_HISTORY_SIZE)
|
||||
{
|
||||
$aHistoryEntries = array_slice($aHistoryEntries, 0, static::MAX_HISTORY_SIZE);
|
||||
}
|
||||
|
||||
appUserPreferences::SetPref(static::USER_PREF_CODE, $aHistoryEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of past created object classes
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public static function GetLastClasses()
|
||||
{
|
||||
$aHistoryEntries = appUserPreferences::GetPref(static::USER_PREF_CODE, []);
|
||||
|
||||
for($iIdx = 0; $iIdx < count($aHistoryEntries); $iIdx++)
|
||||
{
|
||||
$sClass = $aHistoryEntries[$iIdx]['class'];
|
||||
|
||||
// Add class icon
|
||||
if(!isset($aHistoryEntries[$iIdx]['icon_url']))
|
||||
{
|
||||
$sClassIconUrl = MetaModel::GetClassIcon($sClass, false);
|
||||
// Mind that some classes don't have an icon
|
||||
if(!empty($sClassIconUrl))
|
||||
{
|
||||
$aHistoryEntries[$iIdx]['icon_url'] = $sClassIconUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Add class label
|
||||
if(!isset($aHistoryEntries[$iIdx]['label_html']))
|
||||
{
|
||||
$aHistoryEntries[$iIdx]['label_html'] = utils::HtmlEntities(MetaModel::GetName($sClass));
|
||||
}
|
||||
|
||||
// Add url
|
||||
if(!isset($aHistoryEntries[$iIdx]['target_url']))
|
||||
{
|
||||
$aHistoryEntries[$iIdx]['target_url'] = DBObject::ComputeStandardUIPage($sClass).'?operation=new&class='.$sClass;
|
||||
}
|
||||
}
|
||||
|
||||
return $aHistoryEntries;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1 @@
|
||||
<div id="{{ aBreadCrumbs.sId }}" class="ibo-breadcrumbs"></div>
|
||||
|
||||
{# TODO: Move this to a dedicated script file #}
|
||||
{% if aBreadCrumbs.aWidgetOptions is defined %}
|
||||
<script type="text/javascript" src="../js/components/breadcrumbs.js"></script>
|
||||
<script type="text/javascript">
|
||||
setTimeout(function(){
|
||||
$('#{{ aBreadCrumbs.sId }}').breadcrumbs({{ aBreadCrumbs.aWidgetOptions|json_encode|raw }});
|
||||
}, 500);
|
||||
</script>
|
||||
{% endif %}
|
||||
<div id="{{ aBreadCrumbs.sId }}" class="ibo-breadcrumbs"></div>
|
||||
@@ -27,12 +27,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# TODO: Move this to a dedicated script file #}
|
||||
<script type="text/javascript" src="../js/components/global-search.js"></script>
|
||||
<script type="text/javascript">
|
||||
setTimeout(function(){
|
||||
$('#{{ aGlobalSearch.sId }}').global_search();
|
||||
}, 500);
|
||||
</script>
|
||||
</div>
|
||||
36
templates/components/quick-create/layout.html.twig
Normal file
36
templates/components/quick-create/layout.html.twig
Normal file
@@ -0,0 +1,36 @@
|
||||
<div id="{{ aQuickCreate.sId }}" class="ibo-quick-create" data-role="ibo-quick-create">
|
||||
<form action="{{ aQuickCreate.sEndpoint }}" method="get" class="ibo-quick-create--head" data-role="ibo-quick-create--head">
|
||||
<input type="hidden" name="operation" value="new" />
|
||||
<a href="#" class="ibo-quick-create--icon fas fa-plus" data-role="ibo-quick-create--icon" data-tooltip-content="{{ 'UI:Component:QuickCreate:Tooltip'|dict_s }}" data-tooltip-placement="bottom-start" data-tooltip-distance-offset="25"></a>
|
||||
<select name="class" class="ibo-quick-create--input" data-role="ibo-quick-create--input" placeholder="{{ 'UI:Component:QuickCreate:Input:Placeholder'|dict_s }}">
|
||||
<option value="">{{ 'UI:Component:QuickCreate:Input:Placeholder'|dict_s }}</option>
|
||||
{% for sClassCode, sClassLabel in aQuickCreate.aAvailableClasses %}
|
||||
<option value="{{ sClassCode }}">{{ sClassLabel }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
<div class="ibo-quick-create--drawer" data-role="ibo-quick-create--drawer">
|
||||
<div class="ibo-quick-create--compartment">
|
||||
<div class="ibo-quick-create--compartment-title" data-role="ibo-quick-create--compartment-title">
|
||||
<span>{{ 'UI:Component:QuickCreate:Recents:Title'|dict_s }}</span>
|
||||
</div>
|
||||
<div class="ibo-quick-create--compartment-content" data-role="ibo-quick-create--compartment-content">
|
||||
{% if aQuickCreate.aLastClasses|length > 0 %}
|
||||
{% for aClass in aQuickCreate.aLastClasses %}
|
||||
<a href="{{ aClass.target_url }}" class="ibo-quick-create--compartment-element" data-role="ibo-quick-create--compartment-element" data-class-code="{{ aQuery.class }}">
|
||||
{% if aClass.icon_url is defined %}
|
||||
<img src="{{ aClass.icon_url}}" class="ibo-quick-create--compartment-element-image" />
|
||||
{% endif %}
|
||||
{{ aClass.label_html|raw }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="ibo-quick-create--compartment--placeholder">
|
||||
<img class="ibo-quick-create--compartment--placeholder-image" src="{{ aPage.sAbsoluteUrlAppRoot }}images/illustrations/undraw-collection/duplicate.svg" />
|
||||
<div class="ibo-quick-create--compartment--placeholder-hint">{{ 'UI:Component:QuickCreate:LastClasses:NoClass:Placeholder'|dict_s }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,12 +34,4 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{# TODO: Move this to a dedicated script file #}
|
||||
<script type="text/javascript" src="../js/layouts/navigation-menu.js"></script>
|
||||
<script type="text/javascript">
|
||||
setTimeout(function(){
|
||||
$('#{{ aNavigationMenu.sId }}').navigation_menu();
|
||||
}, 500);
|
||||
</script>
|
||||
</nav>
|
||||
@@ -1,5 +1,6 @@
|
||||
<nav id="{{ aTopBar.sId }}" class="ibo-top-bar">
|
||||
<div class="ibo-top-bar--quick-actions">
|
||||
{{ include('components/quick-create/layout.html.twig', { aQuickCreate: aTopBar.aComponents.aQuickCreate }) }}
|
||||
{{ include('components/global-search/layout.html.twig', { aGlobalSearch: aTopBar.aComponents.aGlobalSearch }) }}
|
||||
</div>
|
||||
{{ include('components/breadcrumbs/layout.html.twig', { aBreadCrumbs: aTopBar.aComponents.aBreadCrumbs }) }}
|
||||
|
||||
@@ -30,12 +30,6 @@
|
||||
</style>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block iboPageJsFiles %}
|
||||
{% for sJsFile in aPage.aJsFiles %}
|
||||
<script type="text/javascript" src="{{ sJsFile|add_itop_version }}"></script>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body data-gui-type="backoffice">
|
||||
{{ include('layouts/navigation-menu/layout.html.twig', { aNavigationMenu: aLayouts.aNavigationMenu }) }}
|
||||
@@ -46,6 +40,12 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{% block iboPageJsFiles %}
|
||||
{% for sJsFile in aPage.aJsFiles %}
|
||||
<script type="text/javascript" src="{{ sJsFile|add_itop_version }}"></script>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block iboPageJsInlineScripts %}
|
||||
<script type="text/javascript">
|
||||
{# TODO: How to do this in native JS? #}
|
||||
@@ -57,9 +57,11 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block iboPageJsInlineOnDomReady %}
|
||||
{% for sJsInline in aPage.aJsInlineOnDomReady %}
|
||||
{{ sJsInline|raw }}
|
||||
{% endfor %}
|
||||
setTimeout(function(){
|
||||
{% for sJsInline in aPage.aJsInlineOnDomReady %}
|
||||
{{ sJsInline|raw }}
|
||||
{% endfor %}
|
||||
}, 50);
|
||||
{% endblock %}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user