diff --git a/src/UI/Implementation/Component/MainControls/Renderer.php b/src/UI/Implementation/Component/MainControls/Renderer.php
index 4c39bdf8068c..145f67260e3e 100644
--- a/src/UI/Implementation/Component/MainControls/Renderer.php
+++ b/src/UI/Implementation/Component/MainControls/Renderer.php
@@ -1,7 +1,5 @@
trigger_signals[] = $trigger_signal;
$btn_removetool = $close_buttons[$entry_id]
->withAdditionalOnloadCode(
- fn ($id) => "il.UI.maincontrols.mainbar.addPartIdAndEntry('$mb_id', 'remover', '$id', true);"
+ fn($id) => "il.UI.maincontrols.mainbar.addPartIdAndEntry('$mb_id', 'remover', '$id', true);"
)
->withOnClick($trigger_signal);
@@ -268,13 +268,16 @@ function ($id) use ($signals) {
$entry_signal = $signals['entry'];
$close_slates_signal = $signals['close_slates'];
return "
- il.UI.maincontrols.metabar.registerSignals(
- '$id',
+ il.UI.maincontrols.metabar.init('$id');
+ il.UI.maincontrols.metabar.get('$id').registerSignals(
'$entry_signal',
'$close_slates_signal',
);
- il.UI.maincontrols.metabar.init();
- $(window).resize(il.UI.maincontrols.metabar.init);
+ il.UI.maincontrols.metabar.get('$id').init();
+ window.addEventListener(
+ 'resize',
+ ()=>{il.UI.maincontrols.metabar.get('$id').init()}
+ );
";
}
);
@@ -321,13 +324,13 @@ protected function renderSystemInfo(
$close = $close->withOnClick($signal);
$tpl->setVariable('CLOSE_BUTTON', $default_renderer->render($close));
$tpl->setVariable('CLOSE_URI', (string) $component->getDismissAction());
- $component = $component->withAdditionalOnLoadCode(fn ($id) => "$(document).on('$signal', function() { il.UI.maincontrols.system_info.close('$id'); });");
+ $component = $component->withAdditionalOnLoadCode(fn($id) => "$(document).on('$signal', function() { il.UI.maincontrols.system_info.close('$id'); });");
}
$more = $this->getUIFactory()->symbol()->glyph()->more("#");
$tpl->setVariable('MORE_BUTTON', $default_renderer->render($more));
- $component = $component->withAdditionalOnLoadCode(fn ($id) => "il.UI.maincontrols.system_info.init('$id')");
+ $component = $component->withAdditionalOnLoadCode(fn($id) => "il.UI.maincontrols.system_info.init('$id')");
$id = $this->bindJavaScript($component);
$tpl->setVariable('ID', $id);
@@ -454,7 +457,7 @@ public function registerResources(ResourceRegistry $registry): void
{
parent::registerResources($registry);
$registry->register('./src/UI/templates/js/MainControls/dist/mainbar.js');
- $registry->register('./src/UI/templates/js/MainControls/metabar.js');
+ $registry->register('./src/UI/templates/js/MainControls/dist/maincontrols.min.js');
$registry->register('./src/GlobalScreen/Client/dist/GS.js');
$registry->register('./src/UI/templates/js/MainControls/system_info.js');
}
diff --git a/src/UI/Implementation/Component/MainControls/Slate/Renderer.php b/src/UI/Implementation/Component/MainControls/Slate/Renderer.php
index e8d124d7d0d7..7a0b59ebf26c 100644
--- a/src/UI/Implementation/Component/MainControls/Slate/Renderer.php
+++ b/src/UI/Implementation/Component/MainControls/Slate/Renderer.php
@@ -65,7 +65,7 @@ protected function getCombinedSlateContents(
$triggerer = $triggerer
->withOnClick($trigger_signal)
->withAdditionalOnLoadCode(
- fn ($id) => "
+ fn($id) => "
il.UI.maincontrols.mainbar.addTriggerSignal('{$trigger_signal}');
il.UI.maincontrols.mainbar.addPartIdAndEntry('{$mb_id}', 'triggerer', '{$id}');
"
@@ -132,9 +132,15 @@ protected function renderSlate(
$component = $component->withAdditionalOnLoadCode(
function ($id) use ($slate_signals, $mb_id): string {
- $js = "fn = il.UI.maincontrols.slate.onSignal;";
+ $js = "";
foreach ($slate_signals as $key => $signal) {
- $js .= "$(document).on('{$signal}', function(event, signalData) { fn('{$key}', event, signalData, '{$id}'); return false;});";
+ $js .= "$(document).on('{$signal}', function(event, signalData) {
+ il.UI.maincontrols.slate.onSignal('{$key}',
+ event,
+ signalData,
+ '{$id}'
+ );
+ return false;});";
}
if ($mb_id) {
@@ -168,7 +174,7 @@ protected function renderNotificationSlate(
public function registerResources(\ILIAS\UI\Implementation\Render\ResourceRegistry $registry): void
{
parent::registerResources($registry);
- $registry->register('./src/UI/templates/js/MainControls/slate.js');
+ $registry->register('./src/UI/templates/js/MainControls/dist/maincontrols.min.js');
}
/**
diff --git a/src/UI/templates/js/MainControls/dist/maincontrols.min.js b/src/UI/templates/js/MainControls/dist/maincontrols.min.js
new file mode 100644
index 000000000000..3b2b393ffaa6
--- /dev/null
+++ b/src/UI/templates/js/MainControls/dist/maincontrols.min.js
@@ -0,0 +1,15 @@
+/**
+ * This file is part of ILIAS, a powerful learning management system
+ * published by ILIAS open source e-Learning e.V.
+ *
+ * ILIAS is licensed with the GPL-3.0,
+ * see https://www.gnu.org/licenses/gpl-3.0.en.html
+ * You should have received a copy of said license along with the
+ * source code, too.
+ *
+ * If this is not the case or you just want to try ILIAS, you'll find
+ * us at:
+ * https://www.ilias.de
+ * https://github.com/ILIAS-eLearning
+ */
+!function(t,e){"use strict";function n(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var s=n(t),a=n(e);const i="engaged",o="il-maincontrols-metabar",r="il-metabar-slates",l="il-metabar-more-button",u="il-metabar-more-slate",c="il-maincontrols-slate";function h(t){t.removeClass(i),t.attr("aria-expanded",!1)}class g{#t;#e;#n;#s;#a;#i;#o;constructor(t,e,n,s,a,i){this.#t=t,this.#e=e,this.#s=n,this.#a=s,this.#i=a,this.#o=i}registerSignals(t,e){this.#t(document).on(t,((t,e)=>(this.#r(t,e),this.#s()&&this.#i(),!1))),this.#t(document).on(e,(()=>(this.onClickDisengageAll(),!1))),this.#t(`.${o}`).on("click",(()=>{this.#n=!0})),this.#t("body").on("click",(()=>{this.#n?this.#n=!1:this.onClickDisengageAll()})),this.#t(`.${r} > .${c}`).on("focusout",(t=>{if(!this.#s()){const e=t.relatedTarget,n=t.currentTarget;this.#t.contains(n,e)||this.onClickDisengageAll()}}))}#r(t,e){const n=e.triggerer;!function(t){return t.hasClass(i)}(n)?(this.disengageAllSlates(),this.disengageAllButtons(),0===n.parents(`.${u}`).length&&function(t){t.addClass(i),t.attr("aria-expanded",!0)}(n)):h(n)}onClickDisengageAll(){this.disengageAllButtons(),this.disengageAllSlates()}disengageAllButtons(){this.#t(`#${this.#e}.${o}`).children("li").children(`.btn.${i}`).each(((t,e)=>{h(this.#t(e))}))}disengageAllSlates(){this.getEngagedSlates().each(((t,e)=>{this.#o(this.#t(e))}))}disengageAll(){this.disengageAllSlates(),this.disengageAllButtons()}getEngagedSlates(){const t=`#${this.#e} .${c}.engaged`;return this.#t(t)}init(){this.#l(),this.#u(),this.#s()?this.#c():this.#h(),this.#t(`.${o}`).css("visibility","visible"),this.#t(`#${this.#e} .${r}`).children(`.${c}`).attr("aria-hidden",!0)}#c(){this.#g(),this.#d().hide(),this.getMoreButton().show(),this.#y()}#h(){this.getMoreButton().hide(),this.#d().show()}#l(){if(0===this.getMoreButton().length){const t=this.#t(`#${this.#e}.${o}`).find(".btn, .il-link").last();this.#t(t).addClass(l)}}#u(){if(0===this.#S().length){const t=this.#t(`#${this.#e} .${r}`).children(`.${c}`).last();this.#t(t).addClass(u)}}getMoreButton(){return this.#t(`.${l}`)}#S(){return this.#t(`.${u}`)}#d(){return this.#t(`#${this.#e}.${o}`).children("li").children(".btn, .il-link").not(`.${l}`)}#g(){const t=this.#S().children(".il-maincontrols-slate-content");0===t.children().length&&this.#d().clone(!0,!0).appendTo(t)}#y(){const t=this.#a.getCounterObjectOrNull(this.#S());t&&this.#a.getCounterObject(this.getMoreButton()).setNoveltyTo(t.getNoveltyCount()).setStatusTo(t.getStatusCount())}}const d="engaged",y="disengaged";function S(t){return t.hasClass(d)}function f(t){t.removeClass(y),t.addClass(d),t.attr("aria-expanded","true"),t.attr("aria-hidden","false")}function m(t){t.removeClass(d),t.addClass(y),t.attr("aria-expanded","false"),t.attr("aria-hidden","true")}function b(t){S(t)?m(t):f(t)}var C=function(t){var e="il-counter",n={getCounterObject:function(t){let e;return e=s(t),console.assert(e.length>0,"Passed jQuery Object does not contain a counter"),a(e)},getCounterObjectOrNull:function(t){let e;return e=s(t),0===e.length?null:a(e)}},s=function(n){console.assert(n instanceof t,"$object_containing_counter is not a jQuery Object, param: "+n);var s=n;return n.hasClass(e)||(s=n.find("."+e)),s},a=function(e){return p.bind({})(e,t)};return n},p=function(t,e){const n=" Counter does not exist in the DOM. Make sure the respective Counter type has been rendered before applying this operations.",s=" is not a number";this.getStatusCount=function(){return r(o(t))},this.getNoveltyCount=function(){return r(i(t))},this.hasNoveltyObject=function(){return i(t).length>0},this.hasStatusObject=function(){return o(t).length>0},this.setNoveltyTo=function(e){console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s);var a=i(t);return a.html(e),0===e?a.hide():a.show(),this},this.setStatusTo=function(e){console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s);var a=o(t);return a.html(e),0===e?a.hide():a.show(),this},this.incrementNoveltyCount=function(e){return console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasNoveltyObject()&&t.setNoveltyTo(t.getNoveltyCount()+e)}),t),this},this.decrementNoveltyCount=function(e){return console.assert(this.hasNoveltyObject(),"Novelty "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasNoveltyObject()&&t.setNoveltyTo(t.getNoveltyCount()-e)}),t),this},this.incrementStatusCount=function(e){return console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasStatusObject()&&t.setStatusTo(t.getStatusCount()+e)}),t),this},this.decrementStatusCount=function(e){return console.assert(this.hasStatusObject(),"Status "+n),console.assert("number"==typeof e,e+s),l((function(t){t.hasStatusObject()&&t.setStatusTo(t.getStatusCount()-e)}),t),this},this.setTotalNoveltyToStatusCount=function(){return console.assert(this.hasStatusObject(),"Status "+n),console.assert(this.hasNoveltyObject(),"Novelty "+n),this.incrementStatusCount(this.getNoveltyCount()).setNoveltyTo(0)};var a={getNoveltyCount:this.getNoveltyCount,getStatusCount:this.getStatusCount,hasNoveltyObject:this.hasNoveltyObject,hasStatusObject:this.hasStatusObject,setNoveltyTo:this.setNoveltyTo,setStatusTo:this.setStatusTo,incrementNoveltyCount:this.incrementNoveltyCount,decrementNoveltyCount:this.decrementNoveltyCount,incrementStatusCount:this.incrementStatusCount,decrementStatusCount:this.decrementStatusCount,setTotalNoveltyToStatusCount:this.setTotalNoveltyToStatusCount},i=function(t){return t.find(".il-counter-novelty")},o=function(t){return t.find(".il-counter-status")},r=function(t){var n=0;return t.each((function(){var t=e(this).text();n+=parseInt(t)})),n},l=function(t,n){n.each((function(){var n=C(e).getCounterObject(e(this));t(n,e(this))}))};return a};s.default.UI=s.default.UI||{},s.default.UI.maincontrols=s.default.UI.maincontrols||{},s.default.UI.maincontrols.metabar=new class{#t;#f=[];#s;#a;#i;#o;constructor(t,e,n,s,a){this.#t=t,this.#s=e,this.#a=n,this.#i=s,this.#o=a}init(t){if(void 0!==this.#f[t])throw new Error(`Metabar with id '${t}' has already been initialized.`);this.#f[t]=new g(this.#t,t,this.#s,this.#a,this.#i,this.#o)}get(t){return this.#f[t]??null}disengageAll(){Object.values(this.#f).forEach((t=>t.disengageAll()))}}(a.default,s.default.UI.page.isSmallScreen,C(a.default),(()=>s.default.UI.maincontrols.mainbar.disengageAll()),(t=>s.default.UI.maincontrols.slate.disengage(t))),s.default.UI.maincontrols.slate=new class{#t;#m;#b;constructor(t,e,n){this.#t=t,this.#m=e,this.#b=n}onSignal(t,e,n,s){const a=this.#t(`#${s}`),{triggerer:i}=n,o=i.parents(".il-metabar-more-slate").length>0;if("toggle"===t)this.#C(a,i,o);else if("engage"===t)f(a);else{if("replace"!==t)throw new Error(`No such SignalType: ${t}`);this.#p(s,n)}}#C(t,e,n){const s=t.closest(".il-maincontrols-metabar").attr("id"),a=this.#b.get(s);e.attr("id")!==a.getMoreButton().attr("id")?(b(t),n||(S(t)?(e.addClass(d),e.removeClass(y),t.trigger("in_view")):(e.removeClass(d),e.addClass(y)))):a.getEngagedSlates().length>0?a.disengageAllSlates():b(t)}disengage=m;#p(t,e){const{url:n}=e.options;this.#m(t,n,"content")}}(a.default,function(t){return function(e,n,s){t.ajax({url:n,dataType:"html"}).done((function(n){var a=t("
"+n+"
"),i=a.find("[data-replace-marker='"+s+"']").first();0==i.length?t("#"+e+" [data-replace-marker='"+s+"']").html(n):(t("#"+e+" [data-replace-marker='"+s+"']").first().replaceWith(i),t("#"+e+" [data-replace-marker='"+s+"']").first().after(a.find("[data-replace-marker='script']")))}))}}(a.default),s.default.UI.maincontrols.metabar)}(il,$);
diff --git a/src/UI/templates/js/MainControls/metabar.js b/src/UI/templates/js/MainControls/metabar.js
deleted file mode 100644
index 7e7a2da8ef90..000000000000
--- a/src/UI/templates/js/MainControls/metabar.js
+++ /dev/null
@@ -1,216 +0,0 @@
-il = il || {};
-il.UI = il.UI || {};
-il.UI.maincontrols = il.UI.maincontrols || {};
-
-(function($, maincontrols) {
- maincontrols.metabar = (function($) {
-
- var id
- ,_cls_btn_engaged = 'engaged'
- ,_cls_entries = 'il-maincontrols-metabar'
- ,_cls_slates = 'il-metabar-slates'
- ,_cls_slate = 'il-maincontrols-slate'
- ,_cls_more_btn = 'il-metabar-more-button'
- ,_cls_more_slate = 'il-metabar-more-slate'
- ,_cls_single_slate = false //class of one single slate, will be set on registerSignals
- ,_cls_slate_engaged = false //engaged class of a slate, will be set on registerSignals
- ;
-
- var propagation_stopped;
-
- var registerSignals = function (
- component_id,
- entry_signal,
- close_slates_signal
- ) {
- id = component_id;
- _cls_single_slate = il.UI.maincontrols.slate._cls_single_slate;
- _cls_slate_engaged = il.UI.maincontrols.slate._cls_engaged;
-
- $(document).on(entry_signal, function(event, signalData) {
- onClickEntry(event, signalData);
- if(il.UI.page.isSmallScreen() && il.UI.maincontrols.mainbar) {
- il.UI.maincontrols.mainbar.disengageAll();
- }
- return false;
- });
- $(document).on(close_slates_signal, function(event, signalData) {
- onClickDisengageAll();
- return false;
- });
-
- //close metabar when user clicks anywhere
- $('.'+_cls_entries).on('click', function(event) {
- propagation_stopped = true;
-
- });
- $('body').on('click', function(event) {
- if(propagation_stopped) {
- propagation_stopped = false
- } else {
- onClickDisengageAll();
- }
- });
-
- //close metabar slate when focus moves out
- $('.'+_cls_slates+' > .'+_cls_slate).on('focusout', function(event) {
- if(!il.UI.page.isSmallScreen()) {
- let next_focus_target = event.relatedTarget;
- let current_slate = event.currentTarget;
- if (!$.contains(current_slate, next_focus_target)) {
- onClickDisengageAll();
- }
- }
- });
- };
-
- var onClickEntry = function(event, signalData) {
- var btn = signalData.triggerer;
-
- if(btn.attr('id') === _getMoreButton().attr('id')) {
- return;
- }
-
- if(_isEngaged(btn)) {
- _disengageButton(btn);
- } else {
- _disengageAllSlates();
- _disengageAllButtons();
- if(btn.parents('.' + _cls_more_slate).length == 0) {
- _engageButton(btn);
- }
- }
- };
- var onClickDisengageAll = function() {
- _disengageAllButtons();
- _disengageAllSlates();
- };
- var _engageButton = function(btn) {
- btn.addClass(_cls_btn_engaged);
- btn.attr('aria-expanded', true);
- };
- var _disengageButton = function(btn) {
- btn.removeClass(_cls_btn_engaged);
- btn.attr('aria-expanded', false);
- };
- var _isEngaged = function(btn) {
- return btn.hasClass(_cls_btn_engaged);
- };
- var _disengageAllButtons = function() {
- $('#' + id +'.' + _cls_entries)
- .children('li').children('.btn.' + _cls_btn_engaged)
- .each(
- function(i, btn) {
- _disengageButton($(btn));
- }
- )
- };
- var _disengageAllSlates = function() {
- getEngagedSlates().each(
- function(i, slate) {
- il.UI.maincontrols.slate.disengage($(slate));
- }
- )
- };
- var disengageAll = function() {
- _disengageAllSlates();
- _disengageAllButtons();
- };
- var getEngagedSlates = function() {
- var search = '#' + id
- + ' .' + _cls_single_slate
- + '.' + _cls_slate_engaged;
-
- return $(search);
- };
-
- /**
- * decide and init condensed/wide version
- */
- var init = function () {
- _tagMoreButton();
- _tagMoreSlate();
- il.UI.page.isSmallScreen() ? _initCondensed() : _initWide();
- //unfortunately, this does not work properly via a class
- $('.' + _cls_entries).css("visibility","visible");
- $('#' + id +' .' + _cls_slates).children('.' + _cls_single_slate)
- .attr('aria-hidden', true)
- };
-
- var _initCondensed = function () {
- _initMoreSlate();
- _getMetabarEntries().hide();
- _getMoreButton().show();
- collectCounters();
- };
-
- var _initWide = function () {
- _getMoreButton().hide();
- _getMetabarEntries().show();
- };
-
- var _tagMoreButton = function() {
- if(_getMoreButton().length === 0) {
- var entries = $('#' + id +'.' + _cls_entries).find('.btn, .il-link'),
- more = entries.last();
- $(more).addClass(_cls_more_btn);
- }
- }
-
- var _tagMoreSlate = function() {
- if(_getMoreSlate().length === 0) {
- var slates = $('#' + id +' .' + _cls_slates).children('.' + _cls_single_slate),
- more = slates.last();
- $(more).addClass(_cls_more_slate);
- }
- }
-
- var _getMoreButton = function() {
- return $('.' + _cls_more_btn);
- }
-
- var _getMoreSlate = function() {
- return $('.' + _cls_more_slate);
- }
-
- var _getMetabarEntries = function() {
- return $('#' + id +'.' + _cls_entries)
- .children('li').children('.btn, .il-link')
- .not('.' + _cls_more_btn);
- }
-
- var _initMoreSlate = function() {
- var content = _getMoreSlate().children('.il-maincontrols-slate-content');
- if(content.children().length == 0) {
- _getMetabarEntries().clone(true, true)
- .appendTo(content);
- }
- }
-
- var _getAllSlates = function() {
- return $('#' + id + ' .' + _cls_single_slate)
- .not('.' + _cls_more_slate);
- }
-
- var collectCounters = function() {
- var $more_slate_counter = il.UI.counter.getCounterObjectOrNull(_getMoreSlate());
- if($more_slate_counter){
- il.UI.counter.getCounterObject(_getMoreButton())
- .setNoveltyTo($more_slate_counter.getNoveltyCount())
- .setStatusTo($more_slate_counter.getStatusCount())
- };
- }
-
- return {
- registerSignals: registerSignals,
- init: init,
- collectCounters: collectCounters,
- _getMoreButton: _getMoreButton,
- getEngagedSlates: getEngagedSlates,
- _disengageAllSlates: _disengageAllSlates,
- disengageAll: disengageAll
- }
-
- })($);
-})($, il.UI.maincontrols);
-
diff --git a/src/UI/templates/js/MainControls/rollup.config.js b/src/UI/templates/js/MainControls/rollup.config.js
index 342fe092a83d..4fdbcdfa686b 100644
--- a/src/UI/templates/js/MainControls/rollup.config.js
+++ b/src/UI/templates/js/MainControls/rollup.config.js
@@ -1,7 +1,51 @@
-export default {
- input: './src/mainbar.js',
- output: {
- file: './dist/mainbar.js',
- format: 'es'
- }
-};
\ No newline at end of file
+/**
+ * This file is part of ILIAS, a powerful learning management system
+ * published by ILIAS open source e-Learning e.V.
+ *
+ * ILIAS is licensed with the GPL-3.0,
+ * see https://www.gnu.org/licenses/gpl-3.0.en.html
+ * You should have received a copy of said license along with the
+ * source code, too.
+ *
+ * If this is not the case or you just want to try ILIAS, you'll find
+ * us at:
+ * https://www.ilias.de
+ * https://github.com/ILIAS-eLearning
+ *
+ ******************************************************************** */
+
+import terser from '@rollup/plugin-terser';
+import copyright from '../../../../../CI/Copyright-Checker/copyright';
+import preserveCopyright from '../../../../../CI/Copyright-Checker/preserveCopyright';
+
+export default [
+ {
+ input: './src/mainbar.js',
+ output: {
+ file: './dist/mainbar.js',
+ format: 'es',
+ },
+ },
+
+ {
+ input: './src/maincontrols.js',
+ output: {
+ file: './dist/maincontrols.min.js',
+ format: 'iife',
+ banner: copyright,
+ plugins: [
+ terser({
+ format: {
+ comments: preserveCopyright,
+ },
+ }),
+ ],
+ globals: {
+ il: 'il',
+ jquery: '$',
+ },
+ },
+ external: ['il', 'jquery'],
+ },
+
+];
diff --git a/src/UI/templates/js/MainControls/slate.js b/src/UI/templates/js/MainControls/slate.js
deleted file mode 100644
index e404a77f402d..000000000000
--- a/src/UI/templates/js/MainControls/slate.js
+++ /dev/null
@@ -1,91 +0,0 @@
-il = il || {};
-il.UI = il.UI || {};
-il.UI.maincontrols = il.UI.maincontrols || {};
-
-(function($, maincontrols) {
- maincontrols.slate = (function($) {
- var _cls_engaged = 'engaged'
- ,_cls_disengaged = 'disengaged'
- ,_cls_single_slate = 'il-maincontrols-slate'
- ;
-
- var onSignal = function(kind_of_signal, event, signalData, id) {
- var slate = $('#' + id),
- triggerer = signalData.triggerer,
- is_in_metabar_more = triggerer.parents('.il-metabar-more-slate').length > 0;
-
- switch (kind_of_signal) {
- case 'toggle':
- onToggleSignal(slate, triggerer, is_in_metabar_more);
- break;
- case 'engage':
- engage(slate);
- break;
- case 'replace':
- replaceFromSignal(id, signalData);
- break;
- };
- };
-
- var onToggleSignal = function(slate, triggerer, is_in_metabar_more) {
- //special case for metabar-more
- if(triggerer.attr('id') === il.UI.maincontrols.metabar._getMoreButton().attr('id')) {
- if(il.UI.maincontrols.metabar.getEngagedSlates().length > 0){
- il.UI.maincontrols.metabar._disengageAllSlates();
- } else {
- toggle(slate);
- }
- return;
- }
-
- toggle(slate);
- if(is_in_metabar_more) {
- return;
- }
- if(_isEngaged(slate)) {
- triggerer.addClass(_cls_engaged);
- triggerer.removeClass(_cls_disengaged);
- slate.trigger('in_view');
- } else {
- triggerer.removeClass(_cls_engaged);
- triggerer.addClass(_cls_disengaged);
- }
- };
-
- var toggle = function(slate) {
- _isEngaged(slate) ? disengage(slate) : engage(slate);
- };
-
- var _isEngaged = function(slate) {
- return slate.hasClass(_cls_engaged);
- };
-
- var engage = function(slate) {
- slate.removeClass(_cls_disengaged);
- slate.addClass(_cls_engaged);
- slate.attr("aria-expanded", "true");
- slate.attr("aria-hidden", "false");
- };
-
- var disengage = function(slate) {
- slate.removeClass(_cls_engaged);
- slate.addClass(_cls_disengaged);
- slate.attr("aria-expanded", "false");
- slate.attr("aria-hidden", "true");
- };
-
- var replaceFromSignal = function (id, signalData) {
- var url = signalData.options.url;
- il.UI.core.replaceContent(id, url, "content");
- };
-
- return {
- onSignal: onSignal,
- engage: engage,
- disengage: disengage,
- _cls_single_slate: _cls_single_slate,
- _cls_engaged: _cls_engaged
- }
-
- })($);
-})($, il.UI.maincontrols);
diff --git a/src/UI/templates/js/MainControls/src/maincontrols.js b/src/UI/templates/js/MainControls/src/maincontrols.js
new file mode 100644
index 000000000000..bc22aac0ff43
--- /dev/null
+++ b/src/UI/templates/js/MainControls/src/maincontrols.js
@@ -0,0 +1,37 @@
+/**
+ * This file is part of ILIAS, a powerful learning management system
+ * published by ILIAS open source e-Learning e.V.
+ *
+ * ILIAS is licensed with the GPL-3.0,
+ * see https://www.gnu.org/licenses/gpl-3.0.en.html
+ * You should have received a copy of said license along with the
+ * source code, too.
+ *
+ * If this is not the case or you just want to try ILIAS, you'll find
+ * us at:
+ * https://www.ilias.de
+ * https://github.com/ILIAS-eLearning
+ */
+
+import il from 'il';
+import $ from 'jquery';
+import MetabarFactory from './metabar.factory';
+import Slate from './slate.class';
+import replaceContent from '../../Core/src/core.replaceContent';
+import { counterFactory } from '../../Counter/src/counter.main';
+
+il.UI = il.UI || {};
+il.UI.maincontrols = il.UI.maincontrols || {};
+
+il.UI.maincontrols.metabar = new MetabarFactory(
+ $,
+ il.UI.page.isSmallScreen,
+ counterFactory($),
+ () => il.UI.maincontrols.mainbar.disengageAll(),
+ (slate) => il.UI.maincontrols.slate.disengage(slate),
+);
+il.UI.maincontrols.slate = new Slate(
+ $,
+ replaceContent($),
+ il.UI.maincontrols.metabar,
+);
diff --git a/src/UI/templates/js/MainControls/src/metabar.class.js b/src/UI/templates/js/MainControls/src/metabar.class.js
new file mode 100644
index 000000000000..81c723c4d81a
--- /dev/null
+++ b/src/UI/templates/js/MainControls/src/metabar.class.js
@@ -0,0 +1,349 @@
+/**
+ * This file is part of ILIAS, a powerful learning management system
+ * published by ILIAS open source e-Learning e.V.
+ *
+ * ILIAS is licensed with the GPL-3.0,
+ * see https://www.gnu.org/licenses/gpl-3.0.en.html
+ * You should have received a copy of said license along with the
+ * source code, too.
+ *
+ * If this is not the case or you just want to try ILIAS, you'll find
+ * us at:
+ * https://www.ilias.de
+ * https://github.com/ILIAS-eLearning
+ *
+ ******************************************************************** */
+
+/**
+ * @type {string}
+ */
+const classForBtnEngaged = 'engaged';
+
+/**
+ * @type {string}
+ */
+const classForEntries = 'il-maincontrols-metabar';
+
+/**
+ * @type {string}
+ */
+const classForSlates = 'il-metabar-slates';
+
+/**
+ * @type {string}
+ */
+const classForMoreBtn = 'il-metabar-more-button';
+
+/**
+ * @type {string}
+ */
+const classForMoreSlate = 'il-metabar-more-slate';
+
+/**
+ * @type {string}
+ */
+const classForSingleSlate = 'il-maincontrols-slate';
+
+/**
+ * @type {string}
+ */
+const classForSlateEngaged = 'engaged';
+
+/**
+ * @param {jQueryDomObject} btn
+ * @return {void}
+ */
+function engageButton(btn) {
+ btn.addClass(classForBtnEngaged);
+ btn.attr('aria-expanded', true);
+}
+
+/**
+ * @param {jQueryDomObject} btn
+ * @return {void}
+ */
+function disengageButton(btn) {
+ btn.removeClass(classForBtnEngaged);
+ btn.attr('aria-expanded', false);
+}
+
+/**
+ * @param {jQueryDomObject} btn
+ * @return {void}
+ */
+function isEngaged(btn) {
+ return btn.hasClass(classForBtnEngaged);
+}
+
+export default class Metabar {
+ /**
+ * @type {jQuery}
+ */
+ #jquery;
+
+ /**
+ * @type {string}
+ */
+ #id;
+
+ /**
+ * @type {bool}
+ */
+ #propagationStopped;
+
+ /**
+ * @type {function}
+ */
+ #pageIsSmallScreen;
+
+ /**
+ * @type {counterFactory}
+ */
+ #counterFactory;
+
+ /**
+ * @type {function}
+ */
+ #disengageMainbar;
+
+ /**
+ * @type {function}
+ */
+ #disengageSlate;
+
+ /**
+ * @param {jQuery} jquery
+ * @param {string} componentId
+ * @param {function} pageIsSmallScreen
+ * @param {counterFactory} counterFactory
+ * @param {function} disengageMainbar
+ * @param {function} disengageSlate
+ */
+ constructor(
+ jquery,
+ componentId,
+ pageIsSmallScreen,
+ counterFactory,
+ disengageMainbar,
+ disengageSlate,
+ ) {
+ this.#jquery = jquery;
+ this.#id = componentId;
+ this.#pageIsSmallScreen = pageIsSmallScreen;
+ this.#counterFactory = counterFactory;
+ this.#disengageMainbar = disengageMainbar;
+ this.#disengageSlate = disengageSlate;
+ }
+
+ /**
+ * @param {string} entrySignal
+ * @param {string} closeSlatesSignal
+ */
+ registerSignals(
+ entrySignal,
+ closeSlatesSignal,
+ ) {
+ this.#jquery(document).on(entrySignal, (event, signalData) => {
+ this.#onClickEntry(event, signalData);
+ if (this.#pageIsSmallScreen()) {
+ this.#disengageMainbar();
+ }
+ return false;
+ });
+ this.#jquery(document).on(closeSlatesSignal, () => {
+ this.onClickDisengageAll();
+ return false;
+ });
+
+ // close metabar when user clicks anywhere
+ this.#jquery(`.${classForEntries}`).on('click', () => {
+ this.#propagationStopped = true;
+ });
+ this.#jquery('body').on('click', () => {
+ if (this.#propagationStopped) {
+ this.#propagationStopped = false;
+ } else {
+ this.onClickDisengageAll();
+ }
+ });
+
+ // close metabar slate when focus moves out
+ this.#jquery(`.${classForSlates} > .${classForSingleSlate}`).on('focusout', (event) => {
+ if (!this.#pageIsSmallScreen()) {
+ const nextFocusTarget = event.relatedTarget;
+ const currentSlate = event.currentTarget;
+ if (!this.#jquery.contains(currentSlate, nextFocusTarget)) {
+ this.onClickDisengageAll();
+ }
+ }
+ });
+ }
+
+ /**
+ * @param {MouseEvent} event
+ * @param {array} signalData
+ */
+ #onClickEntry(event, signalData) {
+ const btn = signalData.triggerer;
+ if (isEngaged(btn)) {
+ disengageButton(btn);
+ } else {
+ this.disengageAllSlates();
+ this.disengageAllButtons();
+ if (btn.parents(`.${classForMoreSlate}`).length === 0) {
+ engageButton(btn);
+ }
+ }
+ }
+
+ /**
+ * @return {void}
+ */
+ onClickDisengageAll() {
+ this.disengageAllButtons();
+ this.disengageAllSlates();
+ }
+
+ /**
+ * @return {void}
+ */
+ disengageAllButtons() {
+ this.#jquery(`#${this.#id}.${classForEntries}`)
+ .children('li').children(`.btn.${classForBtnEngaged}`)
+ .each(
+ (i, btn) => {
+ disengageButton(this.#jquery(btn));
+ },
+ );
+ }
+
+ /**
+ * @return {void}
+ */
+ disengageAllSlates() {
+ this.getEngagedSlates().each(
+ (i, slate) => {
+ this.#disengageSlate(this.#jquery(slate));
+ },
+ );
+ }
+
+ /**
+ * @return {void}
+ */
+ disengageAll() {
+ this.disengageAllSlates();
+ this.disengageAllButtons();
+ }
+
+ getEngagedSlates() {
+ const search = `#${this.#id} .${classForSingleSlate}.${classForSlateEngaged}`;
+ return this.#jquery(search);
+ }
+
+ /**
+ * decide and init condensed/wide version
+ * @return {void}
+ */
+ init() {
+ this.#tagMoreButton();
+ this.#tagMoreSlate();
+
+ if (this.#pageIsSmallScreen()) {
+ this.#initCondensed();
+ } else {
+ this.#initWide();
+ }
+
+ // unfortunately, this does not work properly via a class
+ this.#jquery(`.${classForEntries}`).css('visibility', 'visible');
+ this.#jquery(`#${this.#id} .${classForSlates}`).children(`.${classForSingleSlate}`)
+ .attr('aria-hidden', true);
+ }
+
+ /**
+ * @return {void}
+ */
+ #initCondensed() {
+ this.#initMoreSlate();
+ this.#getMetabarEntries().hide();
+ this.getMoreButton().show();
+ this.#collectCounters();
+ }
+
+ /**
+ * @return {void}
+ */
+ #initWide() {
+ this.getMoreButton().hide();
+ this.#getMetabarEntries().show();
+ }
+
+ /**
+ * @return {void}
+ */
+ #tagMoreButton() {
+ if (this.getMoreButton().length === 0) {
+ const entries = this.#jquery(`#${this.#id}.${classForEntries}`).find('.btn, .il-link');
+ const more = entries.last();
+ this.#jquery(more).addClass(classForMoreBtn);
+ }
+ }
+
+ /**
+ * @return {void}
+ */
+ #tagMoreSlate() {
+ if (this.#getMoreSlate().length === 0) {
+ const slates = this.#jquery(`#${this.#id} .${classForSlates}`).children(`.${classForSingleSlate}`);
+ const more = slates.last();
+ this.#jquery(more).addClass(classForMoreSlate);
+ }
+ }
+
+ /**
+ * @return {void}
+ */
+ getMoreButton() {
+ return this.#jquery(`.${classForMoreBtn}`);
+ }
+
+ /**
+ * @return {void}
+ */
+ #getMoreSlate() {
+ return this.#jquery(`.${classForMoreSlate}`);
+ }
+
+ /**
+ * @return {void}
+ */
+ #getMetabarEntries() {
+ return this.#jquery(`#${this.#id}.${classForEntries}`)
+ .children('li').children('.btn, .il-link')
+ .not(`.${classForMoreBtn}`);
+ }
+
+ /**
+ * @return {void}
+ */
+ #initMoreSlate() {
+ const content = this.#getMoreSlate().children('.il-maincontrols-slate-content');
+ if (content.children().length === 0) {
+ this.#getMetabarEntries().clone(true, true)
+ .appendTo(content);
+ }
+ }
+
+ /**
+ * @return {void}
+ */
+ #collectCounters() {
+ const moreSlateCounter = this.#counterFactory.getCounterObjectOrNull(this.#getMoreSlate());
+ if (moreSlateCounter) {
+ this.#counterFactory.getCounterObject(this.getMoreButton())
+ .setNoveltyTo(moreSlateCounter.getNoveltyCount())
+ .setStatusTo(moreSlateCounter.getStatusCount());
+ }
+ }
+}
diff --git a/src/UI/templates/js/MainControls/src/metabar.factory.js b/src/UI/templates/js/MainControls/src/metabar.factory.js
new file mode 100644
index 000000000000..8f545329de38
--- /dev/null
+++ b/src/UI/templates/js/MainControls/src/metabar.factory.js
@@ -0,0 +1,106 @@
+/**
+ * This file is part of ILIAS, a powerful learning management system
+ * published by ILIAS open source e-Learning e.V.
+ *
+ * ILIAS is licensed with the GPL-3.0,
+ * see https://www.gnu.org/licenses/gpl-3.0.en.html
+ * You should have received a copy of said license along with the
+ * source code, too.
+ *
+ * If this is not the case or you just want to try ILIAS, you'll find
+ * us at:
+ * https://www.ilias.de
+ * https://github.com/ILIAS-eLearning
+ */
+
+import Metabar from './metabar.class';
+
+export default class MetabarFactory {
+ /**
+ * @type {jQuery}
+ */
+ #jquery;
+
+ /**
+ * @type {Array}
+ */
+ #instances = [];
+
+ /**
+ * @type {function}
+ */
+ #pageIsSmallScreen;
+
+ /**
+ * @type {counterFactory}
+ */
+ #counterFactory;
+
+ /**
+ * @type {function}
+ */
+ #disengageMainbar;
+
+ /**
+ * @type {function}
+ */
+ #disengageSlate;
+
+ /**
+ * @param {jQuery} jquery
+ * @param {function} pageIsSmallScreen
+ * @param {counterFactory} counterFactory
+ * @param {function} disengageMainbar
+ * @param {function} disengageSlate
+ */
+ constructor(
+ jquery,
+ pageIsSmallScreen,
+ counterFactory,
+ disengageMainbar,
+ disengageSlate,
+ ) {
+ this.#jquery = jquery;
+ this.#pageIsSmallScreen = pageIsSmallScreen;
+ this.#counterFactory = counterFactory;
+ this.#disengageMainbar = disengageMainbar;
+ this.#disengageSlate = disengageSlate;
+ }
+
+ /**
+ * @param {string} componentId
+ * @return {void}
+ * @throws {Error} if the bar was already initialized.
+ */
+ init(componentId) {
+ if (this.#instances[componentId] !== undefined) {
+ throw new Error(`Metabar with id '${componentId}' has already been initialized.`);
+ }
+
+ this.#instances[componentId] = new Metabar(
+ this.#jquery,
+ componentId,
+ this.#pageIsSmallScreen,
+ this.#counterFactory,
+ this.#disengageMainbar,
+ this.#disengageSlate,
+ );
+ }
+
+ /**
+ * @param {string} tableId
+ * @return {Metabar|null}
+ */
+ get(componentId) {
+ return this.#instances[componentId] ?? null;
+ }
+
+ /**
+ * @return {void}
+ */
+ disengageAll() {
+ Object.values(this.#instances).forEach(
+ (slate) => slate.disengageAll(),
+ );
+ }
+}
diff --git a/src/UI/templates/js/MainControls/src/slate.class.js b/src/UI/templates/js/MainControls/src/slate.class.js
new file mode 100644
index 000000000000..53f849501886
--- /dev/null
+++ b/src/UI/templates/js/MainControls/src/slate.class.js
@@ -0,0 +1,174 @@
+/**
+ * This file is part of ILIAS, a powerful learning management system
+ * published by ILIAS open source e-Learning e.V.
+ *
+ * ILIAS is licensed with the GPL-3.0,
+ * see https://www.gnu.org/licenses/gpl-3.0.en.html
+ * You should have received a copy of said license along with the
+ * source code, too.
+ *
+ * If this is not the case or you just want to try ILIAS, you'll find
+ * us at:
+ * https://www.ilias.de
+ * https://github.com/ILIAS-eLearning
+ *
+ ******************************************************************** */
+
+/**
+ * @type {string}
+ */
+const replacementType = 'content';
+
+/**
+ * @type {string}
+ */
+const classForEngaged = 'engaged';
+
+/**
+ * @type {string}
+ */
+const classForDisEngaged = 'disengaged';
+
+/**
+ * @param {jQueryDomObject} slate
+ * @return {bool}
+ */
+function isEngaged(slate) {
+ return slate.hasClass(classForEngaged);
+}
+
+/**
+ * @param {jQueryDomObject} slate
+ * @return {void}
+ */
+function engage(slate) {
+ slate.removeClass(classForDisEngaged);
+ slate.addClass(classForEngaged);
+ slate.attr('aria-expanded', 'true');
+ slate.attr('aria-hidden', 'false');
+}
+
+/**
+ * @param {jQueryDomObject} slate
+ * @return {void}
+ */
+function disengage(slate) {
+ slate.removeClass(classForEngaged);
+ slate.addClass(classForDisEngaged);
+ slate.attr('aria-expanded', 'false');
+ slate.attr('aria-hidden', 'true');
+}
+
+/**
+ * @param {jQueryDomObject} slate
+ * @return {void}
+ */
+function toggle(slate) {
+ if (isEngaged(slate)) {
+ disengage(slate);
+ } else {
+ engage(slate);
+ }
+}
+
+export default class Slate {
+ /**
+ * @type {jQuery}
+ */
+ #jquery;
+
+ /**
+ * @type {function}
+ */
+ #coreReplaceContent;
+
+ /**
+ * @type {MetabarFactory}
+ */
+ #metabarFactory;
+
+ /**
+ * @param {jQuery} jquery
+ * @param {function} coreReplaceContent
+ * @param {MetabarFactory} metabarFactory
+ */
+ constructor(jquery, coreReplaceContent, metabarFactory) {
+ this.#jquery = jquery;
+ this.#coreReplaceContent = coreReplaceContent;
+ this.#metabarFactory = metabarFactory;
+ }
+
+ /**
+ * @param {string} signalType
+ * @param {Event} event
+ * @param {array} signalData
+ * @param {string} id
+ * @return {void}
+ * @throws {Error} if the signalType is not known
+ */
+ onSignal(signalType, event, signalData, id) {
+ const slate = this.#jquery(`#${id}`);
+ const { triggerer } = signalData;
+ const isInMetabarMore = triggerer.parents('.il-metabar-more-slate').length > 0;
+
+ if (signalType === 'toggle') {
+ this.#onToggleSignal(slate, triggerer, isInMetabarMore);
+ } else if (signalType === 'engage') {
+ engage(slate);
+ } else if (signalType === 'replace') {
+ this.#replaceFromSignal(id, signalData);
+ } else {
+ throw new Error(`No such SignalType: ${signalType}`);
+ }
+ }
+
+ /**
+ * @param {jQueryDomObject} slate
+ * @param {jQueryDomObject} triggerer
+ * @param {bool} isInMetabarMore
+ * @return null|{void}
+ */
+ #onToggleSignal(slate, triggerer, isInMetabarMore) {
+ // special case for metabar-more
+ const metabarId = slate.closest('.il-maincontrols-metabar').attr('id');
+ const metabar = this.#metabarFactory.get(metabarId);
+
+ if (triggerer.attr('id') === metabar.getMoreButton().attr('id')) {
+ if (metabar.getEngagedSlates().length > 0) {
+ metabar.disengageAllSlates();
+ } else {
+ toggle(slate);
+ }
+ return;
+ }
+
+ toggle(slate);
+ if (isInMetabarMore) {
+ return;
+ }
+ if (isEngaged(slate)) {
+ triggerer.addClass(classForEngaged);
+ triggerer.removeClass(classForDisEngaged);
+ slate.trigger('in_view');
+ } else {
+ triggerer.removeClass(classForEngaged);
+ triggerer.addClass(classForDisEngaged);
+ }
+ }
+
+ /**
+ * @param {jQueryDomObject} slate
+ * @return {void}
+ */
+ disengage = disengage;
+
+ /**
+ * @param {string} id
+ * @param {array} signalData
+ * @return {void}
+ */
+ #replaceFromSignal(id, signalData) {
+ const { url } = signalData.options;
+ this.#coreReplaceContent(id, url, replacementType);
+ }
+}