N°5904 - Use attribute linked set edit mode to enable actions (#440)

* Add corresponding buttons depending on old edit mode (need to check with piR pour récuperer l'ancienne valeur.

* N°5904 - Handle attribute linked set edit_mode

* N°5904 Move calls to private jquery widget methods to public

* N°5904 - Worker improvements add button on link tagset

* Change itop set widget to new set block UI (5)

* Change itop set widget to new set block UI (5)

* Renommage variables JS avec le prefix combodo

* Search dialog block id conflict with form id

* add moved js files in iTopWebPage compatibility list

---------

Co-authored-by: Stephen Abello <stephen.abello@combodo.com>
This commit is contained in:
bdalsass
2023-02-06 16:07:55 +01:00
committed by GitHub
parent 3fbc5e1ce0
commit 1fe9520b7e
28 changed files with 801 additions and 356 deletions

View File

@@ -2337,7 +2337,7 @@ EOF
if (array_key_exists('bulk_context', $aArgs)) {
$oTagSetBlock = LinksSetUIBlockFactory::MakeForBulkLinkSet($iId, $oAttDef, $value, $sWizardHelperJsVarName, $aArgs['bulk_context']);
} else {
$oTagSetBlock = LinksSetUIBlockFactory::MakeForLinkSet($iId, $oAttDef, $value, $sWizardHelperJsVarName);
$oTagSetBlock = LinksSetUIBlockFactory::MakeForLinkSet($iId, $oAttDef, $value, $sWizardHelperJsVarName, $aArgs['this']);
}
$oTagSetBlock->SetName("attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}");
$aEventsList[] = 'validate';

View File

@@ -685,15 +685,15 @@ JS
}
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, $this->iId,
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, 'dtc_'.$this->iId,
array(
'menu' => false,
'currentId' => $this->iId,
'table_id' => "dr_{$this->iId}",
'menu' => false,
'currentId' => $this->iId,
'table_id' => "dr_{$this->iId}",
'table_inner_id' => "{$this->iId}_results",
'selection_mode' => true,
'selection_type' => 'single',
'cssCount' => '#count_'.$this->iId.'_results',
'cssCount' => '#count_'.$this->iId.'_results',
)
));
$sCancel = Dict::S('UI:Button:Cancel');

View File

@@ -1,133 +0,0 @@
let LinkSetWorker = new function(){
// defines
const ROUTER_BASE_URL = '../pages/ajax.render.php';
const ROUTE_LINK_SET_DELETE_OBJECT = 'linkset.delete_linked_object';
const ROUTE_LINK_SET_DETACH_OBJECT = 'linkset.detach_linked_object';
const ROUTE_LINK_SET_MODIFY_OBJECT = 'object.modify';
const ROUTE_LINK_SET_CREATE_OBJECT = 'linkset.create_linked_object';
/**
* CallAjaxDeleteLinkedObject.
*
* @param sLinkedObjectClass
* @param sLinkedObjectKey
* @param sTableId
* @constructor
*/
const CallAjaxDeleteLinkedObject = function(sLinkedObjectClass, sLinkedObjectKey, sTableId){
let oTableSettingsDialog = $('#datatable_dlg_datatable_' + sTableId);
$.post(`${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_DELETE_OBJECT}`, {
linked_object_class: sLinkedObjectClass,
linked_object_key: sLinkedObjectKey,
transaction_id: $('#linkset_transactions_id').val()
}, function (data) {
if(data.data.success === true){
oTableSettingsDialog.DataTableSettings('DoRefresh');
}
else{
CombodoModal.OpenInformativeModal(data.data.error_message, 'error');
}
});
};
/**
* CallAjaxDetachLinkedObject.
*
* @param sLinkedObjectClass
* @param sLinkedObjectKey
* @param sExternalKeyAttCode
* @param sTableId
* @constructor
*/
const CallAjaxDetachLinkedObject = function(sLinkedObjectClass, sLinkedObjectKey, sExternalKeyAttCode, sTableId){
let oTableSettingsDialog = $('#datatable_dlg_datatable_' + sTableId);
$.post(`${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_DETACH_OBJECT}`, {
linked_object_class: sLinkedObjectClass,
linked_object_key: sLinkedObjectKey,
external_key_att_code: sExternalKeyAttCode,
transaction_id: $('#linkset_transactions_id').val()
}, function (data) {
if(data.data.success === true){
oTableSettingsDialog.DataTableSettings('DoRefresh');
}
else{
CombodoModal.OpenInformativeModal(data.data.error_message, 'error');
}
});
};
/**
* CallAjaxModifyLinkedObject.
*
* @param {string} sLinkedObjectClass
* @param {string} sLinkedObjectKey
* @param {string} sTableId
* @constructor
*/
const CallAjaxModifyLinkedObject = function(sLinkedObjectClass, sLinkedObjectKey, sTableId){
let oTable = $('#datatable_' + sTableId);
let oTableSettingsDialog = $('#datatable_dlg_datatable_' + sTableId);
let oOptions = {
title: Dict.S('UI:Links:ActionRow:Modify:Modal:Title'),
content: {
endpoint: `${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_MODIFY_OBJECT}`,
data: {
class: sLinkedObjectClass,
id: sLinkedObjectKey,
},
},
extra_options: {
callback_on_modal_close: function () {
oTableSettingsDialog.DataTableSettings('DoRefresh');
$(this).find("form").remove();
$(this).dialog('destroy');
}
},
}
CombodoModal.OpenModal(oOptions);
};
/**
* @param {string} sTableId
*/
const CallAjaxCreateLinkedObject = function(sTableId){
let oTable = $('#datatable_' + sTableId);
let oTableSettingsDialog = $('#datatable_dlg_datatable_' + sTableId);
let sClass = oTable.closest('[data-role="ibo-block-links-table"]').attr('data-link-class');
let sAttCode = oTable.closest('[data-role="ibo-block-links-table"]').attr('data-link-attcode');
let sHostObjectClass = oTable.closest('[data-role="ibo-object-details"]').attr('data-object-class');
let sHostObjectId = oTable.closest('[data-role="ibo-object-details"]').attr('data-object-id');
let oOptions = {
title: Dict.S('UI:Layout:ObjectDetails:New:Modal:Title'),
content: {
endpoint: `${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_CREATE_OBJECT}`,
data: {
class: sClass,
att_code: sAttCode,
host_class: sHostObjectClass,
host_id: sHostObjectId
}
},
extra_options: {
callback_on_modal_close: function () {
oTableSettingsDialog.DataTableSettings('DoRefresh');
$(this).find("form").remove();
$(this).dialog('destroy');
}
},
}
CombodoModal.OpenModal(oOptions);
};
return {
DeleteLinkedObject: CallAjaxDeleteLinkedObject,
DetachLinkedObject: CallAjaxDetachLinkedObject,
ModifyLinkedObject: CallAjaxModifyLinkedObject,
CreateLinkedObject: CallAjaxCreateLinkedObject
}
};

View File

@@ -33,15 +33,9 @@ $(function()
submit_to: '../pages/ajax.render.php',
submit_parameters: {},
labels: {
// 'delete': 'Delete',
// modify: 'Modify...' ,
creation_title: 'Creation of a new object...' ,
// create: 'Create...',
// add: 'Add...',
// remove: 'Remove',
selection_title: 'Objects selection'
},
// buttons: ['create', 'delete'],
oWizardHelper: null
},
@@ -56,12 +50,6 @@ $(function()
this.datatable = this.element.find('table.listResults');
// var aButtonsTypes = ['delete', 'remove', 'modify', 'add', 'create'];
// this.oButtons = {};
// for(k in aButtonsTypes)
// {
// this.oButtons[aButtonsTypes[k]] = $('<button class="ibo-button ibo-is-regular ibo-is-neutral" type="button">' + this.options.labels[aButtonsTypes[k]] + '</button>');
// }
this.indicator = $('<span></span>');
this.inputToBeCreated = $('<input type="hidden" name="'+this.options.input_name+'_tbc" value="{}">');
this.toBeCreated = {};
@@ -80,27 +68,9 @@ $(function()
.after(this.inputToBeRemoved)
.after(this.indicator);
// for (k in this.options.buttons) {
// this.element.after(this.oButtons[this.options.buttons[k]]).after('&nbsp;&nbsp;&nbsp;');
// }
this.element.find('.selectList'+this.id).bind('change', function () {
me._updateButtons();
});
// this.oButtons['delete'].on('click', function () {
// me._deleteSelection();
// });
// this.oButtons['create'].on('click', function () {
// me._createRow();
// });
// this.oButtons['remove'].on('click', function () {
// $('.selectList'+me.id+':checked', me.element).each(function () {
// me._removeRow($(this));
// });
// });
// this.oButtons['add'].on('click', function () {
// me._selectToAdd();
// });
this._updateButtons();
@@ -132,24 +102,16 @@ $(function()
}
},
_updateButtons: function () {
var oChecked = $('.selectList'+this.id+':checked', this.element);
const oChecked = $('.selectList'+this.id+':checked', this.element);
switch (oChecked.length) {
case 0:
// this.oButtons['delete'].prop('disabled', true);
// this.oButtons['remove'].prop('disabled', true);
// this.oButtons['modify'].prop('disabled', true);
break;
case 1:
// this.oButtons['delete'].prop('disabled', false);
// this.oButtons['remove'].prop('disabled', false);
// this.oButtons['modify'].prop('disabled', false);
$('[data-role="ibo-button"][data-action="delete"]', this.element).prop('disabled', true);
$('[data-role="ibo-button"][data-action="detach"]', this.element).prop('disabled', true);
break;
default:
// this.oButtons['delete'].prop('disabled', false);
// this.oButtons['remove'].prop('disabled', false);
// this.oButtons['modify'].prop('disabled', true);
$('[data-role="ibo-button"][data-action="delete"]', this.element).prop('disabled', false);
$('[data-role="ibo-button"][data-action="detach"]', this.element).prop('disabled', false);
break;
}
},
@@ -186,7 +148,7 @@ $(function()
this.oDlg.dialog('option', {position: {my: "center", at: "center", of: window}});
},
_createRow: function () {
// this.oButtons['create'].prop('disabled', true);
$('[data-role="ibo-button"][data-action="create"]', this.element).prop('disabled', true);
this.indicator.html('<img src="../images/indicator.gif">');
oParams = this.options.submit_parameters;
oParams.operation = 'createObject';
@@ -225,14 +187,14 @@ $(function()
}
});
me.indicator.html('');
// me.oButtons['create'].prop('disabled', false);
$('[data-role="ibo-button"][data-action="create"]', this.element).prop('disabled', false);
me._updateDlgPosition();
});
},
_selectToAdd: function()
{
// this.oButtons['add'].prop('disabled', true);
$('[data-role="ibo-button"][data-action="add"]', this.element).prop('disabled', true);
this.indicator.html('<img src="../images/indicator.gif">');
oParams = this.options.submit_parameters;
oParams.operation = 'selectObjectsToAdd';
@@ -298,7 +260,7 @@ $(function()
});
me.indicator.html('');
// me.oButtons['add'].prop('disabled', false);
$('[data-role="ibo-button"][data-action="add"]', this.element).prop('disabled', false);
if (me.options.do_search)
{
me._onSearchToAdd();
@@ -468,7 +430,7 @@ $(function()
me._updateTable();
me.indicator.html('');
// me.oButtons['add'].prop('disabled', false);
$('[data-role="ibo-button"][data-action="add"]', this.element).prop('disabled', false);
me._updateTableInformation();
});
@@ -535,7 +497,7 @@ $(function()
oParams.tempId = nextIdx;
var me = this;
// this.oButtons['create'].prop('disabled', true);
$('[data-role="ibo-button"][data-action="create"]', this.element).prop('disabled', true);
this.indicator.html('<img src="../images/indicator.gif">');
$.post(this.options.submit_to, oParams, function (data) {
@@ -545,7 +507,7 @@ $(function()
me._updateTable();
me.indicator.html('');
// me.oButtons['create'].prop('disabled', false);
$('[data-role="ibo-button"][data-action="create"]', this.element).prop('disabled', false);
});
}
},
@@ -656,6 +618,18 @@ $(function()
Remove: function(oCheckbox) // for public access
{
this._removeRow(oCheckbox);
},
selectToAdd: function(){
this._selectToAdd();
},
removeSelection: function(){
this._removeSelection();
},
createRow: function(){
this._createRow();
},
deleteSelection: function(){
this._deleteSelection();
}
});
});

46
js/links/links_set.js Normal file
View File

@@ -0,0 +1,46 @@
let CombodoLinkSet = new function () {
/**
* Create a new link object and add it to set widget.
*
* @param sLinkedClass
* @param sCode
* @param sHostObjectClass
* @param sHostObjectKey
* @param sRemoteExtKey
* @param sRemoteClass
* @param oWidget
* @constructor
*/
const CallCreateLinkedObject = function(sLinkedClass, sCode, sHostObjectClass, sHostObjectKey, sRemoteExtKey, sRemoteClass, oWidget)
{
// Create link object
CombodoLinkSetWorker.CreateLinkedObject(sLinkedClass, sCode, sHostObjectClass, sHostObjectKey,
function(){
$(this).find("form").remove();
$(this).dialog('destroy');
},
function(event, data){
// We have just create a link object, now request the remote object
CombodoLinkSetWorker.GetRemoteObject(data.data.object.class_name, data.data.object.key, sRemoteExtKey, sRemoteClass, function(data){
// Add the new remote object in widget set options list
const selectize = oWidget[0].selectize;
selectize.addOption(data.data.object);
selectize.refreshOptions(false);
// Select the new remote object
selectize.addItem(data.data.object.key);
// Add to initial values, to handle remove action
selectize.addInitialValue(data.data.object.key);
});
});
}
return {
CreateLinkedObject: CallCreateLinkedObject,
}
};

View File

@@ -0,0 +1,106 @@
let CombodoLinkSetWorker = new function(){
// defines
const ROUTER_BASE_URL = '../pages/ajax.render.php';
const ROUTE_LINK_SET_DELETE_OBJECT = 'linkset.delete_linked_object';
const ROUTE_LINK_SET_DETACH_OBJECT = 'linkset.detach_linked_object';
const ROUTE_LINK_SET_CREATE_OBJECT = 'linkset.create_linked_object';
const ROUTE_LINK_GET_REMOTE_OBJECT = 'linkset.get_remote_object';
/**
* CallAjaxDeleteLinkedObject.
*
* @param {string} sLinkedObjectClass
* @param {string} sLinkedObjectKey
* @param oOnResponseCallback
* @constructor
*/
const CallAjaxDeleteLinkedObject = function(sLinkedObjectClass, sLinkedObjectKey, oOnResponseCallback){
$.post(`${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_DELETE_OBJECT}`, {
linked_object_class: sLinkedObjectClass,
linked_object_key: sLinkedObjectKey,
transaction_id: $('#linkset_transactions_id').val()
}, oOnResponseCallback);
};
/**
* CallAjaxDetachLinkedObject.
*
* @param {string} sLinkedObjectClass
* @param {string} sLinkedObjectKey
* @param {string} sExternalKeyAttCode
* @param oOnResponseCallback
* @constructor
*/
const CallAjaxDetachLinkedObject = function(sLinkedObjectClass, sLinkedObjectKey, sExternalKeyAttCode, oOnResponseCallback){
$.post(`${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_DETACH_OBJECT}`, {
linked_object_class: sLinkedObjectClass,
linked_object_key: sLinkedObjectKey,
external_key_att_code: sExternalKeyAttCode,
transaction_id: $('#linkset_transactions_id').val()
}, oOnResponseCallback);
};
/**
* CallAjaxCreateLinkedObject.
*
* @param {string} sClass
* @param {string} sAttCode
* @param {string} sHostObjectClass
* @param {string} sHostObjectId
* @param oOnModalCloseCallback
* @param oOnFormSubmittedCallback
*/
const CallAjaxCreateLinkedObject = function(sClass, sAttCode, sHostObjectClass, sHostObjectId, oOnModalCloseCallback = null, oOnFormSubmittedCallback = null){
let oOptions = {
title: Dict.S('UI:Links:New:Modal:Title'),
content: {
endpoint: `${ROUTER_BASE_URL}?route=${ROUTE_LINK_SET_CREATE_OBJECT}`,
data: {
class: sClass,
att_code: sAttCode,
host_class: sHostObjectClass,
host_id: sHostObjectId
}
},
extra_options: {
callback_on_modal_close: oOnModalCloseCallback
},
}
const oModal = CombodoModal.OpenModal(oOptions);
if(oOnFormSubmittedCallback !== null){
oModal.on('itop.form.submitted', 'form', oOnFormSubmittedCallback);
}
};
/**
* CallGetRemoteObject.
*
* @param sLinkedObjectClass
* @param sLinkedObjectKey
* @param sExternalKeyAttCode
* @param sRemoteClass
* @param oOnResponseCallback
* @constructor
*/
const CallGetRemoteObject = function(sLinkedObjectClass, sLinkedObjectKey, sExternalKeyAttCode, sRemoteClass, oOnResponseCallback){
$.post(`${ROUTER_BASE_URL}?route=${ROUTE_LINK_GET_REMOTE_OBJECT}`, {
linked_object_class: sLinkedObjectClass,
linked_object_key: sLinkedObjectKey,
external_key_att_code: sExternalKeyAttCode,
remote_class: sRemoteClass
}, oOnResponseCallback);
};
return {
DeleteLinkedObject: CallAjaxDeleteLinkedObject,
DetachLinkedObject: CallAjaxDetachLinkedObject,
CreateLinkedObject: CallAjaxCreateLinkedObject,
GetRemoteObject: CallGetRemoteObject
}
};

View File

@@ -0,0 +1,116 @@
$(function()
{
// the widget definition, where "itop" is the namespace,
// "links_view_table" the widget name
$.widget( "itop.links_view_table",
{
// default options
options:
{
link_class: null,
external_key_to_me: null
},
// the constructor
_create: function () {
$Table = $('table', this.element);
this.$tableSettingsDialog = $('#datatable_dlg_' + $Table.attr('id'));
},
// the destructor
_destroy: function () {
},
/**
* DeleteLinkedObject.
*
* @param sLinkedObjectKey
* @param oTrElement
* @constructor
*/
DeleteLinkedObject: function (sLinkedObjectKey, oTrElement) {
const me = this;
// link object deletion
CombodoLinkSetWorker.DeleteLinkedObject(this.options.link_class, sLinkedObjectKey, function (data) {
if (data.data.success === true) {
oTrElement.remove();
} else {
CombodoModal.OpenInformativeModal(data.data.error_message, 'error');
}
});
},
/**
* DetachLinkedObject.
*
* @param sLinkedObjectKey
* @param oTrElement
* @constructor
*/
DetachLinkedObject: function (sLinkedObjectKey, oTrElement) {
const me = this;
// link object unlink
CombodoLinkSetWorker.DetachLinkedObject(this.options.link_class, sLinkedObjectKey, this.options.external_key_to_me, function (data) {
if (data.data.success === true) {
oTrElement.remove();
} else {
CombodoModal.OpenInformativeModal(data.data.error_message, 'error');
}
});
},
/**
* CreateLinkedObject.
*
*/
CreateLinkedObject: function () {
const me = this;
// retrieve table
const $Table = $('table', this.element);
// retrieve context parameters
const sClass = $Table.closest('[data-role="ibo-block-links-table"]').attr('data-link-class');
const sAttCode = $Table.closest('[data-role="ibo-block-links-table"]').attr('data-link-attcode');
const sHostObjectClass = $Table.closest('[data-role="ibo-object-details"]').attr('data-object-class');
const sHostObjectId = $Table.closest('[data-role="ibo-object-details"]').attr('data-object-id');
// link object creation
CombodoLinkSetWorker.CreateLinkedObject(sClass, sAttCode, sHostObjectClass, sHostObjectId, function(){
$(this).find("form").remove();
$(this).dialog('destroy');
},function (event, data) {
if(data.success){
me.$tableSettingsDialog.DataTableSettings('DoRefresh');
}
});
},
/**
* ModifyLinkedObject.
*
* @param {string} sLinkedObjectKey
*/
ModifyLinkedObject: function (sLinkedObjectKey) {
const me = this;
// link object modification
ObjectWorker.ModifyObject(this.options.link_class, sLinkedObjectKey, function () {
$(this).find("form").remove();
$(this).dialog('destroy');
}, function(event, data){
if(data.success) {
me.$tableSettingsDialog.DataTableSettings('DoRefresh');
}
});
},
});
});

View File

@@ -0,0 +1,60 @@
let ObjectWorker = new function(){
// defines
const ROUTER_BASE_URL = '../pages/ajax.render.php';
const ROUTE_MODIFY_OBJECT = 'object.modify';
const ROUTE_GET_OBJECT = 'object.get';
/**
* CallAjaxModifyObject.
*
* @param {string} sObjectClass
* @param {string} sObjectKey
* @param oOnModalCloseCallback
* @param oOnFormSubmittedCallback
* @constructor
*/
const CallAjaxModifyObject = function(sObjectClass, sObjectKey, oOnModalCloseCallback, oOnFormSubmittedCallback){
let oOptions = {
title: Dict.S('UI:Links:ActionRow:Modify:Modal:Title'),
content: {
endpoint: `${ROUTER_BASE_URL}?route=${ROUTE_MODIFY_OBJECT}`,
data: {
class: sObjectClass,
id: sObjectKey,
},
},
extra_options: {
callback_on_modal_close: oOnModalCloseCallback
},
}
const oModal = CombodoModal.OpenModal(oOptions);
if(oOnFormSubmittedCallback !== null){
oModal.on('itop.form.submitted', 'form', oOnFormSubmittedCallback);
}
};
/**
* CallAjaxGetObject.
*
* @param {string} sObjectClass
* @param {string} sObjectKey
* @param oOnResponseCallback
* @constructor
*/
const CallAjaxGetObject = function(sObjectClass, sObjectId, oOnResponseCallback){
$.post(`${ROUTER_BASE_URL}?route=${ROUTE_GET_OBJECT}`, {
object_class: sObjectClass,
object_key: sObjectId,
}, oOnResponseCallback);
};
return {
ModifyObject: CallAjaxModifyObject,
GetObject: CallAjaxGetObject
}
};

View File

@@ -78,14 +78,14 @@ Selectize.define("combodo_multi_values_synthesis", function (aOptions) {
}
// Element exist in default selection,
// click allow user to switch between add or ignore states
if(oSelf.settings.initial.includes(sItemValue)) {
if(oSelf.plugins.settings.combodo_update_operations.initial.includes(sItemValue)) {
oSelf.listenClick($Item, sItemValue);
}
return;
}
// If no operation to restore
if(!oSelf.settings.initial.includes(sItemValue)) {
if(!oSelf.plugins.settings.combodo_update_operations.initial.includes(sItemValue)) {
// Element doesn't exist in initial value, we mark it as added
oSelf.Add($Item, sItemValue);
@@ -112,7 +112,7 @@ Selectize.define("combodo_multi_values_synthesis", function (aOptions) {
const $Item = oSelf.getItem(sItem);
// Element doesn't exist in default selection,
if(!oSelf.settings.initial.includes(sItem)) {
if(!oSelf.plugins.settings.combodo_update_operations.initial.includes(sItem)) {
// Remove operation
delete aOperations[sItem];

View File

@@ -15,11 +15,18 @@
*
* You should have received a copy of the GNU Affero General Public License
*/
Selectize.define("combodo_update_operations", function () {
Selectize.define("combodo_update_operations", function (aOptions) {
// Selectize instance
let oSelf = this;
// Plugin options
aOptions = $.extend({
initial: [],
},
aOptions
);
// Plugin variables
oSelf.bIsInitialized = false;
oSelf.operations = {};
@@ -88,7 +95,7 @@ Selectize.define("combodo_update_operations", function () {
// Scan items in current value and not in initial value
aCurrentItems.forEach(function(e){
if(!oSelf.settings.initial.includes(e)){
if(!aOptions.initial.includes(e)){
oSelf.operations[e] = {
operation: 'add',
data: CombodoGlobalToolbox.ExtractArrayItemsContainingThisKeyAndValue(aCurrentOptions, oSelf.settings.valueField, e)
@@ -97,7 +104,7 @@ Selectize.define("combodo_update_operations", function () {
});
// scan items in initial value and not in current value
oSelf.settings.initial.forEach(function(e){
aOptions.initial.forEach(function(e){
if(!aCurrentItems.includes(e)){
oSelf.operations[e] = {
operation: 'remove',
@@ -109,4 +116,12 @@ Selectize.define("combodo_update_operations", function () {
};
})();
// Declare addInitialValue function
oSelf.addInitialValue = (function () {
return function (value) {
aOptions.initial.push(value);
oSelf.updateOperationsInput();
};
})();
});

View File

@@ -5,7 +5,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '9482139b5aec9978f05b1cbb0542b24c18681819',
'reference' => 'f25f91ad79b38d9dcbe2a175bdb2fd2635255968',
'name' => 'combodo/itop',
'dev' => true,
),
@@ -25,7 +25,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '9482139b5aec9978f05b1cbb0542b24c18681819',
'reference' => 'f25f91ad79b38d9dcbe2a175bdb2fd2635255968',
'dev_requirement' => false,
),
'combodo/tcpdf' => array(

View File

@@ -34,7 +34,6 @@ $oP->add_linked_script("../js/json.js");
$oP->add_linked_script("../js/forms-json-utils.js");
$oP->add_linked_script("../js/wizardhelper.js");
$oP->add_linked_script("../js/wizard.utils.js");
$oP->add_linked_script("../js/linkswidget.js");
$oP->add_linked_script("../js/extkeywidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js");

View File

@@ -37,7 +37,6 @@ try
$oP->add_linked_script("../js/forms-json-utils.js");
$oP->add_linked_script("../js/wizardhelper.js");
$oP->add_linked_script("../js/wizard.utils.js");
$oP->add_linked_script("../js/linkswidget.js");
$oP->add_linked_script("../js/extkeywidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js");

View File

@@ -37,6 +37,7 @@ class Set extends AbstractInput
public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'base/components/input/set/layout';
public const DEFAULT_JS_FILES_REL_PATH = [
'js/links/links_set_worker.js',
'js/selectize/plugin_combodo_add_button.js',
'js/selectize/plugin_combodo_auto_position.js',
'js/selectize/plugin_combodo_update_operations.js',
@@ -55,6 +56,9 @@ class Set extends AbstractInput
/** @var bool $bHasAddOptionButton Enable add option button */
private bool $bHasAddOptionButton;
/** @var string|null $sAddOptionButtonJsOnClick JS code to execute on button click */
private ?string $sAddOptionButtonJsOnClick;
/** @var string $sAddButtonTitle Add button title */
private string $sAddButtonTitle;
@@ -103,6 +107,7 @@ class Set extends AbstractInput
$this->iMaxOptions = null;
$this->bHasRemoveItemButton = true;
$this->bHasAddOptionButton = false;
$this->sAddOptionButtonJsOnClick = null;
$this->sAddButtonTitle = Dict::S('UI:Button:Create');
$this->bIsPreloadEnabled = false;
$this->sTemplateOptions = null;
@@ -183,6 +188,30 @@ class Set extends AbstractInput
return $this->bHasRemoveItemButton;
}
/**
* SetAddOptionButtonJsOnClick.
*
* @param string $sJsOnClick
*
* @return $this
*/
public function SetAddOptionButtonJsOnClick(string $sJsOnClick): Set
{
$this->sAddOptionButtonJsOnClick = $sJsOnClick;
return $this;
}
/**
* GetAddOptionButtonJsOnClick.
*
* @return string
*/
public function GetAddOptionButtonJsOnClick(): string
{
return $this->sAddOptionButtonJsOnClick;
}
/**
* SetHasAddOptionButton.
*

View File

@@ -32,10 +32,12 @@ use WebPage;
abstract class AbstractBlockLinksViewTable extends UIContentBlock
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-abstract-block-links-view-table';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'application/links/layout';
public const DEFAULT_JS_FILES_REL_PATH = [
'js/links/link_set_worker.js',
public const BLOCK_CODE = 'ibo-abstract-block-links-view-table';
public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'application/links/layout';
public const DEFAULT_JS_FILES_REL_PATH = [
'js/links/links_view_table_widget.js',
'js/links/links_set_worker.js',
'js/objects/objects_worker.js',
'js/wizardhelper.js',
];
@@ -70,7 +72,7 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
*/
public function __construct(WebPage $oPage, DBObject $oDbObject, string $sObjectClass, string $sAttCode, AttributeLinkedSet $oAttDef)
{
parent::__construct('', ["ibo-block-links-table"]);
parent::__construct("links_view_table_$sAttCode", ["ibo-block-links-table"]);
// retrieve parameters
$this->oAttDef = $oAttDef;
@@ -204,6 +206,8 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
/**
* GetAttCode.
*
* @return string
*/
public function GetAttCode(): string
@@ -211,4 +215,33 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
return $this->sAttCode;
}
/**
* GetLinkedClass.
*
* @return mixed
*/
public function GetLinkedClass()
{
return $this->oAttDef->GetLinkedClass();
}
/**
* GetExternalKeyToMe.
*
* @return mixed
*/
public function GetExternalKeyToMe()
{
return $this->oAttDef->GetExtKeyToMe();
}
/**
* GetWidgetName.
*
* @return string
*/
public function GetWidgetName(): string
{
return "oWidget{$this->GetId()}";
}
}

View File

@@ -6,18 +6,28 @@
namespace Combodo\iTop\Application\UI\Links\Direct;
use ApplicationContext;
use ArchivedObjectException;
use AttributeLinkedSet;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Toolbar\Toolbar;
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use ConfigException;
use CoreException;
use CoreUnexpectedValue;
use DBObjectSet;
use Dict;
use DictExceptionMissingString;
use Exception;
use MetaModel;
use MySQLException;
use UILinksWidgetDirect;
use utils;
use WebPage;
/**
* Class BlockDirectLinksEditTable
@@ -31,40 +41,43 @@ class BlockDirectLinksEditTable extends UIContentBlock
// Overloaded constants
public const BLOCK_CODE = 'ibo-block-direct-links-edit-table';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'application/links/direct/block-direct-links-edit-table/layout';
public const DEFAULT_JS_FILES_REL_PATH = [
'js/links/links_direct_widget.js',
];
/** @var \UILinksWidgetDirect */
public \UILinksWidgetDirect $oUILinksDirectWidget;
/** @var UILinksWidgetDirect $oUILinksDirectWidget */
public UILinksWidgetDirect $oUILinksDirectWidget;
/** @var \AttributeLinkedSet */
private \AttributeLinkedSet $oAttributeLinkedSet;
/** @var AttributeLinkedSet $oAttributeLinkedSet */
private AttributeLinkedSet $oAttributeLinkedSet;
/** @var string */
/** @var string $sInputName */
public string $sInputName;
/** @var array */
/** @var array $aLabels */
public array $aLabels;
/** @var string */
/** @var string $sSubmitUrl */
public string $sSubmitUrl;
/** @var string */
/** @var string $sWizHelper */
public string $sWizHelper;
/** @var string */
/** @var string $sJSDoSearch */
public string $sJSDoSearch;
/**
* Constructor.
*
* @param \UILinksWidgetDirect $oUILinksDirectWidget
* @param UILinksWidgetDirect $oUILinksDirectWidget
* @param string $sId
*
* @throws \ConfigException
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \Exception
* @throws ConfigException
* @throws CoreException
* @throws DictExceptionMissingString
* @throws Exception
*/
public function __construct(\UILinksWidgetDirect $oUILinksDirectWidget, string $sId)
public function __construct(UILinksWidgetDirect $oUILinksDirectWidget, string $sId)
{
parent::__construct($sId, ["ibo-block-direct-links--edit-in-place"]);
@@ -73,18 +86,14 @@ class BlockDirectLinksEditTable extends UIContentBlock
// compute
$this->aLabels = array(
'delete' => Dict::S('UI:Button:Delete'),
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->oUILinksDirectWidget->GetLinkedClass())),
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->oUILinksDirectWidget->GetLinkedClass())),
'remove' => Dict::S('UI:Button:Remove'),
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->oUILinksDirectWidget->GetLinkedClass())),
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->oUILinksDirectWidget->GetLinkedClass())),
);
$oContext = new \ApplicationContext();
$this->sSubmitUrl = \utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
$oContext = new ApplicationContext();
$this->sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
// Don't automatically launch the search if the table is huge
$bDoSearch = !\utils::IsHighCardinality($this->oUILinksDirectWidget->GetLinkedClass());
$bDoSearch = !utils::IsHighCardinality($this->oUILinksDirectWidget->GetLinkedClass());
$this->sJSDoSearch = $bDoSearch ? 'true' : 'false';
// Initialization
@@ -98,7 +107,7 @@ class BlockDirectLinksEditTable extends UIContentBlock
* Initialisation.
*
* @return void
* @throws \Exception
* @throws Exception
*/
private function Init()
{
@@ -109,8 +118,8 @@ class BlockDirectLinksEditTable extends UIContentBlock
* Initialize UI.
*
* @return void
* @throws \CoreException
* @throws \Exception
* @throws CoreException
* @throws Exception
*/
private function InitUI()
{
@@ -118,15 +127,14 @@ class BlockDirectLinksEditTable extends UIContentBlock
}
/**
* @param \WebPage $oPage
* @param \DBObjectSet $oValue
* @param WebPage $oPage
* @param DBObjectSet $oValue
* @param string $sFormPrefix
*
* @return void
*/
public function InitTable(\WebPage $oPage, $oValue, string $sFormPrefix)
public function InitTable(WebPage $oPage, DBObjectSet $oValue, string $sFormPrefix)
{
/** @todo fields initialization */
$this->sInputName = $sFormPrefix.'attr_'.$this->oUILinksDirectWidget->GetAttCode();
$this->sWizHelper = 'oWizardHelper'.$sFormPrefix;
@@ -138,7 +146,7 @@ class BlockDirectLinksEditTable extends UIContentBlock
$oDatatable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]);
// Panel
$aTablePanel = PanelUIBlockFactory::MakeForClass($this->oUILinksDirectWidget->GetLinkedClass(), $this->oAttributeLinkedSet->GetLabel())
$oTablePanel = PanelUIBlockFactory::MakeForClass($this->oUILinksDirectWidget->GetLinkedClass(), $this->oAttributeLinkedSet->GetLabel())
->SetSubTitle(Dict::Format('UI:Pagination:HeaderNoSelection', count($aRows)))
->SetIcon(MetaModel::GetClassIcon($this->oUILinksDirectWidget->GetLinkedClass(), false))
->AddCSSClass('ibo-datatable-panel');
@@ -146,30 +154,17 @@ class BlockDirectLinksEditTable extends UIContentBlock
// - Panel description
$sDescription = $this->oAttributeLinkedSet->GetDescription();
if (utils::IsNotNullOrEmptyString($sDescription)) {
$oTitleBlock = $aTablePanel->GetTitleBlock()
$oTitleBlock = $oTablePanel->GetTitleBlock()
->AddDataAttribute('tooltip-content', $sDescription)
->AddDataAttribute('tooltip-max-width', 'min(600px, 90vw)') // Allow big description to be wide enough while shrinking on small screens
->AddCSSClass('ibo-has-description');
}
// Toolbar and actions
$oToolbar = ToolbarUIBlockFactory::MakeForButton();
$oActionButtonUnlink = ButtonUIBlockFactory::MakeNeutral('Unlink');
$oActionButtonUnlink->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._removeSelection();");
$oToolbar->AddSubBlock($oActionButtonUnlink);
$oActionButtonLink = ButtonUIBlockFactory::MakeNeutral('Link');
$oActionButtonLink->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._selectToAdd();");
$oToolbar->AddSubBlock($oActionButtonLink);
$oActionButtonCreate = ButtonUIBlockFactory::MakeNeutral('Create');
$oActionButtonCreate->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._createRow();");
$oToolbar->AddSubBlock($oActionButtonCreate);
$oActionButtonDelete = ButtonUIBlockFactory::MakeNeutral('Delete');
$oActionButtonDelete->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('instance')._deleteSelection();");
$oToolbar->AddSubBlock($oActionButtonDelete);
$aTablePanel->AddToolbarBlock($oToolbar);
$aTablePanel->AddSubBlock($oDatatable);
$this->AddSubBlock($aTablePanel);
$oToolbar = $this->InitToolBar();
$oTablePanel->AddToolbarBlock($oToolbar);
$oTablePanel->AddSubBlock($oDatatable);
$this->AddSubBlock($oTablePanel);
}
catch (\Exception $e) {
$oAlert = AlertUIBlockFactory::MakeForDanger('error', Dict::S('UI:Datatables:Language:Error'));
@@ -179,18 +174,70 @@ class BlockDirectLinksEditTable extends UIContentBlock
}
}
/**
* InitToolBar.
*
* @return \Combodo\iTop\Application\UI\Base\Component\Toolbar\Toolbar
*/
private function InitToolBar(): Toolbar
{
$oToolbar = ToolbarUIBlockFactory::MakeForButton();
// until a full link set refactoring (continue using edit_mode property)
switch ($this->oAttributeLinkedSet->GetEditMode()) {
case LINKSET_EDITMODE_NONE: // The linkset is read-only
break;
case LINKSET_EDITMODE_ADDONLY: // The only possible action is to open (in a new window) the form to create a new object
$oActionButtonLink = ButtonUIBlockFactory::MakeNeutral('Link', 'link');
$oActionButtonLink->AddDataAttribute('action', 'add');
$oActionButtonLink->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('selectToAdd');");
$oToolbar->AddSubBlock($oActionButtonLink);
break;
case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place'
$oActionButtonCreate = ButtonUIBlockFactory::MakeNeutral('Create', 'create');
$oActionButtonCreate->AddDataAttribute('action', 'create');
$oActionButtonCreate->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('createRow');");
$oToolbar->AddSubBlock($oActionButtonCreate);
$oActionButtonDelete = ButtonUIBlockFactory::MakeNeutral('Delete', 'delete');
$oActionButtonDelete->AddDataAttribute('action', 'delete');
$oActionButtonDelete->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('deleteSelection');");
$oToolbar->AddSubBlock($oActionButtonDelete);
break;
case LINKSET_EDITMODE_ADDREMOVE: // The whole linkset can be edited 'in-place'
$oActionButtonUnlink = ButtonUIBlockFactory::MakeNeutral('Unlink', 'unlink');
$oActionButtonUnlink->AddDataAttribute('action', 'detach');
$oActionButtonUnlink->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('removeSelection');");
$oToolbar->AddSubBlock($oActionButtonUnlink);
$oActionButtonLink = ButtonUIBlockFactory::MakeNeutral('Link', 'link');
$oActionButtonLink->AddDataAttribute('action', 'add');
$oActionButtonLink->SetOnClickJsCode("$('#{$this->oUILinksDirectWidget->GetInputId()}').directlinks('selectToAdd');");
$oToolbar->AddSubBlock($oActionButtonLink);
break;
case LINKSET_EDITMODE_ACTIONS: // Show the usual 'Actions' popup menu
default:
}
return $oToolbar;
}
/**
* Return table rows.
*
* @param \DBObjectSet $oValue
* @param DBObjectSet $oValue
*
* @return array
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MySQLException
* @throws \Exception
* @throws ArchivedObjectException
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws DictExceptionMissingString
* @throws MySQLException
* @throws Exception
*/
private function GetTableRows(\WebPage $oPage, \DBObjectSet $oValue): array
{
@@ -220,6 +267,41 @@ class BlockDirectLinksEditTable extends UIContentBlock
return ($bSafe) ? \utils::GetSafeId($sFieldId) : $sFieldId;
}
/**
* Convert edit_mode to relation type.
*
* @return string|null
*/
private function ConvertEditModeToRelationType(): ?string
{
switch ($this->oAttributeLinkedSet->GetEditMode()) {
case LINKSET_EDITMODE_INPLACE:
return LINKSET_RELATIONTYPE_PROPERTY;
case LINKSET_EDITMODE_ADDREMOVE:
return LINKSET_RELATIONTYPE_LINK;
default:
return null;
}
}
/**
* Convert edit_mode to read only.
*
* @return bool
*/
private function ConvertEditModeToReadOnly(): bool
{
switch ($this->oAttributeLinkedSet->GetEditMode()) {
case LINKSET_EDITMODE_NONE:
case LINKSET_EDITMODE_ADDONLY:
case LINKSET_EDITMODE_ACTIONS:
return true;
default:
return false;
}
}
/**
* Return row actions.
*
@@ -229,9 +311,9 @@ class BlockDirectLinksEditTable extends UIContentBlock
{
$aRowActions = array();
if (!$this->oAttributeLinkedSet->GetReadOnly()) {
if (!$this->ConvertEditModeToReadOnly()) {
switch ($this->oAttributeLinkedSet->GetRelationType()) {
switch ($this->ConvertEditModeToRelationType()) {
case LINKSET_RELATIONTYPE_LINK:
$aRowActions[] = array(

View File

@@ -37,9 +37,9 @@ class BlockDirectLinksViewTable extends AbstractBlockLinksViewTable
'default' => $this->GetDefault(),
'table_id' => $this->GetTableId(),
'row_actions' => $this->GetRowActions(),
'currentId' => $this->GetTableId(),
'currentId' => $this->GetTableId(),
'panel_title' => $this->oAttDef->GetLabel(),
'panel_icon' => MetaModel::GetClassIcon($this->GetTargetClass(), false),
'panel_icon' => MetaModel::GetClassIcon($this->GetTargetClass(), false),
);
// Description
@@ -50,9 +50,9 @@ class BlockDirectLinksViewTable extends AbstractBlockLinksViewTable
// Add creation in modal if the linkset is not readonly
if (!$this->oAttDef->GetReadOnly()) {
$aExtraParams['creation_in_modal_is_allowed'] = true;
$aExtraParams['creation_in_modal_js_handler'] = 'LinkSetWorker.CreateLinkedObject("'.$this->GetTableId().'");';
$aExtraParams['creation_in_modal_js_handler'] = "{$this->GetWidgetName()}.links_view_table('CreateLinkedObject');";
}
return $aExtraParams;
}
@@ -70,7 +70,7 @@ class BlockDirectLinksViewTable extends AbstractBlockLinksViewTable
'label' => 'UI:Links:ActionRow:Detach',
'tooltip' => 'UI:Links:ActionRow:Detach+',
'icon_classes' => 'fas fa-minus',
'js_row_action' => "LinkSetWorker.DetachLinkedObject('{$this->sTargetClass}', aRowData['{$this->sTargetClass}/_key_/raw'], '{$this->oAttDef->GetExtKeyToMe()}', '{$this->GetTableId()}');",
'js_row_action' => "{$this->GetWidgetName()}.links_view_table('DetachLinkedObject', aRowData['{$this->sTargetClass}/_key_/raw'], oTrElement);",
'confirmation' => [
'message' => 'UI:Links:ActionRow:Detach:Confirmation',
'message_row_data' => "{$this->sTargetClass}/hyperlink",
@@ -84,7 +84,7 @@ class BlockDirectLinksViewTable extends AbstractBlockLinksViewTable
'label' => 'UI:Links:ActionRow:Delete',
'tooltip' => 'UI:Links:ActionRow:Delete+',
'icon_classes' => 'fas fa-trash',
'js_row_action' => "LinkSetWorker.DeleteLinkedObject('{$this->oAttDef->GetLinkedClass()}', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw'], '{$this->GetTableId()}');",
'js_row_action' => "{$this->GetWidgetName()}.links_view_table('DeleteLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw'], oTrElement);",
'confirmation' => [
'message' => 'UI:Links:ActionRow:Delete:Confirmation',
'message_row_data' => "{$this->sTargetClass}/hyperlink",
@@ -97,7 +97,7 @@ class BlockDirectLinksViewTable extends AbstractBlockLinksViewTable
'label' => 'UI:Links:ActionRow:Modify',
'tooltip' => 'UI:Links:ActionRow:Modify+',
'icon_classes' => 'fas fa-pen',
'js_row_action' => "LinkSetWorker.ModifyLinkedObject('{$this->sTargetClass}', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw'], '{$this->GetTableId()}');",
'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['{$this->oAttDef->GetLinkedClass()}/_key_/raw']);",
);
}

View File

@@ -6,19 +6,23 @@
namespace Combodo\iTop\Application\UI\Links\Indirect;
use AttributeLinkedSetIndirect;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use ConfigException;
use CoreException;
use DBObject;
use Exception;
use Dict;
use MetaModel;
use UILinksWidget;
use utils;
use WebPage;
/**
* Class BlockIndirectLinksEditTable
@@ -32,12 +36,15 @@ class BlockIndirectLinksEditTable extends UIContentBlock
// Overloaded constants
public const BLOCK_CODE = 'ibo-block-indirect-links-edit-table';
public const DEFAULT_JS_TEMPLATE_REL_PATH = 'application/links/indirect/block-indirect-links-edit-table/layout';
public const DEFAULT_JS_FILES_REL_PATH = [
'js/links/links_widget.js',
];
/** @var \UILinksWidget */
public \UILinksWidget $oUILinksWidget;
/** @var UILinksWidget $oUILinksWidget */
public UILinksWidget $oUILinksWidget;
/** @var \AttributeLinkedSetIndirect */
private \AttributeLinkedSetIndirect $oAttributeLinkedSetIndirect;
/** @var AttributeLinkedSetIndirect $oAttributeLinkedSetIndirect */
private AttributeLinkedSetIndirect $oAttributeLinkedSetIndirect;
/** @var string */
public string $sDuplicates;
@@ -60,13 +67,13 @@ class BlockIndirectLinksEditTable extends UIContentBlock
/**
* Constructor.
*
* @param \UILinksWidget $oUILinksWidget
* @param UILinksWidget $oUILinksWidget
*
* @throws \ConfigException
* @throws \CoreException
* @throws \Exception
* @throws ConfigException
* @throws CoreException
* @throws Exception
*/
public function __construct(\UILinksWidget $oUILinksWidget)
public function __construct(UILinksWidget $oUILinksWidget)
{
parent::__construct("linkedset_{$oUILinksWidget->GetLinkedSetId()}", ["ibo-block-indirect-links--edit"]);
@@ -88,7 +95,7 @@ class BlockIndirectLinksEditTable extends UIContentBlock
* Initialization.
*
* @return void
* @throws \Exception
* @throws Exception
*/
private function Init()
{
@@ -99,8 +106,8 @@ class BlockIndirectLinksEditTable extends UIContentBlock
* Initialize UI.
*
* @return void
* @throws \CoreException
* @throws \Exception
* @throws CoreException
* @throws Exception
*/
private function InitUI()
{
@@ -109,37 +116,6 @@ class BlockIndirectLinksEditTable extends UIContentBlock
$this->AddDeferredBlock($oDeferredBlock);
}
/**
* CreateTableInformationAlert.
*
* @return iUIBlock
*/
private function CreateTableInformationAlert(): iUIBlock
{
// Selection alert
$oAlert = AlertUIBlockFactory::MakeNeutral('', '', "linkedset_{$this->oUILinksWidget->GetInputId()}_alert_information");
$oAlert->AddCSSClasses([
'ibo-table--alert-information',
]);
$oAlert->SetIsClosable(false);
$oAlert->SetIsCollapsible(false);
$oAlert->AddSubBlock(new Html('<span class="ibo-table--alert-information--label" data-role="ibo-datatable-selection-value"></span>'));
// Delete button
$oUIButton = ButtonUIBlockFactory::MakeForDestructiveAction("Détacher les {$this->oUILinksWidget->GetRemoteClass()}", 'table-selection');
$oUIButton->SetOnClickJsCode("oWidget{$this->oUILinksWidget->GetInputId()}.RemoveSelected();");
$oUIButton->AddCSSClass('ibo-table--alert-information--delete-button');
$oAlert->AddSubBlock($oUIButton);
// Add button
$oUIAddButton = ButtonUIBlockFactory::MakeForPrimaryAction("Attacher des {$this->oUILinksWidget->GetRemoteClass()}", 'table-selection');
$oUIAddButton->AddCSSClass('ibo-table--alert-information--add-button');
$oUIAddButton->SetOnClickJsCode("oWidget{$this->oUILinksWidget->GetInputId()}.AddObjects();");
$oAlert->AddSubBlock($oUIAddButton);
return $oAlert;
}
/**
* @param \WebPage $oPage
* @param $oValue
@@ -149,6 +125,9 @@ class BlockIndirectLinksEditTable extends UIContentBlock
* @param $aTableConfig
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
*/
public function InitTable(\WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aTableConfig)
{
@@ -192,7 +171,7 @@ class BlockIndirectLinksEditTable extends UIContentBlock
]);
// Panel
$aTablePanel = PanelUIBlockFactory::MakeForClass($this->oUILinksWidget->GetRemoteClass(), $this->oAttributeLinkedSetIndirect->GetLabel())
$oTablePanel = PanelUIBlockFactory::MakeForClass($this->oUILinksWidget->GetRemoteClass(), $this->oAttributeLinkedSetIndirect->GetLabel())
->SetSubTitle(Dict::Format('UI:Pagination:HeaderNoSelection', count($aForm)))
->SetIcon(MetaModel::GetClassIcon($this->oUILinksWidget->GetRemoteClass(), false))
->AddCSSClass('ibo-datatable-panel');
@@ -200,7 +179,7 @@ class BlockIndirectLinksEditTable extends UIContentBlock
// - Panel description
$sDescription = $this->oAttributeLinkedSetIndirect->GetDescription();
if (utils::IsNotNullOrEmptyString($sDescription)) {
$oTitleBlock = $aTablePanel->GetTitleBlock()
$oTitleBlock = $oTablePanel->GetTitleBlock()
->AddDataAttribute('tooltip-content', $sDescription)
->AddDataAttribute('tooltip-max-width', 'min(600px, 90vw)') // Allow big description to be wide enough while shrinking on small screens
->AddCSSClass('ibo-has-description');
@@ -214,10 +193,10 @@ class BlockIndirectLinksEditTable extends UIContentBlock
$oActionButtonLink = ButtonUIBlockFactory::MakeNeutral('Link');
$oActionButtonLink->SetOnClickJsCode("oWidget{$this->oUILinksWidget->GetInputId()}.AddObjects();");
$oToolbar->AddSubBlock($oActionButtonLink);
$aTablePanel->AddToolbarBlock($oToolbar);
$aTablePanel->AddSubBlock($oDataTable);
$oTablePanel->AddToolbarBlock($oToolbar);
$oTablePanel->AddSubBlock($oDataTable);
$this->AddSubBlock($aTablePanel);
$this->AddSubBlock($oTablePanel);
$this->AddSubBlock(InputUIBlockFactory::MakeForHidden("{$sFormPrefix}{$this->oUILinksWidget->GetInputId()}", '', "{$sFormPrefix}{$this->oUILinksWidget->GetInputId()}"));
}
@@ -238,7 +217,7 @@ class BlockIndirectLinksEditTable extends UIContentBlock
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function GetFormRow(\WebPage $oP, \DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false)
public function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false)
{
$sPrefix = "{$this->oUILinksWidget->GetAttCode()}{$this->oUILinksWidget->GetNameSuffix()}";
$aRow = array();

View File

@@ -58,11 +58,12 @@ class BlockIndirectLinksViewTable extends AbstractBlockLinksViewTable
if ($this->oAttDef->HasDescription()) {
$aExtraParams['panel_title_tooltip'] = $this->oAttDef->GetDescription();
}
// Add creation in modal if the linkset is not readonly
if (!$this->oAttDef->GetReadOnly()) {
$sHostClass = get_class($this->oDbObject);
$aExtraParams['creation_in_modal_is_allowed'] = true;
$aExtraParams['creation_in_modal_js_handler'] = 'LinkSetWorker.CreateLinkedObject("'.$this->GetTableId().'");';
$aExtraParams['creation_in_modal_js_handler'] = "{$this->GetWidgetName()}.links_view_table('CreateLinkedObject');";
}
return $aExtraParams;
@@ -79,7 +80,7 @@ class BlockIndirectLinksViewTable extends AbstractBlockLinksViewTable
'label' => 'UI:Links:ActionRow:Detach',
'tooltip' => 'UI:Links:ActionRow:Detach+',
'icon_classes' => 'fas fa-minus',
'js_row_action' => "LinkSetWorker.DeleteLinkedObject('{$this->oAttDef->GetLinkedClass()}', aRowData['Link/_key_/raw'], '{$this->GetTableId()}');",
'js_row_action' => "{$this->GetWidgetName()}.links_view_table('DeleteLinkedObject', aRowData['Link/_key_/raw'], oTrElement);",
'confirmation' => [
'message' => 'UI:Links:ActionRow:Detach:Confirmation',
'message_row_data' => "Remote/hyperlink",
@@ -91,7 +92,7 @@ class BlockIndirectLinksViewTable extends AbstractBlockLinksViewTable
'label' => 'UI:Links:ActionRow:Modify',
'tooltip' => 'UI:Links:ActionRow:Modify+',
'icon_classes' => 'fas fa-pen',
'js_row_action' => "LinkSetWorker.ModifyLinkedObject('{$this->oAttDef->GetLinkedClass()}', aRowData['Link/_key_/raw'], '{$this->GetTableId()}');",
'js_row_action' => "{$this->GetWidgetName()}.links_view_table('ModifyLinkedObject', aRowData['Link/_key_/raw']);",
);
}

View File

@@ -26,6 +26,7 @@ use Combodo\iTop\Service\Links\LinksBulkDataPostProcessor;
use Combodo\iTop\Service\Links\LinkSetDataTransformer;
use Combodo\iTop\Service\Links\LinkSetModel;
use Combodo\iTop\Service\Links\LinkSetRepository;
use DBObject;
use iDBObjectSetIterator;
/**
@@ -46,10 +47,11 @@ class LinksSetUIBlockFactory extends SetUIBlockFactory
* @param AttributeLinkedSet $oAttDef Link set attribute definition
* @param iDBObjectSetIterator $oDbObjectSet Link set value
* @param string $sWizardHelperJsVarName Wizard helper name
* @param DBObject|null $oHostDbObject Host DB object
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Set\Set
*/
public static function MakeForLinkSet(string $sId, AttributeLinkedSet $oAttDef, iDBObjectSetIterator $oDbObjectSet, string $sWizardHelperJsVarName): Set
public static function MakeForLinkSet(string $sId, AttributeLinkedSet $oAttDef, iDBObjectSetIterator $oDbObjectSet, string $sWizardHelperJsVarName, DBObject $oHostDbObject = null): Set
{
$sTargetClass = LinkSetModel::GetTargetClass($oAttDef);
$sTargetField = LinkSetModel::GetTargetField($oAttDef);
@@ -57,6 +59,16 @@ class LinksSetUIBlockFactory extends SetUIBlockFactory
// Set UI block for OQL
$oSetUIBlock = SetUIBlockFactory::MakeForOQL($sId, $sTargetClass, $oAttDef->GetValuesDef()->GetFilterExpression(), $sWizardHelperJsVarName);
$oSetUIBlock->AddJsFileRelPath('js/links/links_set.js');
// Add button behaviour
if (in_array($oAttDef->GetEditMode(), [LINKSET_EDITMODE_ADDREMOVE, LINKSET_EDITMODE_ADDONLY, LINKSET_EDITMODE_INPLACE, LINKSET_EDITMODE_ACTIONS])
&& $oHostDbObject !== null) {
$sHostClass = get_class($oHostDbObject);
$oSetUIBlock->SetHasAddOptionButton(true);
$oSetUIBlock->SetAddOptionButtonJsOnClick("CombodoLinkSet.CreateLinkedObject('{$oAttDef->GetLinkedClass()}', '{$oAttDef->GetCode()}', '{$sHostClass}', '{$oHostDbObject->GetKey()}', '{$sTargetField}', '{$sTargetClass}', oWidget{$oSetUIBlock->GetId()} );");
}
// Current value
$aCurrentValues = LinkSetDataTransformer::Decode($oDbObjectSet, $sTargetClass, $sTargetField);

View File

@@ -47,6 +47,9 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
// - DisplayableGraph, impact analysis
'js/raphael-min.js',
'js/jquery.mousewheel.js',
/** - links widgets moved in links folder @since 3.1.0 * */
'js/links/links_direct_widget.js',
'js/links/links_widget.js',
];
/** @inheritDoc */
protected const COMPATIBILITY_DEPRECATED_LINKED_SCRIPTS_REL_PATH = [

View File

@@ -18,6 +18,7 @@ use Combodo\iTop\Service\Base\ObjectRepository;
use CoreCannotSaveObjectException;
use DeleteException;
use Dict;
use Exception;
use IssueLog;
use iTopOwnershipLock;
use iTopWebPage;
@@ -90,6 +91,8 @@ class ObjectController extends AbstractController
/* Alerts the results */
sPosting.done(function(data) {
// fire event
oForm.trigger('itop.form.submitted', [data]);
if(data.success !== undefined && data.success === true) {
oForm.closest('[data-role="ibo-modal"]').dialog('close');
}
@@ -151,6 +154,7 @@ JS;
$sClass = utils::ReadPostedParam('class', '', 'class');
$sClassLabel = MetaModel::GetName($sClass);
$sFormPrefix = utils::ReadPostedParam('formPrefix', '', FILTER_SANITIZE_STRING);
$sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id');
$aErrors = array();
$aWarnings = array();
@@ -203,7 +207,7 @@ JS;
$oObj->Set($sStateAttCode, $sTargetState);
}
}
$aErrors = $oObj->UpdateObjectFromPostedForm();
$aErrors = $oObj->UpdateObjectFromPostedForm($sFormPrefix);
}
if (isset($oObj) && is_object($oObj))
{
@@ -260,6 +264,7 @@ JS;
// Nothing more to do
if ($this->IsHandlingXmlHttpRequest()) {
$aResult['success'] = true;
$aResult['data'] = ['object' => ObjectRepository::ConvertObjectToArray($oObj, $sClass)];
} else {
ReloadAndDisplay($oPage, $oObj, 'create', $sMessage, 'ok');
}
@@ -543,8 +548,6 @@ JS;
'js/forms-json-utils.js',
'js/wizardhelper.js',
'js/wizard.utils.js',
'js/linkswidget.js',
'js/linksdirectwidget.js',
'js/extkeywidget.js',
'js/jquery.blockUI.js',
];
@@ -589,4 +592,33 @@ JS;
]);
}
/**
* OperationGet.
*
* @return JsonPage
*/
public function OperationGet(): JsonPage
{
$oPage = new JsonPage();
$bSuccess = true;
$aObjectData = null;
// Retrieve query params
$sObjectClass = utils::ReadParam('object_class', '', false, utils::ENUM_SANITIZATION_FILTER_STRING);
$sObjectKey = utils::ReadParam('object_key', '', false, utils::ENUM_SANITIZATION_FILTER_STRING);
// Retrieve object
try {
$oObject = MetaModel::GetObject($sObjectClass, $sObjectKey);
$aObjectData = ObjectRepository::ConvertObjectToArray($oObject, $sObjectClass);
}
catch (Exception $e) {
$bSuccess = false;
}
return $oPage->SetData([
'object' => $aObjectData,
'success' => $bSuccess,
]);
}
}

View File

@@ -10,6 +10,7 @@ use AjaxPage;
use cmdbAbstractObject;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Controller\AbstractController;
use Combodo\iTop\Service\Base\ObjectRepository;
use Exception;
use JsonPage;
use CoreException;
@@ -169,8 +170,8 @@ class LinkSetController extends AbstractController
$aPrefillParam = array('source_obj' => $oSourceObj);
$oObj->PrefillForm('creation_from_editinplace', $aPrefillParam);
// We display this form in a modal, once we submit (in ajax) we probably want to only close the modal
$sFormOnSubmitJsCode =
<<<JS
$sFormOnSubmitJsCode =
<<<JS
event.preventDefault();
if(bOnSubmitForm === true)
{
@@ -180,6 +181,8 @@ class LinkSetController extends AbstractController
/* Alerts the results */
sPosting.done(function(data) {
// fire event
oForm.trigger('itop.form.submitted', [data]);
if(data.success !== undefined && data.success === true) {
oForm.closest('[data-role="ibo-modal"]').dialog('close');
}
@@ -195,18 +198,20 @@ class LinkSetController extends AbstractController
JS
;
$aExtraParams = [
'noRelations' => true,
'noRelations' => true,
'hide_transitions' => true,
'fieldsFlags' => $aFieldFlags,
'js_handlers' => [
'form_on_submit' => $sFormOnSubmitJsCode,
'formPrefix' => $sAttCode,
'fieldsFlags' => $aFieldFlags,
'js_handlers' => [
'form_on_submit' => $sFormOnSubmitJsCode,
'cancel_button_on_click' =>
<<<JS
function() {
$(this).closest('[data-role="ibo-modal"]').dialog('close');
};
JS
]
,
],
];
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, array(), $aExtraParams);
}
@@ -234,13 +239,50 @@ JS
return false;
JS
);
// - Add a select and a button to validate the form
$oClassForm->AddSubBlock(cmdbAbstractObject::DisplayBlockSelectClassToCreate( $sProposedRealClass, MetaModel::GetName($sProposedRealClass), $aPossibleClasses));
$oClassForm->AddSubBlock(cmdbAbstractObject::DisplayBlockSelectClassToCreate($sProposedRealClass, MetaModel::GetName($sProposedRealClass), $aPossibleClasses));
$oPage->AddUiBlock($oClassForm);
}
return $oPage;
}
/**
* OperationGetRemoteObject.
*
* @return JsonPage
*/
public function OperationGetRemoteObject(): JsonPage
{
$oPage = new JsonPage();
$bSuccess = true;
$aObjectData = null;
// Retrieve query params
$sObjectClass = utils::ReadParam('linked_object_class', '', false, utils::ENUM_SANITIZATION_FILTER_STRING);
$sObjectKey = utils::ReadParam('linked_object_key', '', false, utils::ENUM_SANITIZATION_FILTER_STRING);
$sRemoteClass = utils::ReadParam('remote_class', null, false, utils::ENUM_SANITIZATION_FILTER_STRING);
$sRemoteExtKey = utils::ReadParam('external_key_att_code', null, false, utils::ENUM_SANITIZATION_FILTER_STRING);
// Retrieve object
try {
$oObject = MetaModel::GetObject($sObjectClass, $sObjectKey);
$sLinkKey = $oObject->GetKey();
if ($sRemoteExtKey !== null) {
$oObject = MetaModel::GetObject($sRemoteClass, $oObject->Get($sRemoteExtKey));
}
$aObjectData = ObjectRepository::ConvertObjectToArray($oObject, $sObjectClass);
$aObjectData['link_keys'] = [$sObjectKey];
}
catch (Exception $e) {
$bSuccess = false;
}
return $oPage->SetData([
'object' => $aObjectData,
'success' => $bSuccess,
]);
}
}

View File

@@ -140,19 +140,8 @@ class ObjectRepository
$oDbObjectSet->Rewind();
while ($oObject = $oDbObjectSet->Fetch()) {
// Prepare objet data
$aObjectData = [];
// Object key
$aObjectData['key'] = $oObject->GetKey();
// Fill loaded columns...
foreach ($aFieldsToLoad as $sField) {
$aObjectData[$sField] = $oObject->Get($sField);
}
// Compute others data
$aResult[] = ObjectRepository::ComputeOthersData($oObject, $sObjectClass, $aObjectData, $aComplementAttributeSpec, $sObjectImageAttCode);
$aResult[] = self::ConvertObjectToArray($oObject, $sObjectClass, $aFieldsToLoad, $aComplementAttributeSpec, $sObjectImageAttCode);
}
return $aResult;
@@ -199,6 +188,9 @@ class ObjectRepository
{
try {
// object class
$aData['class_name'] = get_class($oDbObject);
// Obsolescence flag
$aData['obsolescence_flag'] = $oDbObject->IsObsolete();
@@ -260,4 +252,50 @@ class ObjectRepository
}
}
/**
* ConvertObjectToArray.
*
* @param DBObject $oObject
* @param string $sObjectClass
* @param array|null $aFieldsToLoad
* @param array|null $aComplementAttributeSpec
* @param string|null $sObjectImageAttCode
*
* @return array
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public static function ConvertObjectToArray(DBObject $oObject, string $sObjectClass, array $aFieldsToLoad = null, array $aComplementAttributeSpec = null, string $sObjectImageAttCode = null): array
{
// Retrieve friendly name complementary specification
if ($aComplementAttributeSpec === null) {
$aComplementAttributeSpec = MetaModel::GetNameSpec($sObjectClass, FriendlyNameType::COMPLEMENTARY);
}
// Retrieve image attribute code
if ($sObjectImageAttCode === null) {
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sObjectClass);
}
// Fields to load
if ($aFieldsToLoad === null) {
$aFieldsToLoad = self::GetDefaultFieldsToLoad($aComplementAttributeSpec, $sObjectImageAttCode);
}
// Prepare objet data
$aObjectData = [];
// Object key
$aObjectData['key'] = $oObject->GetKey();
// Fill loaded columns...
foreach ($aFieldsToLoad as $sField) {
$aObjectData[$sField] = $oObject->Get($sField);
}
// Compute others data
return ObjectRepository::ComputeOthersData($oObject, $sObjectClass, $aObjectData, $aComplementAttributeSpec, $sObjectImageAttCode);
}
}

View File

@@ -0,0 +1,8 @@
{# @copyright Copyright (C) 2010-2021 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% apply spaceless %}
{{ oUIBlock.GetWidgetName() }} = $('#{{ oUIBlock.GetId() }}').links_view_table({
link_class: '{{ oUIBlock.GetLinkedClass() }}',
external_key_to_me: '{{ oUIBlock.GetExternalKeyToMe() }}'
});
{% endapply %}

View File

@@ -1,4 +1,4 @@
<div id="datatable_dlg_{{ oUIBlock.GetTableId() }}" style="display: none;" class=" {{ oUIBlock.GetBlockCode() }}">
<div id="datatable_dlg_{{ oUIBlock.GetTableId() }}" style="display: none;" class=" {{ oUIBlock.GetBlockCode() }}" role="datatable-settings-dialog">
<input type="hidden" name="action" value="none">
<form id="form_{{ oUIBlock.GetTableId() }}" onsubmit="return false">
<div>

View File

@@ -18,7 +18,9 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
{# Remove button plugin #}
plugins: {
{# PLUGIN update operations #}
'combodo_update_operations' : {},
'combodo_update_operations' : {
initial: {{ oUIBlock.GetValue()|raw }},
},
{# PLUGIN combodo auto position #}
'combodo_auto_position' : {
maxDropDownHeight: 300, {# in px #}
@@ -73,7 +75,6 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
optgroups: {{ oDataProvider.GetOptionsGroups()|json_encode()|raw }},
{# Items data #}
initial: {{ oUIBlock.GetValue()|raw }},
items: {{ oUIBlock.GetValue()|raw }},
inputClass: 'ibo-input ibo-input-selectize ibo-input-set attribute-set selectize-input',
@@ -197,9 +198,12 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
{# plugin combodo_add_button #}
{% if oUIBlock.HasAddOptionButton() %}
onAdd: function(){
alert('todo on add option');
{{ oUIBlock.GetAddOptionButtonJsOnClick()|raw }}
},
{% endif %}
});