diff --git a/components/ILIAS/GlobalScreen/classes/UI/Footer/Groups/class.ilFooterGroupsGUI.php b/components/ILIAS/GlobalScreen/classes/UI/Footer/Groups/class.ilFooterGroupsGUI.php index 1ac2eb4e2505..15e3f724b56a 100755 --- a/components/ILIAS/GlobalScreen/classes/UI/Footer/Groups/class.ilFooterGroupsGUI.php +++ b/components/ILIAS/GlobalScreen/classes/UI/Footer/Groups/class.ilFooterGroupsGUI.php @@ -153,7 +153,7 @@ private function addButtons(): array $confirm_reset = $this->ui_factory ->prompt() ->standard( - $this->pons->flow()->getHereAsURI(self::CMD_CONFIRM_RESET), + new \ILIAS\UI\URLBuilder($this->pons->flow()->getHereAsURI(self::CMD_CONFIRM_RESET)), ); $this->dic diff --git a/components/ILIAS/GlobalScreen/src/GUI/I18n/MultiLanguage/MultiLanguageGUI.php b/components/ILIAS/GlobalScreen/src/GUI/I18n/MultiLanguage/MultiLanguageGUI.php index f1c4497a844a..36b3ad67a104 100644 --- a/components/ILIAS/GlobalScreen/src/GUI/I18n/MultiLanguage/MultiLanguageGUI.php +++ b/components/ILIAS/GlobalScreen/src/GUI/I18n/MultiLanguage/MultiLanguageGUI.php @@ -115,7 +115,7 @@ private function index(): void } $prompt = $this->ui_factory->prompt()->standard( - $this->pons->flow()->getHereAsURI(self::CMD_LANGUAGE_SELECTION) + new \ILIAS\UI\URLBuilder($this->pons->flow()->getHereAsURI(self::CMD_LANGUAGE_SELECTION)), ); // Edit Button diff --git a/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/ComponentFactory.php b/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/ComponentFactory.php index 1607fac1727c..c5d322dbcf67 100644 --- a/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/ComponentFactory.php +++ b/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/ComponentFactory.php @@ -51,7 +51,7 @@ public function getButtonToControlCenter( rtrim(ILIAS_HTTP_PATH, '/') . '/' . ltrim($this->link_factory->getViewLink($ref_id, $obj_id, $type), '/') ); - $prompt = $this->ui_factory->prompt()->standard($view_link); + $prompt = $this->ui_factory->prompt()->standard(new \ILIAS\UI\URLBuilder($view_link)); $button = $this->ui_factory->button()->standard($this->getButtonLabel(), $prompt->getShowSignal()); $message = $this->ui_factory->messageBox()->info($this->getStatusInfo($status))->withButtons([$button]); return [$message, $prompt]; diff --git a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Overview.php b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Overview.php index 75206e73a8c0..6fe188ca0761 100644 --- a/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Overview.php +++ b/components/ILIAS/Questions/src/AnswerForm/Capabilities/SuggestedLearningContent/Overview.php @@ -99,7 +99,7 @@ private function buildPrompt(): Prompt return $this->environment->getUIFactory()->prompt()->standard( $this->environment->withSubActionParameter( self::SUB_ACTION_SELECT_TYPE - )->getUrlBuilder()->buildURI() + )->getUrlBuilder(), ); } diff --git a/components/ILIAS/Test/src/Scoring/Manual/class.ConsecutiveScoringGUI.php b/components/ILIAS/Test/src/Scoring/Manual/class.ConsecutiveScoringGUI.php index 03e7762aad5a..9cd65ee37fe3 100644 --- a/components/ILIAS/Test/src/Scoring/Manual/class.ConsecutiveScoringGUI.php +++ b/components/ILIAS/Test/src/Scoring/Manual/class.ConsecutiveScoringGUI.php @@ -60,7 +60,6 @@ class ConsecutiveScoringGUI implements SegmentRetrieval private const FILTER_SCOREDBY = 'fscrby'; private ?array $filter_values = null; - private Prompt $prompt; public function __construct( protected readonly \ilCtrlInterface $ctrl, @@ -78,7 +77,6 @@ public function __construct( protected readonly ConsecutiveScoringURLs $scoring_url_builder, protected readonly \ilUIFilterService $filter_service, ) { - $this->prompt = $this->ui_factory->prompt()->standard($this->scoring_url_builder->buildURI()); } public function executeCommand(): void { @@ -254,7 +252,7 @@ public function getSegment( $viewcontrol_values->isUserCentric() ? $this->collectForUser($usr_active_id, $question_ids) : $this->collectForQuestion($question_id, $usr_active_ids), - $this->prompt + $this->ui_factory->prompt()->standard(new \ILIAS\UI\URLBuilder($this->scoring_url_builder->buildURI())) ] ) ) @@ -663,17 +661,22 @@ protected function singleSegmentContent( ); } - protected function getSingleFormButton(int $question_id, int $usr_active_id, int $attempt_id): StdButton + /** @return array{0: StdButton, 1: Prompt} */ + protected function getSingleFormButton(int $question_id, int $usr_active_id, int $attempt_id): array { $url = $this->scoring_url_builder ->withAction(self::ACTION_FORM_STATE) ->withIdParameters($question_id, $usr_active_id, $attempt_id) ->buildURI(); - return $this->ui_factory->button()->standard( + $prompt = $this->ui_factory->prompt()->standard(new \ILIAS\UI\URLBuilder($url)); + + $button = $this->ui_factory->button()->standard( $this->lng->txt('edit_score'), - $this->prompt->getShowSignal($url) + $prompt->getShowSignal() ); + + return [$button, $prompt]; } protected function getSegmentActionsForUser(int $usr_active_id): array diff --git a/components/ILIAS/UI/docs/ROADMAP.md b/components/ILIAS/UI/docs/ROADMAP.md index e0f1486372a8..2205c425ecee 100755 --- a/components/ILIAS/UI/docs/ROADMAP.md +++ b/components/ILIAS/UI/docs/ROADMAP.md @@ -406,6 +406,26 @@ contain an object that is exported from a `.js` file, which can be imported dire Alternatively, we build this as a string and store this in an `.php` artifact and ship its content via our component renderers, so it is available as global state (e.g. `il.UI.Config`). +### Refactoring `UI\Component\Table\Data` and `Ordering` components (advanced, ~8d) + +The Data and Ordering Table have accumulated some structural debt on both server and client, which makes it increasingly +hard to maintain and extend it. We should therefore refactor these components internally and focus on the following +aspects: + +- Remove the unnecessary `Table\AbstractTable` and `Table\Table` inheritance level. +- Refine the public interface of `Table\Action` and `Table\Column` so consumers can contribute custom implementations in +theory. Today they expose too little information for the UI framework to process them and since they are passed to +Tables by composition, we should also support less concrete implementations than our internals. A symptom of this is the +check that given Actions and Columns are an instanfe of our implementation. +- Simplify the rendering by reusing more UI components internally, e.g. for warning dialog and replace implementation of +`Input\NameSource` with proper context rendering. +- Stop carrying around Action information between rendering steps via the composable DTO. This can most likely be +avoided by delegating more of this work to the client. +- Modularise the current client-side facility which has grown into a god object. Create smaller more managable modules +e.g. for selection, action handling, async rendering, navigation. +- Modernise the client-side architecture to fit modern patterns and remove the use of globals. Remove dynamic DOM +lookups (which potentially happen many times) and follow correctness on construction. + ## Long Term ### Mark Some Components as Internal 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 d02c2c73e55e..df00de012610 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;#o;#a;#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.#o=this.#s.getElementsByClassName("c-table-data__async_message").item(0),this.#a=this.#o.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 o=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}#d(){const e=this.#s.querySelector("dialog.c-table-data__multiaction-warning");il.UI.modal.showModal(e,{},{id:e.id})}#l(e){this.#n.querySelectorAll(".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{t.checked&&e.push(t.value)}),e}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],o=i.urlTokens.values().next().value;i.urlBuilder.writeParameter(o,t);const a=decodeURI(i.urlBuilder.getUrl().toString());i.async?this.asyncAction(a):window.location.href=a}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.#a.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:o}=s;switch(e.which){case 37:i-=1;break;case 39:i+=1;break;case 38:o-=1;break;case 40:o+=1}o<0||i<0||o>=this.#n.rows.length||i>=s.cells.length||this.focusCell(t,o,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 o{#s;#n;#h;#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.#h.forEach(e=>this.#u(e)),this.#y()}#y(){this.#n.querySelectorAll('input[type="number"]').forEach(e=>{e.addEventListener("keydown",e=>{"Enter"===e.key&&e.preventDefault()})})}#p(){this.#h=Array.from(this.#n.rows),this.#h.shift(),this.#h.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.#h.indexOf(t)>this.#h.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.#R(),{once:!0}),this.#s.classList.remove("dragInProgress"),this.#p(),this.#f()}#R(){this.#g.classList.remove("c-table-data__row--drag-settle")}#b(){return this.#h.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.#h.includes(s)&&(this.#h.indexOf(s)>this.#h.indexOf(this.#g)?s.after(this.#m):s.before(this.#m)),t.clientY<100?this.#v(-8):t.clientY>window.innerHeight-100?this.#v(8):this.#E()}touchend(){this.#s.removeChild(this.#w),this.#m.replaceWith(this.#g),this.#g.classList.remove("c-table-data__row--drag-origin"),this.#p(),this.#f()}#f(){let e=10;this.#n.querySelectorAll('input[type="number"]').forEach(t=>{t.value=e,e+=10})}#v(e){this.scrollInterval||(this.scrollInterval=setInterval(()=>{window.scrollBy(0,e)},16))}#E(){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;#I=[];constructor(e){this.#e=e}init(e,t,i){if(void 0!==this.#I[e])throw new Error(`DataTable with id '${e}' has already been initialized.`);this.#I[e]=new s(this.#e,t,i,e)}get(e){return this.#I[e]??null}}(t),e.UI.table.presentation=new class{#I=[];init(e){if(void 0!==this.#I[e])throw new Error(`PresentationTable with input-id '${e}' has already been initialized.`);this.#I[e]=new i(e)}get(e){return this.#I[e]??null}},e.UI.table.ordering=new class{#I=[];init(e){if(void 0!==this.#I[e])throw new Error(`OrderingTable with id '${e}' has already been initialized.`);this.#I[e]=new o(e)}get(e){return this.#I[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.querySelectorAll(".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{t.checked&&e.push(t.value)}),e}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)),this.#b()}#b(){this.#r.querySelectorAll('input[type="number"]').forEach(e=>{e.addEventListener("keydown",e=>{"Enter"===e.key&&e.preventDefault()})})}#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.#R())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.#f(),{once:!0}),this.#i.classList.remove("dragInProgress"),this.#u(),this.#v()}#f(){this.#w.classList.remove("c-table-data__row--drag-settle")}#R(){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.#I(-8):t.clientY>window.innerHeight-100?this.#I(8):this.#E()}touchend(){this.#i.removeChild(this.#m),this.#p.replaceWith(this.#w),this.#w.classList.remove("c-table-data__row--drag-origin"),this.#u(),this.#v()}#v(){let e=10;this.#r.querySelectorAll('input[type="number"]').forEach(t=>{t.value=e,e+=10})}#I(e){this.scrollInterval||(this.scrollInterval=setInterval(()=>{window.scrollBy(0,e)},16))}#E(){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;#_=[];constructor(e,t){this.#e=e,this.#t=t}init(e,t,i){if(void 0!==this.#_[e])throw new Error(`DataTable with id '${e}' has already been initialized.`);this.#_[e]=new s(this.#e,this.#t,t,i,e)}get(e){return this.#_[e]??null}}(t,o),e.UI.table.presentation=new class{#_=[];init(e){if(void 0!==this.#_[e])throw new Error(`PresentationTable with input-id '${e}' has already been initialized.`);this.#_[e]=new i(e)}get(e){return this.#_[e]??null}},e.UI.table.ordering=new class{#_=[];init(e){if(void 0!==this.#_[e])throw new Error(`OrderingTable with id '${e}' has already been initialized.`);this.#_[e]=new a(e)}get(e){return this.#_[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 7b68fb9015dd..4685680c907d 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, }; @@ -164,7 +173,7 @@ export default class DataTable { * @return {void} */ #disableRowSelection(flag) { - this.#table.querySelectorAll('.c-table-data__row-selector').forEach( + this.#table.querySelectorAll('.c-table-data__row-selector')?.forEach( (chk) => { chk.disabled = (flag === true && !chk.checked); }, @@ -207,7 +216,7 @@ export default class DataTable { collectSelectedRowIds() { const ret = []; - this.#table.querySelectorAll('.c-table-data__row-selector').forEach( + this.#table.querySelectorAll('.c-table-data__row-selector')?.forEach( (chk) => { if (chk.checked) { ret.push(chk.value); @@ -249,7 +258,6 @@ export default class DataTable { const k = this.#signalConstants.actionId; const signalData = { options: {} }; signalData.options[k] = selectedAction; - modalClose.click(); this.doAction(signalData, ['ALL_OBJECTS']); } @@ -267,10 +275,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 036db44c7625..7d6dc9ce01c1 100644 --- a/components/ILIAS/UI/src/Component/Prompt/Factory.php +++ b/components/ILIAS/UI/src/Component/Prompt/Factory.php @@ -20,7 +20,7 @@ namespace ILIAS\UI\Component\Prompt; -use ILIAS\Data\URI; +use ILIAS\UI\URLBuilder; interface Factory { @@ -46,11 +46,12 @@ 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. * --- + * @param \ILIAS\UI\URLBuilder $async_url_builder * @return \ILIAS\UI\Component\Prompt\Prompt */ - public function standard(URI $async_url): Prompt; + public function standard(URLBuilder $async_url_builder): Prompt; /** * --- diff --git a/components/ILIAS/UI/src/Component/Prompt/Prompt.php b/components/ILIAS/UI/src/Component/Prompt/Prompt.php index 3acccee53a84..3cf2799aa920 100644 --- a/components/ILIAS/UI/src/Component/Prompt/Prompt.php +++ b/components/ILIAS/UI/src/Component/Prompt/Prompt.php @@ -24,6 +24,8 @@ use ILIAS\UI\Component\JavaScriptBindable; use ILIAS\UI\Component\Signal; use ILIAS\Data\URI; +use ILIAS\UI\URLBuilderToken; +use ILIAS\UI\URLBuilder; interface Prompt extends Component, JavaScriptBindable { @@ -33,12 +35,30 @@ interface Prompt extends Component, JavaScriptBindable public function withTitle(string $title): static; /** - * Get the signal to load and show this Prompt. + * Get a Prompt like this, but alter a parameter of the URLBuilder + * with another value. The parameter is identified using a token. + * + * This alters the Signal returned by {@see self::getShowSignal()}. */ - public function getShowSignal(?URI $uri = null): Signal; + public function withParameter(URLBuilderToken $token, string|array $value): static; /** - * Get the signal to close this Prompt. + * Get the URLBuilder used to load Prompt State's for this Prompt. + * + * This is on the public interface so other UI components can access + * the endpoint of the Prompt as well. + */ + public function getUrlBuilder(): URLBuilder; + + /** + * Get the Signal to show this Prompt and load its Prompt State. + * + * Alter this Signal using {@see self::withParameter()}. + */ + public function getShowSignal(): Signal; + + /** + * Get the Signal to close this Prompt. */ public function getCloseSignal(): Signal; } diff --git a/components/ILIAS/UI/src/Component/Table/Action/Action.php b/components/ILIAS/UI/src/Component/Table/Action/Action.php index 76bac41562d9..7c67eef0ac47 100755 --- a/components/ILIAS/UI/src/Component/Table/Action/Action.php +++ b/components/ILIAS/UI/src/Component/Table/Action/Action.php @@ -20,10 +20,13 @@ namespace ILIAS\UI\Component\Table\Action; -use ILIAS\UI\Component\Signal; +use ILIAS\UI\Component as C; use ILIAS\Data\URI; -interface Action extends \ILIAS\UI\Component\Component +interface Action extends C\Component { - public function withAsync(bool $async = true): self; + /** + * @deprecated construct the Action using a {@see C\Prompt\Prompt} instead. + */ + public function withAsync(bool $is_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..b8fe24b15783 100755 --- a/components/ILIAS/UI/src/Component/Table/Action/Factory.php +++ b/components/ILIAS/UI/src/Component/Table/Action/Factory.php @@ -20,6 +20,7 @@ namespace ILIAS\UI\Component\Table\Action; +use ILIAS\UI\Component as C; use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; @@ -29,47 +30,68 @@ interface Factory * --- * description: * purpose: > - * The Standard Action applies to both a single and multiple records. - * A typical example would be "delete". - * + * The Standard Action applies to both a single and multiple records. + * A typical example would be "delete". + * effect: > + * When a Standard Action created with a Prompt is operated, an asynchronous + * request to the Prompt's endpoint is triggered, carrying the row id(s) + * inside the given token. When created with an URLBuilder, the request + * is performed synchronous instead. * --- + * @param string $label + * @param \ILIAS\UI\Component\Prompt\Prompt|URLBuilder $target + * @param URLBuilderToken $row_id_parameter * @return \ILIAS\UI\Component\Table\Action\Standard */ public function standard( string $label, - URLBuilder $url_builder, - URLBuilderToken $row_id_parameter + C\Prompt\Prompt|URLBuilder $target, + URLBuilderToken $row_id_parameter, ): Standard; /** * --- * description: * purpose: > - * The Single Action applies to a single record only. - * A typical example would be "edit". - * + * The Single Action applies to a single record only. + * A typical example would be "edit". + * effect: > + * When a Single Action created with a Prompt is operated, an asynchronous + * request to the Prompt's endpoint is triggered, carrying the row id(s) + * inside the given token. When created with an URLBuilder, the request + * is performed synchronous instead. * --- + * @param string $label + * @param \ILIAS\UI\Component\Prompt\Prompt|URLBuilder $target + * @param URLBuilderToken $row_id_parameter * @return \ILIAS\UI\Component\Table\Action\Single */ public function single( string $label, - URLBuilder $url_builder, - URLBuilderToken $row_id_parameter + C\Prompt\Prompt|URLBuilder $target, + URLBuilderToken $row_id_parameter, ): Single; /** * --- * description: * purpose: > - * The Multi Action can only be used with more than one record. - * A typical example would be "compare". - * + * The Multi Action can only be used with more than one record. + * A typical example would be "compare". + * effect: > + * When a Multi Action created with a Prompt is operated, an asynchronous + * request to the Prompt's endpoint is triggered, carrying the row id(s) + * inside the given token. When created with an URLBuilder, the request + * is performed synchronous instead. * --- + * @param string $label + * @param \ILIAS\UI\Component\Prompt\Prompt|URLBuilder $target + * @param URLBuilderToken $row_id_parameter * @return \ILIAS\UI\Component\Table\Action\Multi */ public function multi( string $label, - URLBuilder $url_builder, - URLBuilderToken $row_id_parameter + C\Prompt\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 cac13f4109f4..2b669fb79865 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/Factory.php @@ -20,9 +20,9 @@ namespace ILIAS\UI\Implementation\Component\Prompt; -use ILIAS\Data\URI; use ILIAS\UI\Component\Prompt as I; use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; +use ILIAS\UI\URLBuilder; class Factory implements I\Factory { @@ -32,9 +32,9 @@ public function __construct( ) { } - public function standard(URI $async_url): Prompt + public function standard(URLBuilder $async_url_builder): Prompt { - return new Prompt($this->signal_generator, $async_url); + return new Prompt($this->signal_generator, $async_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 5f8e99ecf9de..5055b5834e1d 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/Prompt.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/Prompt.php @@ -26,9 +26,10 @@ use ILIAS\UI\Implementation\Component\ComponentHelper; use ILIAS\UI\Implementation\Component\JavaScriptBindable; use ILIAS\UI\Implementation\Component\SignalGenerator; +use ILIAS\UI\URLBuilderToken; +use ILIAS\UI\URLBuilder; use ILIAS\Data\URI; - class Prompt implements I\Prompt { use ComponentHelper; @@ -40,17 +41,12 @@ class Prompt implements I\Prompt public function __construct( SignalGeneratorInterface $signal_generator, - protected URI $async_url + protected URLBuilder $async_url_builder, ) { $this->show_signal = $signal_generator->create(); $this->close_signal = $signal_generator->create(); } - public function getAsyncUrl(): URI - { - return $this->async_url; - } - public function getTitle(): ?string { return $this->title; @@ -63,11 +59,29 @@ public function withTitle(string $title): static return $clone; } + public function withParameter(URLBuilderToken $token, string|array $value): static + { + $clone = clone $this; + $clone->async_url_builder = $clone->async_url_builder->withParameter($token, $value); + return $clone; + } + + public function getUrlBuilder(): URLBuilder + { + return $this->async_url_builder; + } + + /** + * @param URI|null $uri @deprecated, legacy usages should fail loudly since another endpoint + * would be used, which most likely results in weird behaviour. + */ public function getShowSignal(?URI $uri = null): Signal { - $target = $uri ?? $this->async_url; + if (null !== $uri) { + throw new \LogicException('Prompt target must be changed using withParameter() instead.'); + } $signal = clone $this->show_signal; - $signal->addOption('url', $target->__toString()); + $signal = $signal->withOptions(['url' => (string) $this->getUrlBuilder()->buildURI()]); return $signal; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Prompt/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Prompt/Renderer.php index c9b7bba56fd1..ddfc55c3b643 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/Renderer.php @@ -60,7 +60,6 @@ protected function renderPrompt(Prompt $component, RendererInterface $default_re $show_signal = $component->getShowSignal(); $close_signal = $component->getCloseSignal(); - $url = $component->getAsyncUrl()->__toString(); $component = $component->withAdditionalOnLoadCode( fn($id) => "(function() { const prompt = il.UI.Prompt.createPrompt(document.getElementById('$id')); diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/AbstractTable.php b/components/ILIAS/UI/src/Implementation/Component/Table/AbstractTable.php index ab36db7b522f..dc858feeaf88 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Table/AbstractTable.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/AbstractTable.php @@ -20,60 +20,52 @@ namespace ILIAS\UI\Implementation\Component\Table; -use ILIAS\UI\Component\Table as T; -use ILIAS\UI\Component\Table\Column\Column; -use ILIAS\UI\Component\Table\Action\Action; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; use Psr\Http\Message\ServerRequestInterface; -use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; -use ILIAS\UI\Component\Signal; -use ILIAS\UI\Implementation\Component\JavaScriptBindable; -use ILIAS\UI\Component\JavaScriptBindable as JSBindable; -use ILIAS\UI\Component\Input\ViewControl; -use ILIAS\UI\Component\Input\Container\ViewControl as ViewControlContainer; -use ILIAS\UI\Implementation\Component\Input\ArrayInputData; - -abstract class AbstractTable extends Table implements JSBindable + +abstract class AbstractTable extends Table implements C\JavaScriptBindable { - use JavaScriptBindable; + use I\JavaScriptBindable; /** - * @var array + * @var array */ protected $columns = []; /** - * @var array + * @var array */ protected $actions_single = []; /** - * @var array + * @var array */ protected $actions_multi = []; /** - * @var array + * @var array */ protected $actions_std = []; protected ?string $id = null; - protected Signal $multi_action_signal; - protected Signal $selection_signal; - protected Signal $async_action_signal; + protected C\Signal $multi_action_signal; + protected C\Signal $selection_signal; + protected C\Signal $async_action_signal; protected ?ServerRequestInterface $request = null; /** - * @param array $columns + * @param array $columns */ public function __construct( - SignalGeneratorInterface $signal_generator, - protected ViewControl\Factory $view_control_factory, - protected ViewControlContainer\Factory $view_control_container_factory, - protected \ArrayAccess $storage, + I\SignalGeneratorInterface $signal_generator, + protected I\Input\ViewControl\Factory $view_control_factory, + protected I\Input\Container\ViewControl\Factory $view_control_container_factory, + protected \ILIAS\UI\Storage $storage, string $title, - array $columns + array $columns, ) { - $this->checkArgListElements('columns', $columns, [Column::class]); + $this->checkArgListElements('columns', $columns, [I\Table\Column\Column::class]); if ($columns === []) { throw new \InvalidArgumentException('cannot construct a table without columns.'); } @@ -86,8 +78,8 @@ public function __construct( } /** - * @param array $columns - * @return array + * @param array $columns + * @return array */ private function enumerateColumns(array $columns): array { @@ -100,7 +92,7 @@ private function enumerateColumns(array $columns): array } /** - * @return array + * @return array */ public function getColumns(): array { @@ -112,18 +104,20 @@ public function getColumns(): array */ public function withActions(array $actions): static { - $this->checkArgListElements('actions', $actions, [T\Action\Action::class]); + // check actions against internal interface, will not be + // deletaged to rendering chain and cannot be customised + $this->checkArgListElements('actions', $actions, [I\Table\Action\Action::class]); $clone = clone $this; foreach ($actions as $id => $action) { switch (true) { - case ($action instanceof T\Action\Single): + case ($action instanceof I\Table\Action\Single): $clone->actions_single[$id] = $action; break; - case ($action instanceof T\Action\Multi): + case ($action instanceof I\Table\Action\Multi): $clone->actions_multi[$id] = $action; break; - case ($action instanceof T\Action\Standard): + case ($action instanceof I\Table\Action\Standard): $clone->actions_std[$id] = $action; break; } @@ -142,17 +136,17 @@ public function getRequest(): ?ServerRequestInterface return $this->request; } - public function getMultiActionSignal(): Signal + public function getMultiActionSignal(): C\Signal { return $this->multi_action_signal; } - public function getSelectionSignal(): Signal + public function getSelectionSignal(): C\Signal { return $this->selection_signal; } - public function getAsyncActionSignal(): Signal + public function getAsyncActionSignal(): C\Signal { return $this->async_action_signal; } @@ -168,7 +162,7 @@ public function hasMultiActions(): bool } /** - * @return array + * @return array */ public function getMultiActions(): array { @@ -176,7 +170,7 @@ public function getMultiActions(): array } /** - * @return array + * @return array */ public function getSingleActions(): array { @@ -184,7 +178,7 @@ public function getSingleActions(): array } /** - * @return array + * @return array */ public function getAllActions(): array { @@ -232,15 +226,14 @@ protected function getId(): ?string } protected function applyValuesToViewcontrols( - ViewControlContainer\ViewControl $view_controls, + I\Input\Container\ViewControl\ViewControl $view_controls, ServerRequestInterface $request - ): ViewControlContainer\ViewControl { - $stored_values = new ArrayInputData($this->getStorageData() ?? []); + ): I\Input\Container\ViewControl\ViewControl { + $stored_values = new I\Input\ArrayInputData($this->getStorageData() ?? []); $view_controls = $view_controls ->withStoredInput($stored_values) ->withRequest($request); $this->setStorageData($view_controls->getComponentInternalValues()); return $view_controls; } - } 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..06c88ba57d90 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Action/Action.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Action/Action.php @@ -21,32 +21,23 @@ namespace ILIAS\UI\Implementation\Component\Table\Action; use ILIAS\UI\Implementation\Component\ComponentHelper; -use ILIAS\UI\Component\Table\Action as I; -use ILIAS\UI\Component\Signal; +use ILIAS\UI\Component as C; use ILIAS\Data\URI; use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; -abstract class Action implements I\Action +abstract class Action implements C\Table\Action\Action { use ComponentHelper; - /** - * JS needs to know about the type of an action - * and where to find the options (in case of signal) - * Theses constants are passed to il.UI.table.data.init - */ - public const OPT_ACTIONID = 'actId'; - public const OPT_ROWID = 'rowid'; - protected Signal|URI $target; - protected bool $async = false; + protected bool $is_legacy_async = false; + protected ?C\Signal $legacy_async_signal = null; public function __construct( protected string $label, - protected URLBuilder $url_builder, - protected URLBuilderToken $row_id_parameter + protected C\Prompt\Prompt|URLBuilder $target, + protected URLBuilderToken $row_id_parameter, ) { - $this->target = $url_builder->buildURI(); } public function getLabel(): string @@ -54,55 +45,62 @@ public function getLabel(): string return $this->label; } - public function getTarget(): Signal|URI + public function getTarget(): C\Prompt\Prompt|URLBuilder { return $this->target; } - public function withSignalTarget(Signal $target): self + /** + * @inheritDoc + */ + public function withAsync(bool $is_async = true): self { + if ($this->target instanceof C\Prompt\Prompt) { + throw new \LogicException('Action already migrated to Prompt and is async by default.'); + } $clone = clone $this; - $clone->target = $target; + $clone->is_legacy_async = $is_async; return $clone; } - public function withAsync(bool $async = true): self + /** + * @deprecated use {@see self::getTarget() to determine behaviour} + */ + public function isTargetLegacyAsync(): bool + { + return $this->is_legacy_async; + } + + /** + * @deprecated do not pass information between rendering steps via DTO! + */ + public function withLegacyAsyncSignal(C\Signal $signal): static { $clone = clone $this; - $clone->async = $async; + $clone->legacy_async_signal = $signal; return $clone; } - public function isAsync(): bool + /** + * @deprecated do not pass information between rendering steps via DTO! + */ + public function getLegacyAsyncSignal(): C\Signal { - return $this->async; + return $this->legacy_async_signal; } public function withRowId(string $row_id): self { $clone = clone $this; - $target = $clone->getTarget(); - - 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; + $clone->target = $clone->target->withParameter( + $this->getRowIdParameter(), + [$row_id] + ); return $clone; } - public function getURLBuilderJS(): string - { - return $this->url_builder->renderObject([$this->row_id_parameter]); - } - public function getURLBuilderTokensJS(): string + public function getRowIdParameter(): URLBuilderToken { - return $this->url_builder->renderTokens([$this->row_id_parameter]); + return $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..c52771533a3e 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Action/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Action/Factory.php @@ -22,31 +22,31 @@ use ILIAS\UI\URLBuilder; use ILIAS\UI\URLBuilderToken; -use ILIAS\UI\Component\Table\Action as I; +use ILIAS\UI\Component as C; -class Factory implements I\Factory +class Factory implements C\Table\Action\Factory { public function standard( string $label, - URLBuilder $url_builder, - URLBuilderToken $row_id_parameter + C\Prompt\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, - URLBuilderToken $row_id_parameter + C\Prompt\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, - URLBuilderToken $row_id_parameter + C\Prompt\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/Data.php b/components/ILIAS/UI/src/Implementation/Component/Table/Data.php index e8b6d544366e..7fe7f1d56ba0 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Data.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Data.php @@ -20,23 +20,14 @@ namespace ILIAS\UI\Implementation\Component\Table; -use ILIAS\UI\Component\Table as T; -use ILIAS\UI\Component\Table\Column\Column; -use ILIAS\UI\Component\Table\Action\Action; -use Psr\Http\Message\ServerRequestInterface; -use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; -use ILIAS\UI\Component\Signal; -use ILIAS\UI\Implementation\Component\JavaScriptBindable; -use ILIAS\UI\Component\JavaScriptBindable as JSBindable; -use ILIAS\Data\Factory as DataFactory; -use ILIAS\UI\Component\Input\ViewControl; -use ILIAS\UI\Component\Input\Container\ViewControl as ViewControlContainer; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; use ILIAS\Data\Range; use ILIAS\Data\Order; -class Data extends AbstractTable implements T\Data +class Data extends AbstractTable implements C\Table\Data { - use JavaScriptBindable; + use I\JavaScriptBindable; use TableViewControlFieldSelection; use TableViewControlPagination; use TableViewControlOrdering; @@ -50,21 +41,21 @@ class Data extends AbstractTable implements T\Data protected mixed $filter = null; protected mixed $additional_parameters = null; protected mixed $additional_viewcontrol_data = null; - protected ?ViewControlContainer\ViewControlInput $additional_view_control = null; + protected C\Input\Container\ViewControl\ViewControlInput|null $additional_view_control = null; /** - * @param array $columns + * @param array $columns */ public function __construct( - SignalGeneratorInterface $signal_generator, - ViewControl\Factory $view_control_factory, - ViewControlContainer\Factory $view_control_container_factory, - protected DataFactory $data_factory, + I\SignalGeneratorInterface $signal_generator, + I\Input\ViewControl\Factory $view_control_factory, + I\Input\Container\ViewControl\Factory $view_control_container_factory, + protected \ILIAS\Data\Factory $data_factory, protected DataRowBuilder $data_row_builder, string $title, array $columns, - protected T\DataRetrieval $data_retrieval, - \ArrayAccess $storage + protected C\Table\DataRetrieval $data_retrieval, + \ILIAS\UI\Storage $storage ) { parent::__construct( $signal_generator, @@ -79,7 +70,7 @@ public function __construct( $this->initViewControlpagination(); } - public function getDataRetrieval(): T\DataRetrieval + public function getDataRetrieval(): C\Table\DataRetrieval { return $this->data_retrieval; } @@ -130,7 +121,7 @@ public function getRowBuilder(): DataRowBuilder } /** - * @return array + * @return array{0: self, 1: I\Input\Container\ViewControl\ViewControl} */ public function applyViewControls( mixed $filter_data, @@ -179,7 +170,7 @@ public function applyViewControls( ]; } - protected function getViewControls(?int $total_count = null): ViewControlContainer\ViewControl + protected function getViewControls(?int $total_count = null): I\Input\Container\ViewControl\ViewControl { $view_controls = [ self::VIEWCONTROL_KEY_PAGINATION => $this->getViewControlPagination($total_count), @@ -196,7 +187,7 @@ protected function getViewControls(?int $total_count = null): ViewControlContain } public function withAdditionalViewControl( - ViewControlContainer\ViewControlInput $view_control + C\Input\Container\ViewControl\ViewControlInput $view_control ): self { $clone = clone $this; $clone->additional_view_control = $view_control diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/DataRow.php b/components/ILIAS/UI/src/Implementation/Component/Table/DataRow.php index 04b3c1e5d50c..e8b8414de569 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/DataRow.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/DataRow.php @@ -20,13 +20,12 @@ namespace ILIAS\UI\Implementation\Component\Table; -use ILIAS\UI\Component\Table as T; -use ILIAS\UI\Component\Component; -use ILIAS\UI\Implementation\Component\ComponentHelper; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; -class DataRow implements T\DataRow +class DataRow implements C\Table\DataRow { - use ComponentHelper; + use I\ComponentHelper; /** * @var array @@ -37,8 +36,8 @@ class DataRow implements T\DataRow * The records's key is the column-id of the table. * Its value will be formatted by the respective colum type's format-method. * - * @param array $columns - * @param array $actions + * @param array $columns + * @param array $actions * @param array $record */ public function __construct( @@ -91,7 +90,7 @@ function (string $id): bool { ); } - public function getCellContent(string $col_id): string|Component + public function getCellContent(string $col_id): C\Component|string { if (!array_key_exists($col_id, $this->record)) { return ''; diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/DataRowBuilder.php b/components/ILIAS/UI/src/Implementation/Component/Table/DataRowBuilder.php index a000b6e8197e..671643a711d4 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/DataRowBuilder.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/DataRowBuilder.php @@ -20,16 +20,15 @@ namespace ILIAS\UI\Implementation\Component\Table; -use ILIAS\UI\Component\Table as T; -use ILIAS\UI\Component\Table\Column\Column; -use ILIAS\UI\Component\Table\Action\Action; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; -class DataRowBuilder extends RowBuilder implements T\DataRowBuilder +class DataRowBuilder extends RowBuilder implements C\Table\DataRowBuilder { /** * @param array $record */ - public function buildDataRow(string $id, array $record): T\DataRow + public function buildDataRow(string $id, array $record): DataRow { return new DataRow( $this->row_actions !== [], diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Ordering.php b/components/ILIAS/UI/src/Implementation/Component/Table/Ordering.php index 855007dbd3d5..ebe7d9a18794 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Ordering.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Ordering.php @@ -20,13 +20,11 @@ namespace ILIAS\UI\Implementation\Component\Table; -use ILIAS\UI\Component\Table as T; -use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; use ILIAS\Data\URI; -use ILIAS\UI\Component\Input\ViewControl; -use ILIAS\UI\Component\Input\Container\ViewControl as ViewControlContainer; -class Ordering extends AbstractTable implements T\Ordering +class Ordering extends AbstractTable implements C\Table\Ordering { use TableViewControlFieldSelection; @@ -36,15 +34,15 @@ class Ordering extends AbstractTable implements T\Ordering protected bool $ordering_disabled = false; public function __construct( - SignalGeneratorInterface $signal_generator, - ViewControl\Factory $view_control_factory, - ViewControlContainer\Factory $view_control_container_factory, + I\SignalGeneratorInterface $signal_generator, + I\Input\ViewControl\Factory $view_control_factory, + I\Input\Container\ViewControl\Factory $view_control_container_factory, protected OrderingRowBuilder $row_builder, string $title, array $columns, - protected T\OrderingRetrieval $binding, + protected C\Table\OrderingRetrieval $binding, protected URI $target_url, - \ArrayAccess $storage + \ILIAS\UI\Storage $storage ) { parent::__construct( $signal_generator, @@ -65,7 +63,7 @@ public function getRowBuilder(): OrderingRowBuilder ->withVisibleColumns($this->getVisibleColumns()); } - public function getDataBinding(): T\OrderingRetrieval + public function getDataBinding(): C\Table\OrderingRetrieval { return $this->binding; } @@ -90,7 +88,7 @@ public function getTargetURL(): ?URI public function getData(): array { if (!$request = $this->getRequest()) { - return null; + return []; } $ordered = $request->getParsedBody(); asort($ordered, SORT_NUMERIC); @@ -98,7 +96,7 @@ public function getData(): array } /** - * @return array + * @return array */ public function applyViewControls(): array { @@ -118,7 +116,7 @@ public function applyViewControls(): array ]; } - protected function getViewControls(): ViewControlContainer\ViewControl + protected function getViewControls(): I\Input\Container\ViewControl\ViewControl { $view_controls = [ self::VIEWCONTROL_KEY_FIELDSELECTION => $this->getViewControlFieldSelection(), diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/OrderingRowBuilder.php b/components/ILIAS/UI/src/Implementation/Component/Table/OrderingRowBuilder.php index 7ccaece6bd09..290dbe1cd734 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Table/OrderingRowBuilder.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/OrderingRowBuilder.php @@ -20,14 +20,15 @@ namespace ILIAS\UI\Implementation\Component\Table; -use ILIAS\UI\Component\Table as T; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; -class OrderingRowBuilder extends RowBuilder implements T\OrderingRowBuilder +class OrderingRowBuilder extends RowBuilder implements C\Table\OrderingRowBuilder { /** * @param array $record */ - public function buildOrderingRow(string $id, array $record): T\OrderingRow + public function buildOrderingRow(string $id, array $record): OrderingRow { return new OrderingRow( $this->row_actions !== [], diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php index f123cb08a2c2..fb916661db2c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php @@ -22,49 +22,47 @@ use ILIAS\UI\Implementation\Render\AbstractComponentRenderer; use ILIAS\UI\Implementation\Render\ResourceRegistry; -use ILIAS\UI\Renderer as RendererInterface; -use ILIAS\UI\Component; use ILIAS\UI\Implementation\Render\Template; +use ILIAS\UI\Renderer as RendererInterface; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; use ILIAS\Data\Order; use ILIAS\Data\URI; -use ILIAS\UI\Implementation\Component\Table\Action\Action; -use ILIAS\UI\Implementation\Component\Input\ViewControl\Pagination; -use ILIAS\UI\Implementation\Component\Input\NameSource; +use ILIAS\UI\URLBuilder; class Renderer extends AbstractComponentRenderer { + protected const string KEY_ACTION_ID = 'action_id'; + protected const string KEY_ROW_ID = 'row_id'; + /** * @inheritdoc */ - public function render(Component\Component $component, RendererInterface $default_renderer): string + public function render(C\Component $component, RendererInterface $default_renderer): string { - if ($component instanceof Component\Table\Presentation) { + if ($component instanceof Presentation) { return $this->renderPresentationTable($component, $default_renderer); } - if ($component instanceof Component\Table\PresentationRow) { + if ($component instanceof PresentationRow) { return $this->renderPresentationRow($component, $default_renderer); } - if ($component instanceof Component\Table\Data) { + if ($component instanceof Data) { return $this->renderDataTable($component, $default_renderer); } - if ($component instanceof Component\Table\DataRow - && !$component instanceof Component\Table\OrderingRow - ) { + if ($component instanceof DataRow && !$component instanceof OrderingRow) { return $this->renderDataRow($component, $default_renderer); } - if ($component instanceof Component\Table\Ordering) { + if ($component instanceof Ordering) { return $this->renderOrderingTable($component, $default_renderer); } - if ($component instanceof Component\Table\OrderingRow) { + if ($component instanceof OrderingRow) { return $this->renderOrderingRow($component, $default_renderer); } $this->cannotHandleComponent($component); } - protected function renderPresentationTable( - Component\Table\Presentation $component, - RendererInterface $default_renderer - ): string { + protected function renderPresentationTable(Presentation $component, RendererInterface $default_renderer): string + { $tpl = $this->getTemplate("tpl.presentationtable.html", true, true); $tpl->setVariable("TITLE", $component->getTitle()); $expcollapsebtns = []; @@ -72,13 +70,13 @@ protected function renderPresentationTable( $sig_ta_expand = clone $sig_ta; $sig_ta_expand->addOption('expand', true); $expcollapsebtns[] = $this->getUIFactory()->button() - ->standard($this->txt('presentation_table_expand'), '') - ->withOnClick($sig_ta_expand); + ->standard($this->txt('presentation_table_expand'), '') + ->withOnClick($sig_ta_expand); $sig_ta_collapse = clone $sig_ta; $sig_ta_collapse->addOption('expand', false); $expcollapsebtns[] = $this->getUIFactory()->button() - ->standard($this->txt('presentation_table_collapse'), '') - ->withOnClick($sig_ta_collapse); + ->standard($this->txt('presentation_table_collapse'), '') + ->withOnClick($sig_ta_collapse); $component = $component->withAdditionalOnLoadCode( static fn($id) => " il.UI.table.presentation.init('{$id}'); @@ -122,10 +120,8 @@ protected function renderPresentationTable( return $tpl->get(); } - protected function renderPresentationRow( - Component\Table\PresentationRow $component, - RendererInterface $default_renderer - ): string { + protected function renderPresentationRow(PresentationRow $component, RendererInterface $default_renderer): string + { $f = $this->getUIFactory(); $tpl = $this->getTemplate("tpl.presentationrow.html", true, true); @@ -136,11 +132,11 @@ protected function renderPresentationRow( $id = $this->bindJavaScript($component); $expander = $f->button()->shy('', '')->withSymbol($f->symbol()->glyph()->expand()) - ->withOnClick($sig_show); + ->withOnClick($sig_show); $collapser = $f->button()->shy('', '')->withSymbol($f->symbol()->glyph()->collapse()) - ->withOnClick($sig_hide); + ->withOnClick($sig_hide); $shy_expander = $f->button()->shy($this->txt("presentation_table_more"), "") - ->withOnClick($sig_show); + ->withOnClick($sig_show); $tpl->setVariable("ID", $id); $tpl->setVariable("EXPANDER", $default_renderer->render($expander)); @@ -204,10 +200,11 @@ protected function renderPresentationRow( return $tpl->get(); } - public function renderDataTable(Component\Table\Data $component, RendererInterface $default_renderer): string + public function renderDataTable(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->getFilter(), @@ -242,6 +239,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. @@ -269,7 +267,7 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa $sortation_view_control = array_filter( $view_controls->getInputs(), - static fn($i): bool => $i instanceof Component\Input\ViewControl\Sortation + static fn($i): bool => $i instanceof C\Input\ViewControl\Sortation ); if ($sortation_view_control) { $sortation_signal = array_shift($sortation_view_control)->getInternalSignal(); @@ -283,9 +281,9 @@ public function renderDataTable(Component\Table\Data $component, RendererInterfa protected function renderTableHeader( RendererInterface $default_renderer, - Component\Table\Data $component, + Data $component, Template $tpl, - ?Component\Signal $sortation_signal, + ?C\Signal $sortation_signal, int $compensate_col_index, ): void { $order = $component->getOrder(); @@ -321,12 +319,12 @@ protected function renderTableHeader( if ($col_id === $sort_col) { $sortation_glyph = $this->getUIFactory()->button()->shy('', '') - ->withSymbol( - $sort_direction === Order::ASC ? - $glyph_factory->sortAscending() : - $glyph_factory->sortDescending() - ) - ->withOnClick($sort_signal); + ->withSymbol( + $sort_direction === Order::ASC ? + $glyph_factory->sortAscending() : + $glyph_factory->sortDescending() + ) + ->withOnClick($sort_signal); $tpl->setVariable('COL_SORTATION', $sortation); $tpl->setVariable('COL_SORTATION_GLYPH', $default_renderer->render($sortation_glyph)); } @@ -340,7 +338,7 @@ protected function renderTableHeader( protected function renderActionsHeader( RendererInterface $default_renderer, - Component\Table\Table $component, + AbstractTable $component, Template $tpl, int $compensate_col_count, ): void { @@ -356,17 +354,17 @@ protected function renderActionsHeader( $sig_all = clone $signal; $sig_all->addOption('select', true); $select_all = $f->button()->shy('', '') - ->withSymbol($glyph_factory->add()) - ->withOnClick($sig_all); + ->withSymbol($glyph_factory->add()) + ->withOnClick($sig_all); $signal->addOption('select', false); $select_none = $f->button()->shy('', '') - ->withSymbol($glyph_factory->close()) - ->withOnClick($signal); + ->withSymbol($glyph_factory->close()) + ->withOnClick($signal); $tpl->setVariable('SELECTION_CONTROL_SELECT', $default_renderer->render($select_all)); $tpl->setVariable('SELECTION_CONTROL_DESELECT', $default_renderer->render($select_none)); } - if ($component instanceof Component\Table\Ordering) { + if ($component instanceof Ordering) { if (!$component->isOrderingDisabled() && !$component->hasMultiActions()) { $tpl->touchBlock('header_rowselection_cell'); } @@ -377,7 +375,7 @@ protected function renderActionsHeader( * Renders a full-width cell with a single message within, indication there is no * data to display. This is achieved using a
colspan attribute. */ - protected function renderFullWidthDataCell(Component\Table\Data $component, Template $tpl, string $content): void + protected function renderFullWidthDataCell(Data $component, Template $tpl, string $content): void { $cell_tpl = $this->getTemplate('tpl.datacell.html', true, true); $cell_tpl->setCurrentBlock('cell'); @@ -393,39 +391,61 @@ 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{0: AbstractTable, 1: C\Prompt\Prompt[]} + */ + protected function registerActions(AbstractTable $component): array + { if ($component->hasMultiActions()) { $component = $component->withAdditionalOnLoadCode( static fn($id): string => "il.UI.table.data.get('{$id}').selectAll(false);" ); } + $prompts = []; $actions = []; foreach ($component->getAllActions() as $action_id => $action) { - $component = $component->withAdditionalOnLoadCode($this->getActionRegistration((string) $action_id, $action)); - if ($action->isAsync()) { + $target = $action->getTarget(); + $signal = null; + if ($target instanceof C\Prompt\Prompt) { + $url_builder = $target->getUrlBuilder(); + $signal = $target->getShowSignal(); + $prompts[] = $target; + } else { + $url_builder = $target; + } + + if ($action->isTargetLegacyAsync()) { $signal = clone $component->getAsyncActionSignal(); - $signal->addOption(Action::OPT_ACTIONID, $action_id); - $action = $action->withSignalTarget($signal); + $signal = $signal->withOptions([self::KEY_ACTION_ID => $action_id]); + $action = $action->withLegacyAsyncSignal($signal); } + + $url_builder_js = $url_builder->renderObject([$action->getRowIdParameter()]); + $tokens_js = $url_builder->renderTokens([$action->getRowIdParameter()]); + $is_legacy_async_js = $action->isTargetLegacyAsync() ? 'true' : 'false'; + + $component = $component->withAdditionalOnLoadCode( + static fn($id): string => + "il.UI.table.data.get('{$id}').registerAction('{$action_id}', {$is_legacy_async_js}, '{$signal}', {$url_builder_js}, {$tokens_js});" + ); + $actions[$action_id] = $action; } $component = $component->withActions($actions); + $opt_action_id = self::KEY_ACTION_ID; + $opt_row_id = self::KEY_ROW_ID; $component = $component ->withAdditionalOnLoadCode( - static fn($id): string => - "il.UI.table.data.init('{$id}','{$opt_action_id}','{$opt_row_id}');" + static fn($id): string => "il.UI.table.data.init('{$id}','{$opt_action_id}','{$opt_row_id}');" ) ->withAdditionalOnLoadCode($this->getAsyncActionHandler($component->getAsyncActionSignal())) ->withAdditionalOnLoadCode($this->getMultiActionHandler($component->getMultiActionSignal())) ->withAdditionalOnLoadCode($this->getSelectionHandler($component->getSelectionSignal())); - return $component; + return [$component, $prompts]; } protected function appendTableRows( @@ -444,15 +464,18 @@ protected function appendTableRows( } } - protected function renderEmptyPresentationRow(Template $tpl, RendererInterface $default_renderer, string $content): void - { + protected function renderEmptyPresentationRow( + Template $tpl, + RendererInterface $default_renderer, + string $content + ): void { $row_tpl = $this->getTemplate('tpl.presentationrow_empty.html', true, true); $row_tpl->setVariable('CONTENT', $content); $tpl->setVariable('ROW', $row_tpl->get()); } /** - * @param array $actions + * @param array $actions */ protected function buildMultiActionsAllObjectsModal( array $actions, @@ -471,12 +494,14 @@ protected function buildMultiActionsAllObjectsModal( "" )->withRequired(true); $submit = $f->button()->primary($this->txt('datatable_multiactionmodal_apply'), '') - ->withOnLoadCode( - static fn($id): string => "$('#{$id}').click(function() { il.UI.table.data.get('{$table_id}').doActionForAll(this); return false; });" - ); + ->withOnLoadCode( + static fn( + $id + ): string => "$('#{$id}').click(function() { il.UI.table.data.get('{$table_id}').doActionForAll(this); return false; });" + ); $modal = $f->modal() - ->roundtrip($this->txt('datatable_multiactionmodal_title'), [$msg, $select]) - ->withActionButtons([$submit]); + ->roundtrip($this->txt('datatable_multiactionmodal_title'), [$msg, $select]) + ->withActionButtons([$submit]); return $modal; } @@ -487,13 +512,13 @@ protected function getUrlTooLongWarning(): \ILIAS\UI\Component\MessageBox\Messag } /** - * @param array $actions + * @param array $actions */ protected function buildMultiActionsDropdown( array $actions, - Component\Signal $action_signal, - Component\Signal $modal_signal, - ): ?\ILIAS\UI\Component\Dropdown\Dropdown { + C\Signal $action_signal, + C\Signal $modal_signal + ): ?I\Dropdown\Dropdown { if ($actions === []) { return null; } @@ -503,17 +528,19 @@ protected function buildMultiActionsDropdown( $all_obj_buttons = []; foreach ($actions as $action_id => $act) { $signal = clone $action_signal; - $signal->addOption(Action::OPT_ACTIONID, $action_id); + $signal->addOption(self::KEY_ACTION_ID, $action_id); $buttons[] = $f->button()->shy($act->getLabel(), $signal); } $buttons[] = $f->divider()->horizontal(); - $buttons[] = $f->button()->shy($this->txt('datatable_multiactionmodal_listentry'), '#')->withOnClick($modal_signal); + $buttons[] = $f->button()->shy($this->txt('datatable_multiactionmodal_listentry'), '#')->withOnClick( + $modal_signal + ); return $f->dropdown()->standard($buttons)->withLabel($this->txt('datatable_multiaction_label')); } - protected function getAsyncActionHandler(Component\Signal $action_signal): \Closure + protected function getAsyncActionHandler(C\Signal $action_signal): \Closure { return static function ($id) use ($action_signal): string { return " @@ -523,7 +550,8 @@ protected function getAsyncActionHandler(Component\Signal $action_signal): \Clos });"; }; } - protected function getMultiActionHandler(Component\Signal $action_signal): \Closure + + protected function getMultiActionHandler(C\Signal $action_signal): \Closure { return static function ($id) use ($action_signal): string { return " @@ -534,7 +562,7 @@ protected function getMultiActionHandler(Component\Signal $action_signal): \Clos }; } - protected function getSelectionHandler(Component\Signal $selection_signal): \Closure + protected function getSelectionHandler(C\Signal $selection_signal): \Closure { return static function ($id) use ($selection_signal): string { return " @@ -546,45 +574,29 @@ 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 + public function renderDataRow(DataRow $component, RendererInterface $default_renderer): string { $cell_tpl = $this->getTemplate("tpl.datacell.html", true, true); $this->fillCells($component, $cell_tpl, $default_renderer); - return $cell_tpl->get(); } - public function renderOrderingRow(Component\Table\OrderingRow $component, RendererInterface $default_renderer): string + public function renderOrderingRow(OrderingRow $component, RendererInterface $default_renderer): string { $cell_tpl = $this->getTemplate("tpl.orderingcell.html", true, true); $this->fillCells($component, $cell_tpl, $default_renderer); - if ($component->isOrderingDisabled()) { return $cell_tpl->get(); } - $namesource = new class () implements NameSource { + $namesource = new class () implements I\Input\NameSource { public function getNewName(): string { return ''; } + public function getNewDedicatedName(string $dedicated_name): string { return $dedicated_name; @@ -593,20 +605,16 @@ public function getNewDedicatedName(string $dedicated_name): string $numeric_label = $this->txt("ui_table_order"); $input = $this->getUIFactory()->input()->field()->numeric($numeric_label) - ->withDedicatedName($component->getId()) - ->withNameFrom($namesource) - ->withValue($component->getPosition() * 10); + ->withDedicatedName($component->getId()) + ->withNameFrom($namesource) + ->withValue($component->getPosition() * 10); $cell_tpl->setVariable('ORDER_INPUT', $default_renderer->render($input)); return $cell_tpl->get(); } - - protected function fillCells( - Component\Table\DataRow $row, - Template $cell_tpl, - RendererInterface $default_renderer - ) { + protected function fillCells(DataRow $row, Template $cell_tpl, RendererInterface $default_renderer): void + { $cols = $row->getColumns(); foreach ($cols as $col_id => $column) { if ($column->isHighlighted()) { @@ -615,7 +623,7 @@ protected function fillCells( $cell_tpl->setCurrentBlock('cell'); $cell_tpl->setVariable('COL_TYPE', strtolower($column->getType())); $cell_content = $row->getCellContent($col_id); - if ($cell_content instanceof Component\Component) { + if ($cell_content instanceof C\Component) { $cell_content = $default_renderer->render($cell_content); } $cell_tpl->setVariable('CELL_CONTENT', $cell_content); @@ -634,7 +642,7 @@ protected function fillCells( $cell_tpl->setVariable('ACTION_CONTENT', $default_renderer->render($row_actions_dropdown)); } - if ($row instanceof Component\Table\OrderingRow) { + if ($row instanceof OrderingRow) { if (!$row->isOrderingDisabled()) { $drag_handle = $this->getUIFactory()->symbol()->glyph()->dragHandle(); $drag_handle = $default_renderer->render($drag_handle); @@ -644,29 +652,32 @@ protected function fillCells( } /** - * @param array $actions + * @param array $actions */ - protected function getSingleActionsForRow(string $row_id, array $actions): \ILIAS\UI\Component\Dropdown\Standard + protected function getSingleActionsForRow(string $row_id, array $actions): I\Dropdown\Dropdown { $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 ($target instanceof C\Prompt\Prompt) { + $btn_action = $target->getShowSignal(); + } elseif ($action->isTargetLegacyAsync()) { + $btn_action = $action->getLegacyAsyncSignal(); + } else { + $btn_action = (string) $target->buildURI(); } - $buttons[] = $f->button()->shy($act->getLabel(), $target); + $buttons[] = $f->button()->shy($action->getLabel(), $btn_action); } return $f->dropdown()->standard($buttons); } - - public function renderOrderingTable(Component\Table\Ordering $component, RendererInterface $default_renderer): string + public function renderOrderingTable(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( @@ -674,7 +685,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}');" @@ -706,12 +716,13 @@ public function renderOrderingTable(Component\Table\Ordering $component, Rendere if (!$component->isOrderingDisabled()) { $submit = $this->getUIFactory()->button()->standard($this->txt('sorting_save'), "") - ->withOnLoadCode(static fn($id) => "document.getElementById('$id').addEventListener('click', - function() {document.querySelector('#$tableid form.c-table-ordering__form').submit();return false;});"); + ->withOnLoadCode( + static fn($id) => "document.getElementById('$id').addEventListener('click', + function() {document.querySelector('#$tableid form.c-table-ordering__form').submit();return false;});" + ); $tpl->setVariable('FORM_BUTTONS', $default_renderer->render($submit)); $tpl->setVariable('POS_INPUT_TITLE', $this->txt('table_posinput_col_title')); - } $tpl->setVariable('ID', $tableid); @@ -719,6 +730,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) { @@ -773,17 +785,18 @@ public function registerResources(ResourceRegistry $registry): void $registry->register('assets/js/modal.min.js'); } - protected function registerSignals(Component\Table\PresentationRow $component): Component\JavaScriptBindable + protected function registerSignals(PresentationRow $component): C\JavaScriptBindable { $show = $component->getShowSignal(); $close = $component->getCloseSignal(); $toggle = $component->getToggleSignal(); $table_id = $component->getTableId(); return $component->withAdditionalOnLoadCode( - static fn($id): string => - "$(document).on('$show', function() { il.UI.table.presentation.get('$table_id').expandRow('$id'); return false; });" . - "$(document).on('$close', function() { il.UI.table.presentation.get('$table_id').collapseRow('$id'); return false; });" . - "$(document).on('$toggle', function() { il.UI.table.presentation.get('$table_id').toggleRow('$id'); return false; });" + static fn( + $id + ): string => "$(document).on('$show', function() { il.UI.table.presentation.get('$table_id').expandRow('$id'); return false; });" . + "$(document).on('$close', function() { il.UI.table.presentation.get('$table_id').collapseRow('$id'); return false; });" . + "$(document).on('$toggle', function() { il.UI.table.presentation.get('$table_id').toggleRow('$id'); return false; });" ); } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/RowBuilder.php b/components/ILIAS/UI/src/Implementation/Component/Table/RowBuilder.php index 234bfca1b390..87d4015fc21c 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Table/RowBuilder.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/RowBuilder.php @@ -20,26 +20,23 @@ namespace ILIAS\UI\Implementation\Component\Table; -use ILIAS\UI\Component\Table as T; -use ILIAS\UI\Component\Table\Column\Column; -use ILIAS\UI\Component\Table\Action\Action; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; abstract class RowBuilder { protected bool $table_has_multiactions = false; /** - * @var array + * @var array */ protected array $row_actions = []; /** - * @var array + * @var array */ protected array $columns = []; - //abstract function buildRow(string $id, array $record); - public function withMultiActionsPresent(bool $flag): self { $clone = clone $this; @@ -48,7 +45,7 @@ public function withMultiActionsPresent(bool $flag): self } /** - * @param array $row_actions + * @param array $row_actions */ public function withSingleActions(array $row_actions): self { @@ -57,7 +54,7 @@ public function withSingleActions(array $row_actions): self return $clone; } /** - * @param array $columns + * @param array $columns */ public function withVisibleColumns(array $columns): self { diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/TableViewControlFieldSelection.php b/components/ILIAS/UI/src/Implementation/Component/Table/TableViewControlFieldSelection.php index fb782d46fe23..b0f1521968ba 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Table/TableViewControlFieldSelection.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/TableViewControlFieldSelection.php @@ -20,8 +20,8 @@ namespace ILIAS\UI\Implementation\Component\Table; -use ILIAS\UI\Component\Input\ViewControl\FieldSelection; -use ILIAS\UI\Component\Table\Column\Column; +use ILIAS\UI\Implementation\Component as I; +use ILIAS\UI\Component as C; trait TableViewControlFieldSelection { @@ -31,14 +31,14 @@ trait TableViewControlFieldSelection protected ?array $selected_optional_column_ids = null; /** - * @param array $columns + * @param array $columns */ protected function initViewControlFieldSelection(array $columns): void { $this->selected_optional_column_ids = $this->filterVisibleColumnIds($columns); } - protected function getViewControlFieldSelection(): ?FieldSelection + protected function getViewControlFieldSelection(): ?I\Input\ViewControl\FieldSelection { $optional_cols = $this->getOptionalColumns(); if ($optional_cols === []) { @@ -54,7 +54,7 @@ protected function getViewControlFieldSelection(): ?FieldSelection } /** - * @param array $columns + * @param array $columns * @return array */ protected function filterVisibleColumnIds(array $columns): array @@ -89,7 +89,7 @@ public function getSelectedOptionalColumns(): array } /** - * @return array + * @return array */ protected function getOptionalColumns(): array { @@ -100,7 +100,7 @@ protected function getOptionalColumns(): array } /** - * @return array + * @return array */ protected function getInitiallyVisibleColumns(): array { @@ -111,14 +111,14 @@ protected function getInitiallyVisibleColumns(): array } /** - * @return array + * @return array */ public function getVisibleColumns(): array { $visible_optional_columns = $this->getSelectedOptionalColumns(); return array_filter( $this->getColumns(), - fn(Column $col, string $col_id): bool => !$col->isOptional() || in_array($col_id, $visible_optional_columns, true), + fn(I\Table\Column\Column $col, string $col_id): bool => !$col->isOptional() || in_array($col_id, $visible_optional_columns, true), ARRAY_FILTER_USE_BOTH ); } diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/TableViewControlOrdering.php b/components/ILIAS/UI/src/Implementation/Component/Table/TableViewControlOrdering.php index 52df155e3d67..6a0536c7fd20 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Table/TableViewControlOrdering.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/TableViewControlOrdering.php @@ -47,7 +47,7 @@ private function initialOrder(): string return array_key_first($sortable_visible_cols); } - protected function getViewControlOrdering(int|null $total_count): Sortation|ViewControl\Group + protected function getViewControlOrdering(int|null $total_count): ViewControl\Sortation|ViewControl\Group { $sortable_visible_cols = array_filter( $this->getVisibleColumns(), diff --git a/components/ILIAS/UI/src/examples/Prompt/Standard/base.php b/components/ILIAS/UI/src/examples/Prompt/Standard/base.php index 48533b5b2439..6cd53c6e5371 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/base.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/base.php @@ -61,8 +61,7 @@ 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/file_upload.php b/components/ILIAS/UI/src/examples/Prompt/Standard/file_upload.php index ea651e851c69..c378302a60df 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/file_upload.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/file_upload.php @@ -104,7 +104,7 @@ function file_upload(): string } // create a Standard Prompt that requests the simulated GET endpoint - $prompt = $factory->prompt()->standard($form_get_url->buildURI())->withTitle('Upload file'); + $prompt = $factory->prompt()->standard($form_get_url)->withTitle('Upload file'); // create a Button that triggers the Standard Prompt $button = $factory->button()->primary('Upload file', $prompt->getShowSignal()); 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/multi_entity_confirmation.php b/components/ILIAS/UI/src/examples/Prompt/Standard/multi_entity_confirmation.php index 173df99faba4..5a84b59ff38e 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/multi_entity_confirmation.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/multi_entity_confirmation.php @@ -151,7 +151,7 @@ function multi_entity_confirmation(): string } // create a Standard Prompt that requests the simulated GET endpoint - $prompt = $factory->prompt()->standard($confirmation_get_url->buildURI())->withTitle('Confirm deletion'); + $prompt = $factory->prompt()->standard($confirmation_get_url)->withTitle('Confirm deletion'); // create a Button that triggers the Standard Prompt $button = $factory->button()->primary('Delete entities', $prompt->getShowSignal()); diff --git a/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php b/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php index 8d802625d072..ce22f6c1c18c 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/parameters.php @@ -57,8 +57,7 @@ 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(); @@ -117,11 +116,11 @@ function parameters() $show_button = $factory->button()->standard('Show Simple Prompt', $prompt->getShowSignal()); $close_button = $factory->button()->standard('close Simple Prompt', $prompt->getCloseSignal()); - $uri = $url_builder->withParameter($amount_token, "8")->buildURI(); - $show_button8 = $factory->button()->standard('Show Prompt with Parameter', $prompt->getShowSignal($uri)); + $prompt = $prompt->withParameter($amount_token, "8"); + $show_button8 = $factory->button()->standard('Show Prompt with Parameter', $prompt->getShowSignal()); - $uri = $url_builder->withParameter($amount_token, "78")->buildURI(); - $show_button78 = $factory->button()->standard('Show Prompt with a lot of Items', $prompt->getShowSignal($uri)); + $prompt = $prompt->withParameter($amount_token, "78"); + $show_button78 = $factory->button()->standard('Show Prompt with a lot of Items', $prompt->getShowSignal()); return $renderer->render([ $prompt, diff --git a/components/ILIAS/UI/src/examples/Prompt/Standard/single_entity_confirmation.php b/components/ILIAS/UI/src/examples/Prompt/Standard/single_entity_confirmation.php index 84b76e9eb7d9..b0d2da65b14d 100644 --- a/components/ILIAS/UI/src/examples/Prompt/Standard/single_entity_confirmation.php +++ b/components/ILIAS/UI/src/examples/Prompt/Standard/single_entity_confirmation.php @@ -165,7 +165,7 @@ function single_entity_confirmation(): string } // create a Standard Prompt that requests the simulated GET endpoint - $prompt = $factory->prompt()->standard($confirmation_get_url->buildURI())->withTitle('Confirm deletion'); + $prompt = $factory->prompt()->standard($confirmation_get_url)->withTitle('Confirm deletion'); // create a Button that triggers the Standard Prompt $button = $factory->button()->primary('Delete entity', $prompt->getShowSignal()); 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..a65165d0f102 100755 --- a/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html +++ b/components/ILIAS/UI/src/templates/default/Table/tpl.datatable.html @@ -9,46 +9,46 @@

    {TITLE}

    - + - - - + + + - - - + + + - - - - + + + + - - - {CELLS} - - - - - - - + + + {CELLS} + + + + + + +
    -
    {SELECTION_CONTROL_SELECT}
    -
    {SELECTION_CONTROL_DESELECT}
    -
    +
    {SELECTION_CONTROL_SELECT}
    +
    {SELECTION_CONTROL_DESELECT}
    +
    aria-sort="{COL_SORTATION}"> -
    - - {COL_SORTATION_GLYPH} - - {COL_TITLE} -
    -
    aria-sort="{COL_SORTATION}"> +
    + + {COL_SORTATION_GLYPH} + + {COL_TITLE} +
    +
    {COL_TITLE_ACTION}
    {COL_TITLE_ACTION}
    -
    {MULTI_ACTION_TRIGGERER}
    - {MULTI_ACTION_ALL_MODAL} -
    +
    {MULTI_ACTION_TRIGGERER}
    + {MULTI_ACTION_ALL_MODAL} +
    @@ -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 f79fdfcd63ba..24f3fe2098f5 100644 --- a/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html +++ b/components/ILIAS/UI/src/templates/default/Table/tpl.orderingtable.html @@ -11,61 +11,61 @@

    {TITLE}

    - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - {CELLS} - - - - - - + {CELLS} + + + + + + - - + + + + @@ -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 index d20ca7044e29..e1366a13893e 100644 --- a/components/ILIAS/UI/tests/Component/Prompt/PromptTest.php +++ b/components/ILIAS/UI/tests/Component/Prompt/PromptTest.php @@ -27,16 +27,18 @@ class PromptTest extends \ILIAS_UI_TestBase { - protected string $uri_stub_value; - protected \ILIAS\Data\URI & Stub $uri_stub; + protected string $url_builder_stub_value; + protected \ILIAS\UI\URLBuilder & Stub $url_builder_stub; protected string $message_box_stub_html; protected I\MessageBox\MessageBox & Stub $message_box_stub; protected function setUp(): void { - $this->uri_stub_value = sha1(\ILIAS\Data\URI::class); - $this->uri_stub = $this->createStub(\ILIAS\Data\URI::class); - $this->uri_stub->method('__toString')->willReturn($this->uri_stub_value); + $this->url_builder_stub_value = sha1(\ILIAS\UI\URLBuilder::class); + $uri_stub = $this->createStub(\ILIAS\Data\URI::class); + $uri_stub->method('__toString')->willReturn($this->url_builder_stub_value); + $this->url_builder_stub = $this->createStub(\ILIAS\UI\URLBuilder::class); + $this->url_builder_stub->method('buildURI')->willReturn($uri_stub); $this->message_box_stub_html = sha1(I\MessageBox\MessageBox::class); $this->message_box_stub = $this->createStub(I\MessageBox\MessageBox::class); @@ -48,7 +50,7 @@ protected function setUp(): void public function getUIFactory(): \NoUIFactory { $real_prompt_factory = new I\Prompt\Factory( - $this->createStub(I\SignalGeneratorInterface::class), + new I\SignalGenerator(), $this->createStub(I\Prompt\State\Factory::class), ); @@ -74,7 +76,7 @@ public function prompt(): I\Prompt\Factory public function testRender(): void { - $prompt = $this->getUIFactory()->prompt()->standard($this->uri_stub); + $prompt = $this->getUIFactory()->prompt()->standard($this->url_builder_stub); $renderer = $this->getDefaultRenderer(null, [$this->message_box_stub]); @@ -110,7 +112,7 @@ public function testRender(): void public function testRenderWithTitle(): void { - $prompt = $this->getUIFactory()->prompt()->standard($this->uri_stub); + $prompt = $this->getUIFactory()->prompt()->standard($this->url_builder_stub); $title = sha1('Hello World!'); $prompt = $prompt->withTitle($title); @@ -125,4 +127,47 @@ public function testRenderWithTitle(): void $this->assertStringContainsString($this->brutallyTrimHTML($expected), $this->brutallyTrimHTML($actual)); } + + public function testShowSignal(): void + { + $prompt = $this->getUIFactory()->prompt()->standard($this->url_builder_stub); + $signal = $prompt->getShowSignal(); + $this->assertSame(['url' => $this->url_builder_stub_value], $signal->getOptions()); + } + + public function testShowSignalAfterWithParameter(): void + { + $test_value = 'some_new_parameter_value'; + $url_builder_token_stub = $this->createStub(\ILIAS\UI\URLBuilderToken::class); + + $initial_uri_stub = $this->createStub(\ILIAS\Data\URI::class); + $initial_uri_stub->method('__toString')->willReturn('initial_uri'); + $altered_uri_stub = $this->createStub(\ILIAS\Data\URI::class); + $altered_uri_stub->method('__toString')->willReturn('altered_uri'); + + $url_builder_mock = $this->createMock(\ILIAS\UI\URLBuilder::class); + $url_builder_mock->method('buildURI')->willReturn($initial_uri_stub, $altered_uri_stub); + + $prompt = $this->getUIFactory()->prompt()->standard($url_builder_mock); + + $initial_signal = $prompt->getShowSignal(); + $this->assertSame(['url' => $initial_uri_stub->__toString()], $initial_signal->getOptions()); + + $url_builder_mock + ->expects($this->once()) + ->method('withParameter') + ->with($url_builder_token_stub, $test_value) + ->willReturnSelf(); + $prompt = $prompt->withParameter($url_builder_token_stub, $test_value); + + $altered_signal = $prompt->getShowSignal(); + $this->assertSame(['url' => $altered_uri_stub->__toString()], $altered_signal->getOptions()); + } + + public function testShowSignalWithLegacyParameter(): void + { + $prompt = $this->getUIFactory()->prompt()->standard($this->url_builder_stub); + $this->expectException(\LogicException::class); + $prompt->getShowSignal($this->createStub(\ILIAS\Data\URI::class)); + } } diff --git a/components/ILIAS/UI/tests/Component/Table/Action/ActionTest.php b/components/ILIAS/UI/tests/Component/Table/Action/ActionTest.php index caa8af6d30a3..4f17ec55df2c 100755 --- a/components/ILIAS/UI/tests/Component/Table/Action/ActionTest.php +++ b/components/ILIAS/UI/tests/Component/Table/Action/ActionTest.php @@ -24,8 +24,9 @@ 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\Prompt; +use ILIAS\UI\URLBuilderToken; /** * Basic Tests for Table-Actions. @@ -34,8 +35,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 +50,24 @@ 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()); } public function testDataTableActionStringTarget(): void @@ -90,8 +81,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->assertInstanceOf(URLBuilder::class, $this->link_action->getTarget()); + $this->assertInstanceOf(URLBuilder::class, $this->async_action->getTarget()); + $this->assertInstanceOf(Prompt::class, $this->prompt_action->getTarget()); + $this->assertFalse($this->prompt_action->isTargetLegacyAsync()); + $this->assertTrue($this->async_action->isTargetLegacyAsync()); + } + + 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 ed9707c1f257..c2e068ce67c9 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\Prompt $mock_dialog; + + public function setUp(): void + { + $this->mock_dialog = $this->createMock(I\Prompt\Prompt::class); + } + private function getRenderer() { return new DTRenderer( @@ -91,6 +94,87 @@ 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, + $this->getLanguage(), + ) extends NoUIFactory { + public function __construct( + protected I\Table\Factory $table_factory, + protected I\Prompt\Factory $prompt_factory, + protected ILIAS\Language\Language $language, + ) { + } + 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($this->language), + 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(); @@ -106,25 +190,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(); @@ -154,7 +219,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( @@ -386,7 +451,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) ]; @@ -496,7 +561,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..24c28efeb3f7 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()); @@ -239,7 +238,7 @@ public function testDataTableWithIdAndStorage(): void $table = new class ($storage) extends C\Table\Data { public function __construct( - protected \ArrayAccess $storage, + protected \ILIAS\UI\Storage $storage, ) { } public function mockGetStorageData(): ?array diff --git a/components/ILIAS/UI/tests/Component/Table/TableTestBase.php b/components/ILIAS/UI/tests/Component/Table/TableTestBase.php index 4371fd652ff1..748b98a2e4ae 100755 --- a/components/ILIAS/UI/tests/Component/Table/TableTestBase.php +++ b/components/ILIAS/UI/tests/Component/Table/TableTestBase.php @@ -87,9 +87,9 @@ protected function getTableFactory(): C\Table\Factory ); } - protected function getMockStorage(): ArrayAccess + protected function getMockStorage(): \ILIAS\UI\Storage { - return new class () implements ArrayAccess { + return new class () implements \ILIAS\UI\Storage { protected array $data = []; public function offsetExists(mixed $offset): bool {
    - {DRAG_HINT} - -
    {SELECTION_CONTROL_SELECT}
    -
    {SELECTION_CONTROL_DESELECT}
    - -
    -
    - {POS_INPUT_TITLE} -
    -
    -
    - {COL_TITLE} -
    -
    {COL_TITLE_ACTION}
    + {DRAG_HINT} + +
    {SELECTION_CONTROL_SELECT}
    +
    {SELECTION_CONTROL_DESELECT}
    + +
    +
    + {POS_INPUT_TITLE} +
    +
    +
    + {COL_TITLE} +
    +
    {COL_TITLE_ACTION}
    -
    -
    -
    {MULTI_ACTION_TRIGGERER}
    -
    -
    + +
    +
    +
    +
    {MULTI_ACTION_TRIGGERER}
    +
    +
    {FORM_BUTTONS} -
    -