diff --git a/components/ILIAS/GlobalScreen/src/GUI/I18n/MultiLanguage/MultiLanguageGUI.php b/components/ILIAS/GlobalScreen/src/GUI/I18n/MultiLanguage/MultiLanguageGUI.php index f1c4497a844a..0a3c15219c49 100644 --- a/components/ILIAS/GlobalScreen/src/GUI/I18n/MultiLanguage/MultiLanguageGUI.php +++ b/components/ILIAS/GlobalScreen/src/GUI/I18n/MultiLanguage/MultiLanguageGUI.php @@ -28,6 +28,7 @@ use ILIAS\GlobalScreen\GUI\I18n\MultiLanguage\TranslationWorkflowForm; use ILIAS\GlobalScreen\GUI\Pons; use ILIAS\Data\URI; +use ILIAS\UI\URLBuilder; /** * @author Fabian Schmid @@ -115,7 +116,9 @@ private function index(): void } $prompt = $this->ui_factory->prompt()->standard( - $this->pons->flow()->getHereAsURI(self::CMD_LANGUAGE_SELECTION) + new URLBuilder( + $this->pons->flow()->getHereAsURI(self::CMD_LANGUAGE_SELECTION) + ) ); // Edit Button diff --git a/components/ILIAS/UI/resources/js/Table/dist/table.min.js b/components/ILIAS/UI/resources/js/Table/dist/table.min.js index 823b92627136..48eda10a24e2 100644 --- a/components/ILIAS/UI/resources/js/Table/dist/table.min.js +++ b/components/ILIAS/UI/resources/js/Table/dist/table.min.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(e,t){"use strict";class s{#e;#t;#s;#i;#a;#o;#n;#r;constructor(e,t,s,i){if(this.#s=document.getElementById(i),null===this.#s)throw new Error(`Could not find a DataTable for id '${i}'.`);if(this.#n=this.#s.getElementsByTagName("table").item(0),null===this.#n)throw new Error("There is no in the component's HTML.");this.#i=this.#s.getElementsByClassName("c-table-data__async_modal_container").item(0),this.#a=this.#s.getElementsByClassName("c-table-data__async_message").item(0),this.#o=this.#a.getElementsByClassName("c-table-data__async_messageresponse").item(0),this.#e=e,this.#t={actionId:t,rowId:s},this.#r={},this.#s.addEventListener("keydown",(e=>this.navigateCellsWithArrowKeys(e)));const a=this.#n.getElementsByClassName("c-table-data__row-selector");for(let e=0;ethis.selectionChange()))}}registerAction(e,t,s,i){this.#r[e]={async:t,urlBuilder:s,urlTokens:i}}selectionChange(){this.#l(!this.#c())}#c(){const{urlBuilder:e}=this.#r[Object.keys(this.#r).at(0)],{urlTokens:t}=this.#r[Object.keys(this.#r).at(0)],s=t.values().next().value,i=this.collectSelectedRowIds();i.push(i[0]),e.writeParameter(s,i);try{e.getUrl().toString()}catch(e){return!1}return!0}#h(){const e=this.#s.querySelector("dialog.c-table-data__multiaction-warning");il.UI.modal.showModal(e,{},{id:e.id})}#l(e){this.#n.getElementsByClassName("c-table-data__row-selector").forEach((t=>{t.disabled=!0===e&&!t.checked}))}selectAll(e){const t=this.#n.getElementsByClassName("c-table-data__row-selector"),s=this.#n.getElementsByClassName("c-table-data__selection_all").item(0),i=this.#n.getElementsByClassName("c-table-data__selection_none").item(0);for(let s=0;s{e.checked&&t.push(e.value)})),t}doMultiAction(e){this.doAction(e,this.collectSelectedRowIds())}doSingleAction(e){const t=e.options[this.#t.rowId];this.doAction(e,[t])}doActionForAll(e){const t=e.parentNode.parentNode.parentNode,s=t.getElementsByClassName("close").item(0),i=t.getElementsByClassName("modal-body")[0].getElementsByTagName("select")[0].value;if(i in this.#r){const e=this.#t.actionId,t={options:{}};t.options[e]=i,s.click(),this.doAction(t,["ALL_OBJECTS"])}}doAction(e,t){const s=e.options[this.#t.actionId],i=this.#r[s],a=i.urlTokens.values().next().value;i.urlBuilder.writeParameter(a,t);const o=decodeURI(i.urlBuilder.getUrl().toString());i.async?this.asyncAction(o):window.location.href=o}asyncAction(e){this.#e.ajax({url:e,dataType:"html"}).done((e=>{if("SCRIPT"===this.#e(e).first().prop("tagName"))this.#e.globalEval(this.#e(e).first().text());else{let t;this.#e(e).first().hasClass("c-modal")?(this.#i.innerHTML=e,t=this.#i.firstChild):(this.#o.innerHTML=e,t=this.#a);this.#e(`
${e}
`).find("[data-replace-marker='script']").each(((e,t)=>this.#e.globalEval(t.innerHTML))),il.UI.modal.showModal(t,{},{id:t.id})}}))}navigateCellsWithArrowKeys(e){if(37!==e.which&&38!==e.which&&39!==e.which&&40!==e.which)return;const t=e.target.closest("td, th"),s=t.closest("tr");let{cellIndex:i}=t,{rowIndex:a}=s;switch(e.which){case 37:i-=1;break;case 39:i+=1;break;case 38:a-=1;break;case 40:a+=1}a<0||i<0||a>=this.#n.rows.length||i>=s.cells.length||this.focusCell(t,a,i)}focusCell(e,t,s){const i=this.#n.rows[t].cells[s];i.focus(),e.setAttribute("tabindex",-1),i.setAttribute("tabindex",0)}}class i{#s;constructor(e){if(this.#s=document.getElementById(e),null===this.#s)throw new Error(`Could not find a PresentationTable for id '${e}'.`)}expandRow(e){const t=this.#s.querySelector(`#${e}`);t.classList.remove("collapsed"),t.classList.add("expanded")}collapseRow(e){const t=this.#s.querySelector(`#${e}`);t.classList.remove("expanded"),t.classList.add("collapsed")}toggleRow(e){this.#s.querySelector(`#${e}`).classList.contains("expanded")?this.collapseRow(e):this.expandRow(e)}expandAll(e){const t=this.#s.querySelectorAll(".il-table-presentation-row");e.options.expand?t.forEach((e=>this.expandRow(e.id))):t.forEach((e=>this.collapseRow(e.id)))}}class a{#s;#n;#d;#g;#w;#m;constructor(e){if(this.#s=document.getElementById(e),null===this.#s)throw new Error(`Could not find a OrderingTable for id '${e}'.`);if(this.#n=this.#s.getElementsByTagName("table").item(0),null===this.#n)throw new Error("There is no
in the component's HTML.");this.#p(),this.#d.forEach((e=>this.#u(e)))}#p(){this.#d=Array.from(this.#n.rows),this.#d.shift(),this.#d.pop()}#u(e){e.addEventListener("dragstart",(e=>this.dragstart(e))),e.addEventListener("dragover",(e=>this.dragover(e))),e.addEventListener("dragend",(e=>this.dragend(e))),e.addEventListener("touchstart",(e=>this.touchstart(e))),e.addEventListener("touchmove",(e=>this.touchmove(e))),e.addEventListener("touchend",(e=>this.touchend(e))),e.addEventListener("touchcancel",(e=>this.touchend(e)))}dragstart(e){this.#s.classList.add("dragInProgress"),this.#g=e.target.closest("tr"),e.dataTransfer.clearData(),e.dataTransfer.setData("text/html",this.#g.outerHTML),e.dataTransfer.setData("text/plain",this.#g.textContent.replace(/[\n\r]+|[\s]{2,}/g," ").trim()),this.#w=this.#g.cloneNode(!0),this.#w.classList.add("c-table-data__row--drag-image"),this.#w.style.top="-9999px",this.#s.appendChild(this.#w),e.dataTransfer.setDragImage(this.#w,0,0),this.#m=this.#g.cloneNode(!0),this.#m.addEventListener("dragover",(e=>this.dragover(e))),this.#m.classList.add("c-table-data__row--placeholder"),Array.from(this.#m.getElementsByTagName("td")).forEach((e=>{e.innerHTML=""})),this.#g.classList.add("c-table-data__row--drag-origin")}dragover(e){if(!this.#b())return;e.preventDefault(),e.dataTransfer.effectAllowed="copyMove";const t=e.target.closest("tr");t&&t!==this.#m&&(this.#d.indexOf(t)>this.#d.indexOf(this.#g)?t.after(this.#m):t.before(this.#m))}dragend(e){e.preventDefault(),this.#w&&this.#s.contains(this.#w)&&(this.#s.removeChild(this.#w),this.#w=null),this.#m.replaceWith(this.#g),this.#g.classList.remove("c-table-data__row--drag-origin"),this.#g.classList.add("c-table-data__row--drag-settle"),this.#g.addEventListener("animationend",(()=>this.#y()),{once:!0}),this.#s.classList.remove("dragInProgress"),this.#p(),this.#R()}#y(){this.#g.classList.remove("c-table-data__row--drag-settle")}#b(){return this.#d.includes(this.#g)}touchstart(e){this.#g=e.target.closest("tr"),this.#m=this.#g.cloneNode(!0),this.#m.classList.add("c-table-data__row--placeholder"),Array.from(this.#m.getElementsByTagName("td")).forEach((e=>{e.innerHTML=""})),this.#w=this.#g.cloneNode(!0),this.#w.classList.add("c-table-data__row--touch-drag-image"),this.#s.appendChild(this.#w),this.#g.classList.add("c-table-data__row--drag-origin")}touchmove(e){e.preventDefault();const t=e.touches[0];this.#w.style.left=`${t.clientX+-50}px`,this.#w.style.top=`${t.clientY}px`;const s=document.elementFromPoint(t.clientX,t.clientY)?.closest("tr");s&&this.#d.includes(s)&&(this.#d.indexOf(s)>this.#d.indexOf(this.#g)?s.after(this.#m):s.before(this.#m)),t.clientY<100?this.#f(-8):t.clientY>window.innerHeight-100?this.#f(8):this.#v()}touchend(){this.#s.removeChild(this.#w),this.#m.replaceWith(this.#g),this.#g.classList.remove("c-table-data__row--drag-origin"),this.#p(),this.#R()}#R(){let e=10;this.#n.querySelectorAll('input[type="number"]').forEach((t=>{t.value=e,e+=10}))}#f(e){this.scrollInterval||(this.scrollInterval=setInterval((()=>{window.scrollBy(0,e)}),16))}#v(){this.scrollInterval&&(clearInterval(this.scrollInterval),this.scrollInterval=null)}}e.UI=e.UI||{},e.UI.table=e.UI.table||{},e.UI.table.data=new class{#e;#E=[];constructor(e){this.#e=e}init(e,t,i){if(void 0!==this.#E[e])throw new Error(`DataTable with id '${e}' has already been initialized.`);this.#E[e]=new s(this.#e,t,i,e)}get(e){return this.#E[e]??null}}(t),e.UI.table.presentation=new class{#E=[];init(e){if(void 0!==this.#E[e])throw new Error(`PresentationTable with input-id '${e}' has already been initialized.`);this.#E[e]=new i(e)}get(e){return this.#E[e]??null}},e.UI.table.ordering=new class{#E=[];init(e){if(void 0!==this.#E[e])throw new Error(`OrderingTable with id '${e}' has already been initialized.`);this.#E[e]=new a(e)}get(e){return this.#E[e]??null}}}(il,$); +!function(e,t){"use strict";class s{#e;#t;#s;#i;#a;#o;#n;#r;#l;constructor(e,t,s,i,a){if(this.#i=document.getElementById(a),null===this.#i)throw new Error(`Could not find a DataTable for id '${a}'.`);if(this.#r=this.#i.getElementsByTagName("table").item(0),null===this.#r)throw new Error("There is no
in the component's HTML.");this.#a=this.#i.getElementsByClassName("c-table-data__async_modal_container").item(0),this.#o=this.#i.getElementsByClassName("c-table-data__async_message").item(0),this.#n=this.#o.getElementsByClassName("c-table-data__async_messageresponse").item(0),this.#e=e,this.#t=t,this.#s={actionId:s,rowId:i},this.#l={},this.#i.addEventListener("keydown",(e=>this.navigateCellsWithArrowKeys(e)));const o=this.#r.getElementsByClassName("c-table-data__row-selector");for(let e=0;ethis.selectionChange()))}}registerAction(e,t,s,i,a){this.#l[e]={async:t,signalId:s,urlBuilder:i,urlTokens:a}}selectionChange(){this.#c(!this.#h())}#h(){const{urlBuilder:e}=this.#l[Object.keys(this.#l).at(0)],{urlTokens:t}=this.#l[Object.keys(this.#l).at(0)],s=t.values().next().value,i=this.collectSelectedRowIds();i.push(i[0]),e.writeParameter(s,i);try{e.getUrl().toString()}catch(e){return!1}return!0}#d(){const e=this.#i.querySelector("dialog.c-table-data__multiaction-warning");il.UI.modal.showModal(e,{},{id:e.id})}#c(e){this.#r.getElementsByClassName("c-table-data__row-selector").forEach((t=>{t.disabled=!0===e&&!t.checked}))}selectAll(e){const t=this.#r.getElementsByClassName("c-table-data__row-selector"),s=this.#r.getElementsByClassName("c-table-data__selection_all").item(0),i=this.#r.getElementsByClassName("c-table-data__selection_none").item(0);for(let s=0;s{e.checked&&t.push(e.value)})),t}doMultiAction(e){this.doAction(e,this.collectSelectedRowIds())}doSingleAction(e){const t=e.options[this.#s.rowId];this.doAction(e,[t])}doActionForAll(e){const t=e.parentNode.parentNode.parentNode,s=t.getElementsByClassName("close").item(0),i=t.getElementsByClassName("modal-body")[0].getElementsByTagName("select")[0].value;if(i in this.#l){const e=this.#s.actionId,t={options:{}};t.options[e]=i,s.click(),this.doAction(t,["ALL_OBJECTS"])}}doAction(e,t){const s=e.options[this.#s.actionId],i=this.#l[s],a=i.urlTokens.values().next().value;i.urlBuilder.writeParameter(a,t);const o=decodeURI(i.urlBuilder.getUrl().toString());""===i.signalId?window.location.href=o:i.async?this.asyncAction(o):(e.options.url=o,this.#t.dispatch(this.#i,i.signalId,e))}asyncAction(e){this.#e.ajax({url:e,dataType:"html"}).done((e=>{if("SCRIPT"===this.#e(e).first().prop("tagName"))this.#e.globalEval(this.#e(e).first().text());else{let t;this.#e(e).first().hasClass("c-modal")?(this.#a.innerHTML=e,t=this.#a.firstChild):(this.#n.innerHTML=e,t=this.#o);this.#e(`
${e}
`).find("[data-replace-marker='script']").each(((e,t)=>this.#e.globalEval(t.innerHTML))),il.UI.modal.showModal(t,{},{id:t.id})}}))}navigateCellsWithArrowKeys(e){if(37!==e.which&&38!==e.which&&39!==e.which&&40!==e.which)return;const t=e.target.closest("td, th"),s=t.closest("tr");let{cellIndex:i}=t,{rowIndex:a}=s;switch(e.which){case 37:i-=1;break;case 39:i+=1;break;case 38:a-=1;break;case 40:a+=1}a<0||i<0||a>=this.#r.rows.length||i>=s.cells.length||this.focusCell(t,a,i)}focusCell(e,t,s){const i=this.#r.rows[t].cells[s];i.focus(),e.setAttribute("tabindex",-1),i.setAttribute("tabindex",0)}}class i{#i;constructor(e){if(this.#i=document.getElementById(e),null===this.#i)throw new Error(`Could not find a PresentationTable for id '${e}'.`)}expandRow(e){const t=this.#i.querySelector(`#${e}`);t.classList.remove("collapsed"),t.classList.add("expanded")}collapseRow(e){const t=this.#i.querySelector(`#${e}`);t.classList.remove("expanded"),t.classList.add("collapsed")}toggleRow(e){this.#i.querySelector(`#${e}`).classList.contains("expanded")?this.collapseRow(e):this.expandRow(e)}expandAll(e){const t=this.#i.querySelectorAll(".il-table-presentation-row");e.options.expand?t.forEach((e=>this.expandRow(e.id))):t.forEach((e=>this.collapseRow(e.id)))}}class a{#i;#r;#g;#w;#m;#p;constructor(e){if(this.#i=document.getElementById(e),null===this.#i)throw new Error(`Could not find a OrderingTable for id '${e}'.`);if(this.#r=this.#i.getElementsByTagName("table").item(0),null===this.#r)throw new Error("There is no
in the component's HTML.");this.#u(),this.#g.forEach((e=>this.#y(e)))}#u(){this.#g=Array.from(this.#r.rows),this.#g.shift(),this.#g.pop()}#y(e){e.addEventListener("dragstart",(e=>this.dragstart(e))),e.addEventListener("dragover",(e=>this.dragover(e))),e.addEventListener("dragend",(e=>this.dragend(e))),e.addEventListener("touchstart",(e=>this.touchstart(e))),e.addEventListener("touchmove",(e=>this.touchmove(e))),e.addEventListener("touchend",(e=>this.touchend(e))),e.addEventListener("touchcancel",(e=>this.touchend(e)))}dragstart(e){this.#i.classList.add("dragInProgress"),this.#w=e.target.closest("tr"),e.dataTransfer.clearData(),e.dataTransfer.setData("text/html",this.#w.outerHTML),e.dataTransfer.setData("text/plain",this.#w.textContent.replace(/[\n\r]+|[\s]{2,}/g," ").trim()),this.#m=this.#w.cloneNode(!0),this.#m.classList.add("c-table-data__row--drag-image"),this.#m.style.top="-9999px",this.#i.appendChild(this.#m),e.dataTransfer.setDragImage(this.#m,0,0),this.#p=this.#w.cloneNode(!0),this.#p.addEventListener("dragover",(e=>this.dragover(e))),this.#p.classList.add("c-table-data__row--placeholder"),Array.from(this.#p.getElementsByTagName("td")).forEach((e=>{e.innerHTML=""})),this.#w.classList.add("c-table-data__row--drag-origin")}dragover(e){if(!this.#b())return;e.preventDefault(),e.dataTransfer.effectAllowed="copyMove";const t=e.target.closest("tr");t&&t!==this.#p&&(this.#g.indexOf(t)>this.#g.indexOf(this.#w)?t.after(this.#p):t.before(this.#p))}dragend(e){e.preventDefault(),this.#m&&this.#i.contains(this.#m)&&(this.#i.removeChild(this.#m),this.#m=null),this.#p.replaceWith(this.#w),this.#w.classList.remove("c-table-data__row--drag-origin"),this.#w.classList.add("c-table-data__row--drag-settle"),this.#w.addEventListener("animationend",(()=>this.#R()),{once:!0}),this.#i.classList.remove("dragInProgress"),this.#u(),this.#f()}#R(){this.#w.classList.remove("c-table-data__row--drag-settle")}#b(){return this.#g.includes(this.#w)}touchstart(e){this.#w=e.target.closest("tr"),this.#p=this.#w.cloneNode(!0),this.#p.classList.add("c-table-data__row--placeholder"),Array.from(this.#p.getElementsByTagName("td")).forEach((e=>{e.innerHTML=""})),this.#m=this.#w.cloneNode(!0),this.#m.classList.add("c-table-data__row--touch-drag-image"),this.#i.appendChild(this.#m),this.#w.classList.add("c-table-data__row--drag-origin")}touchmove(e){e.preventDefault();const t=e.touches[0];this.#m.style.left=`${t.clientX+-50}px`,this.#m.style.top=`${t.clientY}px`;const s=document.elementFromPoint(t.clientX,t.clientY)?.closest("tr");s&&this.#g.includes(s)&&(this.#g.indexOf(s)>this.#g.indexOf(this.#w)?s.after(this.#p):s.before(this.#p)),t.clientY<100?this.#v(-8):t.clientY>window.innerHeight-100?this.#v(8):this.#I()}touchend(){this.#i.removeChild(this.#m),this.#p.replaceWith(this.#w),this.#w.classList.remove("c-table-data__row--drag-origin"),this.#u(),this.#f()}#f(){let e=10;this.#r.querySelectorAll('input[type="number"]').forEach((t=>{t.value=e,e+=10}))}#v(e){this.scrollInterval||(this.scrollInterval=setInterval((()=>{window.scrollBy(0,e)}),16))}#I(){this.scrollInterval&&(clearInterval(this.scrollInterval),this.scrollInterval=null)}}const o=new class{#e;constructor(e){this.#e=e}dispatch(e,t,s){this.#e(e).trigger(t,s)}register(e,t,s){this.#e(e).on(t,s)}}(t);e.UI=e.UI||{},e.UI.table=e.UI.table||{},e.UI.table.data=new class{#e;#t;#E=[];constructor(e,t){this.#e=e,this.#t=t}init(e,t,i){if(void 0!==this.#E[e])throw new Error(`DataTable with id '${e}' has already been initialized.`);this.#E[e]=new s(this.#e,this.#t,t,i,e)}get(e){return this.#E[e]??null}}(t,o),e.UI.table.presentation=new class{#E=[];init(e){if(void 0!==this.#E[e])throw new Error(`PresentationTable with input-id '${e}' has already been initialized.`);this.#E[e]=new i(e)}get(e){return this.#E[e]??null}},e.UI.table.ordering=new class{#E=[];init(e){if(void 0!==this.#E[e])throw new Error(`OrderingTable with id '${e}' has already been initialized.`);this.#E[e]=new a(e)}get(e){return this.#E[e]??null}}}(il,$); diff --git a/components/ILIAS/UI/resources/js/Table/src/datatable.class.js b/components/ILIAS/UI/resources/js/Table/src/datatable.class.js index 99c460228388..fb7ab8e33495 100755 --- a/components/ILIAS/UI/resources/js/Table/src/datatable.class.js +++ b/components/ILIAS/UI/resources/js/Table/src/datatable.class.js @@ -40,7 +40,12 @@ export default class DataTable { #jquery; /** - * @type {actionId: string, rowId: string} + * @type {JQueryEventDispatcher} + */ + #eventDispatcher; + + /** + * @type {actionId: string, rowId: string, async: string} */ #signalConstants; @@ -70,18 +75,19 @@ export default class DataTable { #table; /** - * @type {array} + * @type {array} */ #actionsRegistry; /** * @param {jQuery} jquery + * @param {JQueryEventDispatcher} eventDispatcher * @param {string} optActionId * @param {string} optRowId * @param {string} tableId * @throws {Error} if DOM element is missing */ - constructor(jquery, optActionId, optRowId, componentId) { + constructor(jquery, eventDispatcher, optActionId, optRowId, componentId) { this.#component = document.getElementById(componentId); if (this.#component === null) { throw new Error(`Could not find a DataTable for id '${componentId}'.`); @@ -95,6 +101,7 @@ export default class DataTable { this.#responseContent = this.#responseContainer.getElementsByClassName('c-table-data__async_messageresponse').item(0); this.#jquery = jquery; + this.#eventDispatcher = eventDispatcher; this.#signalConstants = { actionId: optActionId, rowId: optRowId, @@ -113,13 +120,15 @@ export default class DataTable { /** * @param {string} actionId * @param {bool} async + * @param {string} signalId * @param {URLBuilder} urlBuilder * @param {Map} urlTokens * @return {void} */ - registerAction(actionId, async, urlBuilder, urlTokens) { + registerAction(actionId, async, signalId, urlBuilder, urlTokens) { this.#actionsRegistry[actionId] = { async, + signalId, urlBuilder, urlTokens, }; @@ -251,7 +260,6 @@ export default class DataTable { const k = this.#signalConstants.actionId; const signalData = { options: {} }; signalData.options[k] = selectedAction; - modalClose.click(); this.doAction(signalData, ['ALL_OBJECTS']); } @@ -269,10 +277,13 @@ export default class DataTable { action.urlBuilder.writeParameter(token, rowIds); const target = decodeURI(action.urlBuilder.getUrl().toString()); - if (!action.async) { + if (action.signalId === '') { window.location.href = target; - } else { + } else if (action.async) { this.asyncAction(target); + } else { + signalData.options.url = target; + this.#eventDispatcher.dispatch(this.#component, action.signalId, signalData); } } diff --git a/components/ILIAS/UI/resources/js/Table/src/datatable.factory.js b/components/ILIAS/UI/resources/js/Table/src/datatable.factory.js index 09ae03437346..36169a3000e4 100755 --- a/components/ILIAS/UI/resources/js/Table/src/datatable.factory.js +++ b/components/ILIAS/UI/resources/js/Table/src/datatable.factory.js @@ -21,6 +21,11 @@ export default class DataTableFactory { */ #jquery; + /** + * @type {JQueryEventDispatcher} + */ + #eventDispatcher; + /** * @type {Array} */ @@ -28,9 +33,11 @@ export default class DataTableFactory { /** * @param {jQuery} jquery + * @param {JQueryEventDispatcher} eventDispatcher */ - constructor(jquery) { + constructor(jquery, eventDispatcher) { this.#jquery = jquery; + this.#eventDispatcher = eventDispatcher; } /** @@ -47,6 +54,7 @@ export default class DataTableFactory { this.#instances[tableId] = new DataTable( this.#jquery, + this.#eventDispatcher, optActionId, optRowId, tableId, diff --git a/components/ILIAS/UI/resources/js/Table/src/table.js b/components/ILIAS/UI/resources/js/Table/src/table.js index 0f7b92f49aa7..f95b054857d5 100755 --- a/components/ILIAS/UI/resources/js/Table/src/table.js +++ b/components/ILIAS/UI/resources/js/Table/src/table.js @@ -15,12 +15,15 @@ import il from 'il'; import $ from 'jquery'; -import DataTableFactory from './datatable.factory'; -import PresentationTableFactory from './presentationtable.factory'; -import OrderingTableFactory from './orderingtable.factory'; +import DataTableFactory from './datatable.factory.js'; +import PresentationTableFactory from './presentationtable.factory.js'; +import OrderingTableFactory from './orderingtable.factory.js'; +import JQueryEventDispatcher from '../../Core/src/jqueryeventdispatcher.js'; + +const eventDispatcher = new JQueryEventDispatcher($); il.UI = il.UI || {}; il.UI.table = il.UI.table || {}; -il.UI.table.data = new DataTableFactory($); +il.UI.table.data = new DataTableFactory($, eventDispatcher); il.UI.table.presentation = new PresentationTableFactory(); il.UI.table.ordering = new OrderingTableFactory(); diff --git a/components/ILIAS/UI/src/Component/Prompt/Factory.php b/components/ILIAS/UI/src/Component/Prompt/Factory.php index 51433ca275dd..20445b055937 100644 --- a/components/ILIAS/UI/src/Component/Prompt/Factory.php +++ b/components/ILIAS/UI/src/Component/Prompt/Factory.php @@ -21,7 +21,7 @@ namespace ILIAS\UI\Component\Prompt; use ILIAS\UI\Component; -use ILIAS\Data\URI; +use ILIAS\UI\URLBuilder; interface Factory { @@ -47,11 +47,11 @@ interface Factory * usage: * 1: > * The server MUST answer with an Prompt State Component - * to a request to the url provided to the Prompt. + * to a request to the url provided to the Prompt via the URLBuilder. * --- * @return \ILIAS\UI\Component\Prompt\Prompt */ - public function standard(URI $async_url): Prompt; + public function standard(URLBuilder $url_builder): Prompt; /** * --- diff --git a/components/ILIAS/UI/src/Component/Prompt/Prompt.php b/components/ILIAS/UI/src/Component/Prompt/Prompt.php index a4bbc1e3d494..5e56efeaa24f 100644 --- a/components/ILIAS/UI/src/Component/Prompt/Prompt.php +++ b/components/ILIAS/UI/src/Component/Prompt/Prompt.php @@ -24,6 +24,7 @@ use ILIAS\UI\Component\JavaScriptBindable; use ILIAS\UI\Component\Signal; use ILIAS\Data\URI; +use ILIAS\UI\URLBuilderToken; interface Prompt extends Component, JavaScriptBindable { @@ -36,4 +37,10 @@ public function getShowSignal(?URI $uri = null): Signal; * Get the signal to close this Prompt. */ public function getCloseSignal(): Signal; + + /** + * Consumers my modify the target url by altering the parameter + * identified by the respective token. + */ + public function withParameter(URLBuilderToken $token, string|array $value): self; } diff --git a/components/ILIAS/UI/src/Component/Table/Action/Action.php b/components/ILIAS/UI/src/Component/Table/Action/Action.php index 76bac41562d9..2dbe76ba0417 100755 --- a/components/ILIAS/UI/src/Component/Table/Action/Action.php +++ b/components/ILIAS/UI/src/Component/Table/Action/Action.php @@ -25,5 +25,8 @@ interface Action extends \ILIAS\UI\Component\Component { + /** + * @deprecated with ILIAS 11. Please use Prompt. + */ public function withAsync(bool $async = true): self; } diff --git a/components/ILIAS/UI/src/Component/Table/Action/Factory.php b/components/ILIAS/UI/src/Component/Table/Action/Factory.php index 4a85b530db8d..1acedaa7b5cf 100755 --- a/components/ILIAS/UI/src/Component/Table/Action/Factory.php +++ b/components/ILIAS/UI/src/Component/Table/Action/Factory.php @@ -22,6 +22,7 @@ use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; +use ILIAS\UI\Component\Prompt\Prompt; interface Factory { @@ -37,7 +38,7 @@ interface Factory */ public function standard( string $label, - URLBuilder $url_builder, + Prompt | URLBuilder $target, URLBuilderToken $row_id_parameter ): Standard; @@ -53,7 +54,7 @@ public function standard( */ public function single( string $label, - URLBuilder $url_builder, + Prompt | URLBuilder $target, URLBuilderToken $row_id_parameter ): Single; @@ -69,7 +70,7 @@ public function single( */ public function multi( string $label, - URLBuilder $url_builder, + Prompt | URLBuilder $target, URLBuilderToken $row_id_parameter ): Multi; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Prompt/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Prompt/Factory.php index da21193aa8f7..8686f3a60db2 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/Factory.php @@ -22,7 +22,7 @@ use ILIAS\UI\Component\Prompt as I; use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; -use ILIAS\Data\URI; +use ILIAS\UI\URLBuilder; class Factory implements I\Factory { @@ -31,9 +31,9 @@ public function __construct( ) { } - public function standard(URI $async_url): Standard + public function standard(URLBuilder $url_builder): Standard { - return new Standard($this->signal_generator, $async_url); + return new Standard($this->signal_generator, $url_builder); } public function state(): State\Factory diff --git a/components/ILIAS/UI/src/Implementation/Component/Prompt/Prompt.php b/components/ILIAS/UI/src/Implementation/Component/Prompt/Prompt.php index e9870cd6539c..8da8bc37f66e 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/Prompt.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/Prompt.php @@ -25,8 +25,9 @@ use ILIAS\UI\Implementation\Component\ComponentHelper; use ILIAS\UI\Implementation\Component\JavaScriptBindable; use ILIAS\UI\Implementation\Component\SignalGenerator; - use ILIAS\Data\URI; +use ILIAS\UI\URLBuilder; +use ILIAS\UI\URLBuilderToken; /** * @@ -41,7 +42,7 @@ abstract class Prompt implements I\Prompt public function __construct( SignalGenerator $signal_generator, - protected URI $async_url + protected URLBuilder $url_builder ) { $this->show_signal = $signal_generator->create(); $this->close_signal = $signal_generator->create(); @@ -49,12 +50,17 @@ public function __construct( public function getAsyncUrl(): URI { - return $this->async_url; + return $this->url_builder->buildURI(); + } + + public function getURLBuilder(): URLBuilder + { + return $this->url_builder; } public function getShowSignal(?URI $uri = null): Signal { - $target = $uri ?? $this->async_url; + $target = $uri ?? $this->getAsyncUrl(); $signal = clone $this->show_signal; $signal->addOption('url', $target->__toString()); return $signal; @@ -64,4 +70,11 @@ public function getCloseSignal(): Signal { return $this->close_signal; } + + public function withParameter(URLBuilderToken $token, string|array $value): self + { + $clone = clone $this; + $clone->url_builder = $clone->url_builder->withParameter($token, $value); + return $clone; + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Action/Action.php b/components/ILIAS/UI/src/Implementation/Component/Table/Action/Action.php index aca56b4b433f..67ed4301eac9 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Action/Action.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Action/Action.php @@ -26,6 +26,7 @@ use ILIAS\Data\URI; use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; +use ILIAS\UI\Component\Prompt\Prompt; abstract class Action implements I\Action { @@ -38,15 +39,16 @@ abstract class Action implements I\Action public const OPT_ACTIONID = 'actId'; public const OPT_ROWID = 'rowid'; - protected Signal|URI $target; protected bool $async = false; + protected ?Signal $async_signal = null; public function __construct( protected string $label, - protected URLBuilder $url_builder, + protected Prompt | URLBuilder $target, protected URLBuilderToken $row_id_parameter ) { - $this->target = $url_builder->buildURI(); + + $this->target = $target; } public function getLabel(): string @@ -54,20 +56,21 @@ public function getLabel(): string return $this->label; } - public function getTarget(): Signal|URI + public function getTarget(): Prompt|URLBuilder { return $this->target; } - public function withSignalTarget(Signal $target): self + public function isPrompt(): bool { - $clone = clone $this; - $clone->target = $target; - return $clone; + return $this->target instanceof Prompt; } public function withAsync(bool $async = true): self { + if ($async && $this->isPrompt()) { + throw new \LogicException('Prompt is async by default.'); + } $clone = clone $this; $clone->async = $async; return $clone; @@ -78,31 +81,38 @@ public function isAsync(): bool return $this->async; } - public function withRowId(string $row_id): self + public function withAsyncSignal(Signal $async_signal): self { $clone = clone $this; - $target = $clone->getTarget(); + $clone->async_signal = $async_signal; + return $clone; + } - if ($target instanceof Signal) { - $target->addOption('rowid', $row_id); - } - if ($target instanceof URI) { - $target = $this->url_builder->withParameter( - $this->row_id_parameter, - [$row_id] - ) - ->buildURI(); - } - $clone->target = $target; + //internal, should throw when null + public function getAsyncSignal(): Signal + { + return $this->async_signal; + } + + public function withRowId(string $row_id): self + { + $clone = clone $this; + $clone->target = $clone->target->withParameter( + $clone->row_id_parameter, + [$row_id] + ); return $clone; } public function getURLBuilderJS(): string { - return $this->url_builder->renderObject([$this->row_id_parameter]); + $builder = $this->isPrompt() ? $this->target->getURLBuilder() : $this->target; + return $builder->renderObject([$this->row_id_parameter]); } + public function getURLBuilderTokensJS(): string { - return $this->url_builder->renderTokens([$this->row_id_parameter]); + $builder = $this->isPrompt() ? $this->target->getURLBuilder() : $this->target; + return $builder->renderTokens([$this->row_id_parameter]); } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Action/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Table/Action/Factory.php index 122f22782b3c..65be10efc004 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Action/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Action/Factory.php @@ -23,30 +23,31 @@ use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; use ILIAS\UI\Component\Table\Action as I; +use ILIAS\UI\Component\Prompt\Prompt; class Factory implements I\Factory { public function standard( string $label, - URLBuilder $url_builder, + Prompt | URLBuilder $target, URLBuilderToken $row_id_parameter ): Standard { - return new Standard($label, $url_builder, $row_id_parameter); + return new Standard($label, $target, $row_id_parameter); } public function single( string $label, - URLBuilder $url_builder, + Prompt | URLBuilder $target, URLBuilderToken $row_id_parameter ): Single { - return new Single($label, $url_builder, $row_id_parameter); + return new Single($label, $target, $row_id_parameter); } public function multi( string $label, - URLBuilder $url_builder, + Prompt | URLBuilder $target, URLBuilderToken $row_id_parameter ): Multi { - return new Multi($label, $url_builder, $row_id_parameter); + return new Multi($label, $target, $row_id_parameter); } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php index 0b2c91768a07..774cf36c94af 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php @@ -30,6 +30,9 @@ use ILIAS\UI\Implementation\Component\Table\Action\Action; use ILIAS\UI\Implementation\Component\Input\ViewControl\Pagination; use ILIAS\UI\Implementation\Component\Input\NameSource; +use Psr\Http\Message\ServerRequestInterface; +use ILIAS\UI\URLBuilder; +use ILIAS\UI\Component\Prompt\Prompt; class Renderer extends AbstractComponentRenderer { @@ -201,7 +204,8 @@ protected function renderPresentationRow( public function renderDataTable(Component\Table\Data $component, RendererInterface $default_renderer): string { $tpl = $this->getTemplate("tpl.datatable.html", true, true); - $component = $this->registerActions($component); + + [$component, $prompts] = $this->registerActions($component); [$component, $view_controls] = $component->applyViewControls( $component->getAdditionalViewControlData(), @@ -237,6 +241,7 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa $tpl->setVariable('TITLE', $component->getTitle()); $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount() + $compensate_col_count); $tpl->setVariable('VIEW_CONTROLS', $default_renderer->render($view_controls)); + $tpl->setVariable('DIALOG', $default_renderer->render($prompts)); $sortation_signal = null; // if the generator is empty, and thus invalid, we render an empty row. @@ -378,29 +383,47 @@ protected function renderFullWidthDataCell(Component\Table\Data $component, Temp $tpl->parseCurrentBlock(); } - protected function registerActions(Component\Table\Table $component): Component\Table\Table - { - $opt_action_id = Action::OPT_ACTIONID; - $opt_row_id = Action::OPT_ROWID; + /** + * @return array [Component\Table\Table, Prompt[]] + */ + protected function registerActions(Component\Table\Table $component): array + { if ($component->hasMultiActions()) { $component = $component->withAdditionalOnLoadCode( static fn($id): string => "il.UI.table.data.get('{$id}').selectAll(false);" ); } + $prompts = []; + $actions_js = []; $actions = []; foreach ($component->getAllActions() as $action_id => $action) { - $component = $component->withAdditionalOnLoadCode($this->getActionRegistration((string) $action_id, $action)); + $signal = null; + $url_builder_js = $action->getURLBuilderJS(); + $tokens_js = $action->getURLBuilderTokensJS(); + $async = $action->isAsync() ? 'true' : 'false'; + if ($action->isPrompt()) { + $prompts[] = $action->getTarget(); + $signal = $action->getTarget()->getShowSignal(); + } if ($action->isAsync()) { $signal = clone $component->getAsyncActionSignal(); $signal->addOption(Action::OPT_ACTIONID, $action_id); - $action = $action->withSignalTarget($signal); + $action = $action->withAsyncSignal($signal); } + + $component = $component->withAdditionalOnLoadCode( + static fn($id): string => + "il.UI.table.data.get('{$id}').registerAction('{$action_id}', {$async}, '{$signal}', {$url_builder_js}, {$tokens_js});" + ); + $actions[$action_id] = $action; } $component = $component->withActions($actions); + $opt_action_id = Action::OPT_ACTIONID; + $opt_row_id = Action::OPT_ROWID; $component = $component ->withAdditionalOnLoadCode( static fn($id): string => @@ -410,7 +433,7 @@ protected function registerActions(Component\Table\Table $component): Component\ ->withAdditionalOnLoadCode($this->getMultiActionHandler($component->getMultiActionSignal())) ->withAdditionalOnLoadCode($this->getSelectionHandler($component->getSelectionSignal())); - return $component; + return [$component, $prompts]; } protected function appendTableRows( @@ -531,21 +554,6 @@ protected function getSelectionHandler(Component\Signal $selection_signal): \Clo }; } - protected function getActionRegistration( - string $action_id, - Action $action - ): \Closure { - $async = $action->isAsync() ? 'true' : 'false'; - $url_builder_js = $action->getURLBuilderJS(); - $tokens_js = $action->getURLBuilderTokensJS(); - - return static function ($id) use ($action_id, $async, $url_builder_js, $tokens_js): string { - return " - il.UI.table.data.get('{$id}').registerAction('{$action_id}', {$async}, {$url_builder_js}, {$tokens_js}); - "; - }; - } - public function renderDataRow(Component\Table\DataRow $component, RendererInterface $default_renderer): string { $cell_tpl = $this->getTemplate("tpl.datacell.html", true, true); @@ -635,13 +643,17 @@ protected function getSingleActionsForRow(string $row_id, array $actions): \ILIA { $f = $this->getUIFactory(); $buttons = []; - foreach ($actions as $act) { - $act = $act->withRowId($row_id); - $target = $act->getTarget(); - if ($target instanceof URI) { - $target = (string) $target; + foreach ($actions as $action) { + $action = $action->withRowId($row_id); + $target = $action->getTarget(); + if ($action->isPrompt()) { + $btn_action = $target->getShowSignal(); + } elseif ($action->isAsync()) { + $btn_action = $action->getAsyncSignal(); + } else { + $btn_action = $target->buildURI()->__toString(); } - $buttons[] = $f->button()->shy($act->getLabel(), $target); + $buttons[] = $f->button()->shy($action->getLabel(), $btn_action); } return $f->dropdown()->standard($buttons); } @@ -650,8 +662,8 @@ protected function getSingleActionsForRow(string $row_id, array $actions): \ILIA public function renderOrderingTable(Component\Table\Ordering $component, RendererInterface $default_renderer): string { $tpl = $this->getTemplate("tpl.orderingtable.html", true, true); - $component = $this->registerActions($component); + [$component, $prompts] = $this->registerActions($component); [$component, $view_controls] = $component->applyViewControls(); $rows = $component->getDataBinding()->getRows( @@ -659,7 +671,6 @@ public function renderOrderingTable(Component\Table\Ordering $component, Rendere array_keys($component->getVisibleColumns()), ); - if (!$component->isOrderingDisabled()) { $component = $component->withAdditionalOnLoadCode( static fn($id): string => "il.UI.table.ordering.init('{$id}');" @@ -696,7 +707,6 @@ function() {document.querySelector('#$tableid form.c-table-ordering__form').subm $tpl->setVariable('FORM_BUTTONS', $default_renderer->render($submit)); $tpl->setVariable('POS_INPUT_TITLE', $this->txt('table_posinput_col_title')); - } $tpl->setVariable('ID', $tableid); @@ -704,6 +714,7 @@ function() {document.querySelector('#$tableid form.c-table-ordering__form').subm $tpl->setVariable('TITLE', $component->getTitle()); $tpl->setVariable('COL_COUNT', (string) $component->getColumnCount() + $compensate_col_count); $tpl->setVariable('VIEW_CONTROLS', $default_renderer->render($view_controls)); + $tpl->setVariable('DIALOG', $default_renderer->render($prompts)); $columns = $component->getVisibleColumns(); foreach ($columns as $col_id => $col) { diff --git a/components/ILIAS/UI/src/examples/Prompt/Standard/base.php b/components/ILIAS/UI/src/examples/Prompt/Standard/base.php index 48533b5b2439..ac0479d2da7b 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/base.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/base.php @@ -61,8 +61,8 @@ function base() //build the prompt $query_namespace = ['prompt', 'example0']; list($url_builder, $token) = $url_builder->acquireParameters($query_namespace, "show"); - $url_builder = $url_builder->withParameter($token, "true"); - $prompt = $factory->prompt()->standard($url_builder->buildURI()); + $prompt = $factory->prompt()->standard($url_builder) + ->withParameter($token, "true"); //build the endpoint returning the wrapped message $query = $DIC->http()->wrapper()->query(); diff --git a/components/ILIAS/UI/src/examples/Prompt/Standard/form.php b/components/ILIAS/UI/src/examples/Prompt/Standard/form.php index 7ed682b3fc03..bf3424cb0827 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/form.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/form.php @@ -57,7 +57,7 @@ function form() $query_namespace = ['prompt', 'example2']; list($url_builder, $action_token) = $url_builder->acquireParameters($query_namespace, "action"); $url_builder = $url_builder->withParameter($action_token, "form"); - $prompt = $factory->prompt()->standard($url_builder->buildURI()); + $prompt = $factory->prompt()->standard($url_builder); //fill the state according to (query-)parameters $query = $DIC->http()->wrapper()->query(); diff --git a/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php b/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php index 894c592185b4..120da60803a8 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php @@ -58,8 +58,8 @@ function parameters() list($url_builder, $action_token, $amount_token) = $url_builder->acquireParameters($query_namespace, "action", "amount"); $url_builder = $url_builder->withParameter($action_token, "showprompt"); - $default_uri = $url_builder->withParameter($amount_token, "1")->buildURI(); - $prompt = $factory->prompt()->standard($default_uri); + $prompt = $factory->prompt()->standard($url_builder) + ->withParameter($amount_token, "1"); //build some endpoint $query = $DIC->http()->wrapper()->query(); diff --git a/components/ILIAS/UI/src/examples/Prompt/State/Close/base.php b/components/ILIAS/UI/src/examples/Prompt/State/Close/base.php index 8e93b89f6008..48aadeb9539a 100644 --- a/components/ILIAS/UI/src/examples/Prompt/State/Close/base.php +++ b/components/ILIAS/UI/src/examples/Prompt/State/Close/base.php @@ -71,7 +71,7 @@ function base() } //render prompt and button - $prompt = $factory->prompt()->standard($url_builder->buildURI()); + $prompt = $factory->prompt()->standard($url_builder); return $renderer->render([ $factory->button()->standard('Show Prompt', $prompt->getShowSignal()), $prompt diff --git a/components/ILIAS/UI/src/examples/Prompt/State/Redirect/base.php b/components/ILIAS/UI/src/examples/Prompt/State/Redirect/base.php index 09b980fce2eb..b3bfa956eef1 100644 --- a/components/ILIAS/UI/src/examples/Prompt/State/Redirect/base.php +++ b/components/ILIAS/UI/src/examples/Prompt/State/Redirect/base.php @@ -72,7 +72,7 @@ function base() } //render prompt and button - $prompt = $factory->prompt()->standard($url_builder->buildURI()); + $prompt = $factory->prompt()->standard($url_builder); return $renderer->render([ $factory->button()->standard('Show Prompt', $prompt->getShowSignal()), $prompt diff --git a/components/ILIAS/UI/src/examples/Prompt/State/Show/base.php b/components/ILIAS/UI/src/examples/Prompt/State/Show/base.php index f307c9599d86..9e1415644bac 100644 --- a/components/ILIAS/UI/src/examples/Prompt/State/Show/base.php +++ b/components/ILIAS/UI/src/examples/Prompt/State/Show/base.php @@ -62,7 +62,7 @@ function base() } //build the prompt - $prompt = $factory->prompt()->standard($url_builder->buildURI()); + $prompt = $factory->prompt()->standard($url_builder); $show_button = $factory->button()->standard('Show Prompt', $prompt->getShowSignal()); diff --git a/components/ILIAS/UI/src/examples/Table/Action/Multi/base.php b/components/ILIAS/UI/src/examples/Table/Action/Multi/base.php index df1cce9b0c14..21e9cfafb098 100755 --- a/components/ILIAS/UI/src/examples/Table/Action/Multi/base.php +++ b/components/ILIAS/UI/src/examples/Table/Action/Multi/base.php @@ -60,7 +60,7 @@ function base() 'do something else', $url_builder->withParameter($action_token, "do_something_else"), $id_token - )->withAsync(), + ), ]; $table = getExampleTable($f) @@ -75,30 +75,14 @@ function base() if ($query->has($action_token->getName())) { $action = $query->retrieve($action_token->getName(), $refinery->to()->string()); $ids = $query->retrieve($id_token->getName(), $refinery->custom()->transformation(fn($v) => $v)); - - if ($action === 'do_something_else') { - $items = []; - foreach ($ids as $id) { - $items[] = $f->modal()->interruptiveItem()->keyValue($id, $id_token->getName(), $id); - } - echo($r->renderAsync([ - $f->modal()->interruptive( - 'do something else', - 'affected items', - '#' - )->withAffectedItems($items) - ])); - exit(); - } else { - $items = $f->listing()->characteristicValue()->text( - [ - 'table_action' => $action, - 'id' => print_r($ids, true), - ] - ); - $result[] = $f->divider()->horizontal(); - $result[] = $items; - } + $items = $f->listing()->characteristicValue()->text( + [ + 'table_action' => $action, + 'id' => print_r($ids, true), + ] + ); + $result[] = $f->divider()->horizontal(); + $result[] = $items; } return $r->render($result); diff --git a/components/ILIAS/UI/src/examples/Table/Action/Single/base.php b/components/ILIAS/UI/src/examples/Table/Action/Single/base.php index f1266471b847..888d589d5db7 100755 --- a/components/ILIAS/UI/src/examples/Table/Action/Single/base.php +++ b/components/ILIAS/UI/src/examples/Table/Action/Single/base.php @@ -60,7 +60,7 @@ function base() 'do something else', $url_builder->withParameter($action_token, "do_something_else"), $id_token - )->withAsync(), + ), ]; $table = getExampleTable($f) @@ -74,30 +74,14 @@ function base() if ($query->has($action_token->getName())) { $action = $query->retrieve($action_token->getName(), $refinery->to()->string()); $ids = $query->retrieve($id_token->getName(), $refinery->custom()->transformation(fn($v) => $v)); - - if ($action === 'do_something_else') { - $items = []; - foreach ($ids as $id) { - $items[] = $f->modal()->interruptiveItem()->keyValue($id, $id_token->getName(), $id); - } - echo($r->renderAsync([ - $f->modal()->interruptive( - 'do something else', - 'affected items', - '#' - )->withAffectedItems($items) - ])); - exit(); - } else { - $items = $f->listing()->characteristicValue()->text( - [ - 'table_action' => $action, - 'id' => print_r($ids, true), - ] - ); - $result[] = $f->divider()->horizontal(); - $result[] = $items; - } + $items = $f->listing()->characteristicValue()->text( + [ + 'table_action' => $action, + 'id' => print_r($ids, true), + ] + ); + $result[] = $f->divider()->horizontal(); + $result[] = $items; } return $r->render($result); } diff --git a/components/ILIAS/UI/src/examples/Table/Action/Standard/base.php b/components/ILIAS/UI/src/examples/Table/Action/Standard/base.php index a927bef9436d..2bdc9545e014 100755 --- a/components/ILIAS/UI/src/examples/Table/Action/Standard/base.php +++ b/components/ILIAS/UI/src/examples/Table/Action/Standard/base.php @@ -61,10 +61,12 @@ function base() $id_token ), $f->table()->action()->standard( - 'do something else', - $url_builder->withParameter($action_token, "do_something_else"), + 'open a prompt', + $f->prompt()->standard( + $url_builder->withParameter($action_token, "something_prompt") + ), $id_token - )->withAsync(), + ), ]; $table = getExampleTable($f) @@ -80,28 +82,22 @@ function base() $action = $query->retrieve($action_token->getName(), $refinery->to()->string()); $ids = $query->retrieve($id_token->getName(), $refinery->custom()->transformation(fn($v) => $v)); - if ($action === 'do_something_else') { - $items = []; - foreach ($ids as $id) { - $items[] = $f->modal()->interruptiveItem()->keyValue($id, $id_token->getName(), $id); - } - echo($r->renderAsync([ - $f->modal()->interruptive( - 'do something else', - 'affected items', - '#' - )->withAffectedItems($items) - ])); - exit(); - } else { - $items = $f->listing()->characteristicValue()->text( - [ - 'table_action' => $action, - 'id' => print_r($ids, true), - ] - ); - $result[] = $f->divider()->horizontal(); - $result[] = $items; + switch ($action) { + case 'something_prompt': + $message = $f->messageBox()->success('some message box in a prompt'); + $response = $f->prompt()->state()->show($message); + echo($r->renderAsync($response)); + exit(); + + default: + $items = $f->listing()->characteristicValue()->text( + [ + 'table_action' => $action, + 'id' => print_r($ids, true), + ] + ); + $result[] = $f->divider()->horizontal(); + $result[] = $items; } } diff --git a/components/ILIAS/UI/src/examples/Table/Data/base.php b/components/ILIAS/UI/src/examples/Table/Data/base.php index 4c2cc503725b..854dc539be6f 100755 --- a/components/ILIAS/UI/src/examples/Table/Data/base.php +++ b/components/ILIAS/UI/src/examples/Table/Data/base.php @@ -123,16 +123,25 @@ function base() 'Remove Student', $url_builder->withParameter($action_parameter_token, "delete"), $row_id_token - ) - /** - * An async Action will trigger an AJAX-call to the action's target - * and display the results in a modal-layer over the Table. - * Parameters are passed to the call, but you will have to completely - * build the contents of the response. DO NOT render an entire page ;) - */ - ->withAsync(), + ), + /** + * A prompt Action will open a Prompt and load its url, expecting a Prompt State in return. + */ + 'status' => + $f->table()->action()->standard( + 'Status', + $f->prompt()->standard( + $url_builder->withParameter($action_parameter_token, "status") + ), + $row_id_token + ), + /** + * An async Action will trigger an AJAX-call to the action's target + * and display the results in a modal-layer over the Table. + * THIS IS DEPRECATED in 11. Please use Prompts instead. + */ 'info' => - $f->table()->action()->standard( //in both + $f->table()->action()->standard( 'Info', $url_builder->withParameter($action_parameter_token, "info"), $row_id_token @@ -242,10 +251,9 @@ protected function getRecords(?Range $range = null, ?Order $order = null): array } }; - /** * setup the Table and hand over the request; - * with an ID for the table, parameters will be stored throughout url changes + * with an Id for the table, parameters will be stored throughout url changes */ $table = $f->table() ->data($data_retrieval, 'a data table', $columns) @@ -277,25 +285,19 @@ protected function getRecords(?Range $range = null, ?Order $order = null): array 'id' => print_r($ids, true), ]); - /** take care of the async-call; 'delete'-action asks for it. */ - if ($action === 'delete') { - $items = []; - foreach ($ids as $id) { - $items[] = $f->modal()->interruptiveItem()->keyValue($id, $row_id_token->getName(), $id); - } - echo($r->renderAsync([ - $f->modal()->interruptive( - 'Deletion', - 'You are about to delete items!', - '#' - )->withAffectedItems($items) - ->withAdditionalOnLoadCode(static fn($id): string => "console.log('ASYNC JS');") - ])); + if ($action === 'status') { + $ids = array_map(fn($id) => $f->button()->shy($id, '#'), $ids); + $message = $f->messageBox()->info('some message box in a prompt-dialog') + ->withLinks($ids); + + $response = $f->prompt()->state()->show($message); + echo($r->renderAsync($response)); exit(); } + if ($action === 'info') { echo( - $r->render($f->messageBox()->info('an info message:
  • ' . implode('
  • ', $ids))) + $r->render($f->messageBox()->info('an (asynch) info message:
  • ' . implode('
  • ', $ids))) . '' ); exit(); diff --git a/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html b/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html index b3cbd734bfa4..ce6a84fbe873 100755 --- a/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html +++ b/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html @@ -83,4 +83,7 @@

    {TITLE}

    + + {DIALOG} + diff --git a/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html b/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html index 568f0504355d..58c21170f2f2 100644 --- a/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html +++ b/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html @@ -103,4 +103,7 @@

    {TITLE}

    + + {DIALOG} + diff --git a/components/ILIAS/UI/tests/Client/Table/Data/data.table.test.js b/components/ILIAS/UI/tests/Client/Table/Data/data.table.test.js index 0d608f2e591f..98d6f1e944b9 100755 --- a/components/ILIAS/UI/tests/Client/Table/Data/data.table.test.js +++ b/components/ILIAS/UI/tests/Client/Table/Data/data.table.test.js @@ -93,9 +93,10 @@ describe('Data Table', () => { const dt = f.get('tid'); strict.equal(dt.registerAction instanceof Function, true); strict.equal(dt.selectAll instanceof Function, true); - strict.equal(dt.doSingleAction instanceof Function, true); strict.equal(dt.doMultiAction instanceof Function, true); strict.equal(dt.doActionForAll instanceof Function, true); strict.equal(dt.doAction instanceof Function, true); + strict.equal(dt.collectSelectedRowIds instanceof Function, true); + strict.equal(dt.selectAll instanceof Function, true); }); }); diff --git a/components/ILIAS/UI/tests/Component/Prompt/PromptTest.php b/components/ILIAS/UI/tests/Component/Prompt/PromptTest.php new file mode 100644 index 000000000000..d38864937203 --- /dev/null +++ b/components/ILIAS/UI/tests/Component/Prompt/PromptTest.php @@ -0,0 +1,108 @@ +prompt_factory; + } + }; + } + + public function testPromptRender(): void + { + $url_builder = new URLBuilder(new URI('https://www.ilias.de')); + $prompt = $this->getUIFactory()->prompt()->standard($url_builder); + $renderer = $this->getDefaultRenderer(); + + $expected_html = ' +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + '; + $actual_html = $renderer->render($prompt); + + $this->assertEquals( + $this->brutallyTrimHTML($expected_html), + $this->brutallyTrimHTML($actual_html), + ); + } + + public function testPromptSignals(): void + { + $url_builder = new URLBuilder(new URI('https://www.ilias.de')); + $url = $url_builder->buildURI()->__toString(); + $prompt = $this->getUIFactory()->prompt()->standard($url_builder); + $this->assertEquals($url, $prompt->getAsyncUrl()); + $this->assertEquals($url, $prompt->getShowSignal()->getOptions()['url']); + $this->assertInstanceOf(I\Signal::class, $prompt->getShowSignal()); + $this->assertInstanceOf(I\Signal::class, $prompt->getCloseSignal()); + + $uri = new URI('https://test11.ilias.de'); + $this->assertEquals($uri->__toString(), $prompt->getShowSignal($uri)->getOptions()['url']); + } + + public function testPromptURIParamter(): void + { + $url_builder = new URLBuilder(new URI('https://www.ilias.de')); + list($url_builder, $parameter_token) = $url_builder->acquireParameters( + ['prompt', 'test'], + "param" + ); + + $prompt = $this->getUIFactory()->prompt()->standard($url_builder) + ->withParameter($parameter_token, 'somevalue'); + + $url = $url_builder->withParameter($parameter_token, 'somevalue')->buildURI()->__toString(); + $this->assertEquals($url, $prompt->getAsyncUrl()); + $this->assertEquals($url, $prompt->getShowSignal()->getOptions()['url']); + } + +} diff --git a/components/ILIAS/UI/tests/Component/Table/Action/ActionTest.php b/components/ILIAS/UI/tests/Component/Table/Action/ActionTest.php index caa8af6d30a3..f69496b1a093 100755 --- a/components/ILIAS/UI/tests/Component/Table/Action/ActionTest.php +++ b/components/ILIAS/UI/tests/Component/Table/Action/ActionTest.php @@ -24,8 +24,8 @@ use ILIAS\UI\Component\Table\Action as I; use ILIAS\UI\Implementation\Component\Table\Action as Implementation; use ILIAS\Data; -use ILIAS\UI\Implementation\Component\Signal; use ILIAS\UI\URLBuilder; +use ILIAS\UI\Implementation\Component\Prompt\Standard as Prompt; /** * Basic Tests for Table-Actions. @@ -34,8 +34,8 @@ class ActionTest extends ILIAS_UI_TestBase { protected Implementation\Standard $link_action; protected Data\URI $link_target; - protected Implementation\Standard $signal_action; - protected Signal $signal_target; + protected Implementation\Standard $prompt_action; + protected Implementation\Standard $async_action; protected function buildFactories() { @@ -49,34 +49,26 @@ public function setUp(): void { list($f, $df) = $this->buildFactories(); $target = $df->uri('http://wwww.ilias.de?ref_id=1'); - $url_builder = new URLBuilder($target); list($builder, $token) = array_values( $url_builder->acquireParameter(['namespace'], 'rowids') ); - $label = 'label'; + $prompt = $this->createMock(Prompt::class); + $this->link_target = $target; $this->link_action = $f->standard($label, $builder, $token); - $this->signal_target = new Signal('sig_id'); - $this->signal_action = $this->link_action->withAsync(true); + $this->prompt_action = $f->standard($label, $prompt, $token); + $this->async_action = $this->link_action->withAsync(); } public function testDataTableActionAttributes(): void { $act = $this->link_action; $this->assertEquals('label', $act->getLabel()); - $this->assertEquals($this->link_target . "&namespace_rowids=", $act->getTarget()); - $this->assertFalse($this->link_action->isAsync()); - $this->assertTrue($this->signal_action->isAsync()); - $this->assertIsString($this->signal_action->getURLBuilderJS()); - $this->assertIsString($this->signal_action->getURLBuilderTokensJS()); - } - - public function testDataTableActionSignalTarget(): void - { - $act = $this->signal_action->withSignalTarget($this->signal_target); - $this->assertEquals($this->signal_target, $act->getTarget()); + $this->assertEquals($this->link_target . "&namespace_rowids=", $act->getTarget()->buildURI()); + $this->assertIsString($this->link_action->getURLBuilderJS()); + $this->assertIsString($this->link_action->getURLBuilderTokensJS()); } public function testDataTableActionStringTarget(): void @@ -90,8 +82,23 @@ public function testDataTableActionRowIdOnURI(): void { $act = $this->link_action->withRowId('test-id'); $this->assertEquals( - 'ref_id=1&namespace_rowids[]=test-id', - urldecode($act->getTarget()->getQuery()) + 'http://wwww.ilias.de?ref_id=1&namespace_rowids[]=test-id', + urldecode($act->getTarget()->buildURI()->__toString()) ); } + + public function testDataTableActionModes(): void + { + $this->assertFalse($this->link_action->isPrompt()); + $this->assertFalse($this->async_action->isPrompt()); + $this->assertTrue($this->prompt_action->isPrompt()); + $this->assertFalse($this->prompt_action->isAsync()); + $this->assertTrue($this->async_action->isAsync()); + } + + public function testDataTableActionPromptMayNotBeAsync(): void + { + $this->expectException(\LogicException::class); + $this->prompt_action->withAsync(true); + } } diff --git a/components/ILIAS/UI/tests/Component/Table/DataRendererTest.php b/components/ILIAS/UI/tests/Component/Table/DataRendererTest.php index 7ee5d2d11c2d..4e1d2790a618 100644 --- a/components/ILIAS/UI/tests/Component/Table/DataRendererTest.php +++ b/components/ILIAS/UI/tests/Component/Table/DataRendererTest.php @@ -25,6 +25,7 @@ use ILIAS\UI\Implementation\Component as I; use ILIAS\Data; use ILIAS\UI\URLBuilder; +use Psr\Http\Message\ServerRequestInterface; /** * wrapper around the renderer to expose protected functions @@ -36,11 +37,6 @@ public function p_getMultiActionHandler(I\Signal $signal) return $this->getMultiActionHandler($signal); } - public function p_getActionRegistration(string $action_id, I\Table\Action\Action $action) - { - return $this->getActionRegistration($action_id, $action); - } - public function p_buildMultiActionsDropdown( array $actions, I\Signal $action_signal, @@ -76,6 +72,13 @@ public function p_renderActionsHeader( */ class DataRendererTest extends TableRendererTestBase { + protected I\Prompt\Standard $mock_dialog; + + public function setUp(): void + { + $this->mock_dialog = $this->createMock(I\Prompt\Standard::class); + } + private function getRenderer() { return new DTRenderer( @@ -90,6 +93,85 @@ private function getRenderer() ); } + protected function getActionFactory() + { + return new I\Table\Action\Factory(); + } + + protected function getColumnFactory() + { + return new I\Table\Column\Factory( + $this->getLanguage() + ); + } + + protected function getDummyRequest() + { + $request = $this->createMock(ServerRequestInterface::class); + $request + ->method("getUri") + ->willReturn(new \GuzzleHttp\Psr7\Uri('http://localhost:80')); + $request + ->method("getQueryParams") + ->willReturn([]); + return $request; + } + + public function getDataFactory(): Data\Factory + { + return new Data\Factory(); + } + + public function getUIFactory(): NoUIFactory + { + $prompt_factory = $this->createMock(I\Prompt\Factory::class); + $prompt_factory + ->method("standard") + ->willReturn( + $this->mock_dialog + ); + $factory = new class ( + $this->getTableFactory(), + $prompt_factory + ) extends NoUIFactory { + public function __construct( + protected I\Table\Factory $table_factory, + protected I\Prompt\Factory $prompt_factory + ) { + } + public function button(): I\Button\Factory + { + return new I\Button\Factory(); + } + public function dropdown(): I\Dropdown\Factory + { + return new I\Dropdown\Factory(); + } + public function symbol(): I\Symbol\Factory + { + return new I\Symbol\Factory( + new I\Symbol\Icon\Factory(), + new I\Symbol\Glyph\Factory(), + new I\Symbol\Avatar\Factory() + ); + } + public function table(): I\Table\Factory + { + return $this->table_factory; + } + public function divider(): I\Divider\Factory + { + return new I\Divider\Factory(); + } + public function prompt(): I\Prompt\Factory + { + return $this->prompt_factory; + } + + }; + return $factory; + } + public function testDataTableGetMultiActionHandler() { $renderer = $this->getRenderer(); @@ -105,25 +187,6 @@ public function testDataTableGetMultiActionHandler() $this->assertEquals($expected, $actual); } - public function testDataTableGetActionRegistration() - { - $renderer = $this->getRenderer(); - $f = $this->getActionFactory(); - $url = $this->getDataFactory()->uri('http://wwww.ilias.de?ref_id=1'); - $url_builder = new URLBuilder($url); - list($builder, $token) = $url_builder->acquireParameter(['namespace'], 'param'); - - $action = $f->standard('label', $builder, $token); - $closure = $renderer->p_getActionRegistration('action_id', $action); - - $actual = $this->brutallyTrimHTML($closure('component_id')); - $url = $url->__toString(); - $expected = $this->brutallyTrimHTML( - 'il.UI.table.data.get(\'component_id\').registerAction(\'action_id\', false, new il.UI.core.URLBuilder(new URL("http://wwww.ilias.de?ref_id=1&namespace_param="), new Map([["namespace_param",new il.UI.core.URLBuilderToken(["namespace"], "param",' - ); - $this->assertStringStartsWith($expected, $actual); - } - public function testDataTableMultiActionsDropdown() { $renderer = $this->getRenderer(); @@ -153,7 +216,7 @@ public function testDataTableSingleActionsDropdown() $url_builder = new URLBuilder($url); list($builder, $token) = $url_builder->acquireParameter(['namespace'], 'param'); $actions = [ - 'a1' => $f->standard('label1', $builder, $token)->withAsync(), + 'a1' => $f->standard('label1', $builder, $token), 'a2' => $f->standard('label2', $builder, $token) ]; $this->assertEquals( @@ -385,7 +448,7 @@ public function testDataTableRowBuilder() $url_builder = new URLBuilder($url); list($builder, $token) = $url_builder->acquireParameter(['namespace'], 'param'); $actions = [ - 'a1' => $f->standard('label1', $builder, $token)->withAsync(), + 'a1' => $f->standard('label1', $builder, $token), 'a2' => $f->standard('label2', $builder, $token) ]; @@ -495,7 +558,7 @@ public function getTotalRowCount( $table = $this->getTableFactory()->data($data, '', $columns) ->withRequest($this->getDummyRequest()); - $html = $this->getDefaultRenderer()->render($table); + $html = $this->getDefaultRenderer(null, [$this->mock_dialog])->render($table); $translation = $this->getLanguage()->txt('ui_table_no_records'); $column_count = count($columns); diff --git a/components/ILIAS/UI/tests/Component/Table/DataTest.php b/components/ILIAS/UI/tests/Component/Table/DataTest.php index 631f90185d3b..ad32227c286c 100755 --- a/components/ILIAS/UI/tests/Component/Table/DataTest.php +++ b/components/ILIAS/UI/tests/Component/Table/DataTest.php @@ -77,7 +77,6 @@ public function testDataTableBasicConstruction(): void $table = $this->getTableFactory()->data($data, 'title', $cols); $this->assertInstanceOf(Order::class, $table->getOrder()); $this->assertInstanceOf(Range::class, $table->getRange()); - $this->assertInstanceOf(I\Signal::class, $table->getAsyncActionSignal()); $this->assertInstanceOf(I\Signal::class, $table->getMultiActionSignal()); $this->assertInstanceOf(I\Signal::class, $table->getSelectionSignal()); $this->assertFalse($table->hasSingleActions());