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); + } +}