Implemented a new (optional) UI for managing 1:n linksets.

SVN:trunk[2290]
This commit is contained in:
Denis Flaven
2012-10-18 12:03:33 +00:00
parent c9d5743c4a
commit 53aefa895b
10 changed files with 677 additions and 72 deletions

View File

@@ -113,7 +113,7 @@ class ajax_page extends WebPage
}
if (count($this->m_aTabs) > 0)
{
$this->add_ready_script(
$this->add_ready_script(
<<<EOF
// The "tab widgets" to handle.
var tabs = $('div[id^=tabbedContent]');
@@ -183,7 +183,33 @@ EOF
$this->s_content = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $this->s_content);
$container_index++;
}
// Additional UI widgets to be activated inside the ajax fragment ??
if ($this->sContentType == 'text/html')
{
$this->add_ready_script(
<<<EOF
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd',
constrainInput: false,
changeMonth: true,
changeYear: true
});
$(".datetime-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd 00:00:00',
constrainInput: false,
changeMonth: true,
changeYear: true
});
EOF
);
}
$s_captured_output = ob_get_contents();
ob_end_clean();
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))

View File

@@ -36,6 +36,7 @@ require_once(APPROOT.'/application/applicationextension.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/application/applicationcontext.class.inc.php');
require_once(APPROOT.'/application/ui.linkswidget.class.inc.php');
require_once(APPROOT.'/application/ui.linksdirectwidget.class.inc.php');
require_once(APPROOT.'/application/ui.passwordwidget.class.inc.php');
require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php');
@@ -266,58 +267,25 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
if ($bEditMode && (!$bReadOnly))
{
$sInputId = $this->m_iFormId.'_'.$sAttCode;
if (get_class($oAttDef) == 'AttributeLinkedSet')
$sLinkedClass = $oAttDef->GetLinkedClass();
if ($oAttDef->IsIndirect())
{
// 1:n links
$sTargetClass = $oAttDef->GetLinkedClass();
if ($this->IsNew())
{
$oPage->p(Dict::Format('UI:BeforeAdding_Class_ObjectsSaveThisObject', MetaModel::GetName($sTargetClass)));
}
else
{
$oPage->p(MetaModel::GetClassIcon($sTargetClass)."&nbsp;".$oAttDef->GetDescription());
$oFilter = new DBObjectSearch($sTargetClass);
$oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey(),'=');
$aDefaults = array($oAttDef->GetExtKeyToMe() => $this->GetKey());
$oAppContext = new ApplicationContext();
foreach($oAppContext->GetNames() as $sKey)
{
// The linked object inherits the parent's value for the context
if (MetaModel::IsValidAttCode($sClass, $sKey))
{
$aDefaults[$sKey] = $this->Get($sKey);
}
}
$aParams = array(
'target_attr' => $oAttDef->GetExtKeyToMe(),
'object_id' => $this->GetKey(),
'menu' => true,
'default' => $aDefaults,
'table_id' => $sClass.'_'.$sAttCode,
);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oPage, $sInputId, $aParams);
}
}
else // get_class($oAttDef) == 'AttributeLinkedSetIndirect'
{
// n:n links
$sLinkedClass = $oAttDef->GetLinkedClass();
$oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote());
$sTargetClass = $oLinkingAttDef->GetTargetClass();
$oPage->p(MetaModel::GetClassIcon($sTargetClass)."&nbsp;".$oAttDef->GetDescription().'<span id="busy_'.$sInputId.'"></span>');
$sValue = $this->Get($sAttCode);
$sDisplayValue = ''; // not used
$aArgs = array('this' => $this);
$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
$aFieldsMap[$sAttCode] = $sInputId;
$oPage->add($sHTMLValue);
}
else
{
$sTargetClass = $sLinkedClass;
}
$oPage->p(MetaModel::GetClassIcon($sTargetClass)."&nbsp;".$oAttDef->GetDescription().'<span id="busy_'.$sInputId.'"></span>');
$oValue = $this->Get($sAttCode);
$sDisplayValue = ''; // not used
$aArgs = array('this' => $this);
$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $oValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
$aFieldsMap[$sAttCode] = $sInputId;
$oPage->add($sHTMLValue);
}
else
{
@@ -414,6 +382,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$iInputId = 0;
$aFieldsMap = array();
$aFieldsComments = (isset($aExtraParams['fieldsComments'])) ? $aExtraParams['fieldsComments'] : array();
$aExtraFlags = (isset($aExtraParams['fieldsFlags'])) ? $aExtraParams['fieldsFlags'] : array();
$bFieldComments = (count($aFieldsComments) > 0);
foreach($aDetailsStruct as $sTab => $aCols )
@@ -474,6 +443,11 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
$iFlags = $iFlags & ~OPT_ATT_READONLY; // Mandatory fields cannot be read-only when creating an object
}
if (array_key_exists($sAttCode, $aExtraFlags))
{
// the caller may override some flags if needed
$iFlags = $iFlags | $aExtraFlags[$sAttCode];
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0))
{
@@ -1608,12 +1582,19 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
break;
case 'LinkedSet':
if ($oAttDef->IsIndirect())
{
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed(), $aArgs);
}
else
{
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix, $aArgs);
}
$aEventsList[] ='validate';
$aEventsList[] ='change';
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed(), $aArgs);
$oObj = isset($aArgs['this']) ? $aArgs['this'] : null;
$sHTMLValue = $oWidget->Display($oPage, $value, array(), $sFormPrefix, $oObj);
break;
break;
case 'Document':
$aEventsList[] ='validate';
@@ -2434,6 +2415,50 @@ EOF
$this->Set($sAttCode, $iValue);
}
}
else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() && ($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE))
{
$oLinkset = $this->Get($sAttCode);
$sLinkedClass = $oLinkset->GetClass();
$aObjSet = array();
$oLinkset->Rewind();
$bModified = false;
while($oLink = $oLinkset->Fetch())
{
if (in_array($oLink->GetKey(), $value['to_be_deleted']))
{
// The link is to be deleted, don't copy it in the array
$bModified = true;
}
else
{
$aObjSet[] = $oLink;
}
}
if (array_key_exists('to_be_created', $value) && (count($value['to_be_created']) > 0))
{
// Now handle the lniks to be created
foreach($value['to_be_created'] as $aData)
{
$sSubClass = $aData['class'];
if ( ($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass)) )
{
$aObjData = $aData['data'];
$oLink = new $sSubClass;
$oLink->UpdateObjectFromArray($aObjData);
$aObjSet[] = $oLink;
$bModified = true;
}
}
}
if ($bModified)
{
$oNewSet = DBObjectSet::FromArray($oLinkset->GetClass(), $aObjSet);
$this->Set($sAttCode, $oNewSet);
}
}
else
{
if (!is_null($value))
@@ -2466,6 +2491,28 @@ EOF
{
$value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'));
}
else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() && ($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE))
{
$aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}', 'raw_data'), true);
$aToBeCreated = array();
foreach($aRawToBeCreated as $aData)
{
$sSubFormPrefix = $aData['formPrefix'];
$sObjClass = $aData['class'];
$aObjData = array();
foreach($aData as $sKey => $value)
{
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches))
{
$aObjData[$aMatches[1]] = $value;
}
}
$aToBeCreated[] = array('class' => $sObjClass, 'data' => $aObjData);
}
$value = array('to_be_created' => $aToBeCreated,
'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', 'raw_data'), true) );
}
else
{
$value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');

View File

@@ -568,11 +568,13 @@ class DisplayBlock
{
if ((UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
{
$sLinkTarget = '';
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink();
// 1:n links, populate the target object as a default value when creating a new linked object
if (isset($aExtraParams['target_attr']))
{
$sLinkTarget = ' target="_blank" ';
$aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id'];
}
$sDefault = '';
@@ -584,7 +586,7 @@ class DisplayBlock
}
}
$sHtml .= $oPage->GetP("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sClass&$sParams{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sClass))."</a>\n");
$sHtml .= $oPage->GetP("<a{$sLinkTarget} href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sClass&$sParams{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sClass))."</a>\n");
}
}
}

View File

@@ -281,7 +281,7 @@ EOF
changeMonth: true,
changeYear: true
});
$(".datetime-pick").datepicker({
$(".datetime-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
@@ -290,23 +290,6 @@ EOF
changeMonth: true,
changeYear: true
});
// Restore the persisted sortable order, for all sortable lists... if any
$('.sortable').each(function()
{
var sTemp = GetUserPreference(this.id+'_order', undefined);
if (sTemp != undefined)
{
var aSerialized = sTemp.split(',');
var sortable = $(this);
$.each(aSerialized, function(i,v) {
var item = $('#menu_'+v);
if (item.length > 0) // Check that the menu exists
{
sortable.append(item);
}
});
}
});
// Make sortable, everything that claims to be sortable
$('.sortable').sortable( {axis: 'y', cursor: 'move', handle: '.drag_handle', stop: function()

View File

@@ -0,0 +1,238 @@
<?php
// Copyright (C) 2012 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
class UILinksWidgetDirect
{
protected $sClass;
protected $sAttCode;
protected $sInputid;
protected $sNameSuffix;
protected $sLinkedClass;
public function __construct($sClass, $sAttCode, $sInputId, $sNameSuffix = '')
{
$this->sClass = $sClass;
$this->sAttCode = $sAttCode;
$this->sInputid = $sInputId;
$this->sNameSuffix = $sNameSuffix;
$this->aZlist = array();
$this->sLinkedClass = '';
// Compute the list of attributes visible from the given objet:
// All the attributes from the "list" Zlist of the Link class except
// the ExternalKey that points to the current object and its related external fields
$oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$this->sLinkedClass = $oLinksetDef->GetLinkedClass();
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$aZList = MetaModel::FlattenZList(MetaModel::GetZListItems($this->sLinkedClass, 'list'));
foreach($aZList as $sLinkedAttCode)
{
if ($sLinkedAttCode != $sExtKeyToMe)
{
$oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode);
if (!$oAttDef->IsExternalField() || ($oAttDef->GetKeyAttCode() != $sExtKeyToMe) )
{
$this->aZlist[] = $sLinkedAttCode;
}
}
}
}
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
{
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
switch($oLinksetDef->GetEditMode())
{
case LINKSET_EDITMODE_NONE: // The linkset is read-only
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
break;
case LINKSET_EDITMODE_ADDONLY: // The only possible action is to open (in a new window) the form to create a new object
if ($oCurrentObj && !$oCurrentObj->IsNew())
{
$sTargetClass = $oLinksetDef->GetLinkedClass();
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$sDefault = "default[$sExtKeyToMe]=".$oCurrentObj->GetKey();
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink();
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
}
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
break;
case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place'
$this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj);
break;
case LINKSET_EDITMODE_ACTIONS:
default:
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/);
}
}
protected function DisplayAsBlock(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
{
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$sTargetClass = $oLinksetDef->GetLinkedClass();
if ($oCurrentObj && $oCurrentObj->IsNew() && $bDisplayMenu)
{
$oPage->p(Dict::Format('UI:BeforeAdding_Class_ObjectsSaveThisObject', MetaModel::GetName($sTargetClass)));
}
else
{
$oFilter = new DBObjectSearch($sTargetClass);
$oFilter->AddCondition($oLinksetDef->GetExtKeyToMe(), $oCurrentObj->GetKey(),'=');
$aDefaults = array($oLinksetDef->GetExtKeyToMe() => $oCurrentObj->GetKey());
$oAppContext = new ApplicationContext();
foreach($oAppContext->GetNames() as $sKey)
{
// The linked object inherits the parent's value for the context
if (MetaModel::IsValidAttCode($this->sClass, $sKey) && $oCurrentObj)
{
$aDefaults[$sKey] = $oCurrentObj->Get($sKey);
}
}
$aParams = array(
'target_attr' => $oLinksetDef->GetExtKeyToMe(),
'object_id' => $oCurrentObj ? $oCurrentObj->GetKey() : null,
'menu' => $bDisplayMenu,
'default' => $aDefaults,
'table_id' => $this->sClass.'_'.$this->sAttCode,
);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oPage, $this->sInputid, $aParams);
}
}
protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
{
$aAttribs = $this->GetTableConfig();
$oValue->Rewind();
$oPage->add('<table class="listContainer" id="'.$this->sInputid.'"><tr><td>');
$aData = array();
while($oLinkObj = $oValue->Fetch())
{
$aRow = array();
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
foreach($this->aZlist as $sLinkedAttCode)
{
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
}
$aData[] = $aRow;
}
$oPage->table($aAttribs, $aData);
$oPage->add('</td></tr></table>'); //listcontainer
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName' });");
}
public function GetObjectCreationDlg(WebPage $oPage, $sProposedRealClass = '')
{
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
// and that the current user is allowed to create objects of this class
$sRealClass = '';
$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div>');
$aSubClasses = MetaModel::EnumChildClasses($this->sLinkedClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$aPossibleClasses = array();
foreach($aSubClasses as $sCandidateClass)
{
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
{
if ($sCandidateClass == $sProposedRealClass)
{
$sRealClass = $sProposedRealClass;
}
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
}
}
// Only one of the subclasses can be instantiated...
if (count($aPossibleClasses) == 1)
{
$aKeys = array_keys($aPossibleClasses);
$sRealClass = $aKeys[0];
}
if ($sRealClass != '')
{
$oPage->add("<h1>".MetaModel::GetClassIcon($sRealClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($sRealClass))."</h1>\n");
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$aFieldFlags = array( $sExtKeyToMe => OPT_ATT_HIDDEN);
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, null, array(), array('formPrefix' => $this->sInputid, 'noRelations' => true, 'fieldsFlags' => $aFieldFlags));
}
else
{
$sClassLabel = MetaModel::GetName($this->sLinkedClass);
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
$oPage->add('<nobr><select name="class">');
asort($aPossibleClasses);
foreach($aPossibleClasses as $sClassName => $sClassLabel)
{
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
}
$oPage->add('</select>');
$oPage->add('&nbsp; <button type="button" onclick="$(\'#'.$this->sInputid.'\').directlinks(\'subclassSelected\');">'.Dict::S('UI:Button:Apply').'</button><span class="indicator" style="display:inline-block;width:16px"></span></nobr></p>');
}
$oPage->add('</div></div>');
}
public function GetObjectModificationDlg()
{
}
protected function GetTableConfig()
{
$aAttribs = array();
$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->sInputid}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
foreach($this->aZlist as $sLinkedAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode);
$aAttribs[$sLinkedAttCode] = array('label' => MetaModel::GetLabel($this->sLinkedClass, $sLinkedAttCode), 'description' => $oAttDef->GetOrderByHint());
}
return $aAttribs;
}
public function GetRow($oPage, $sRealClass, $aValues, $iTempId)
{
$aAttribs = $this->GetTableConfig();
if ($sRealClass == '')
{
$sRealClass = $this->sLinkedClass;
}
$oLinkObj = new $sRealClass();
$oLinkObj->UpdateObjectFromPostedForm($this->sInputid);
$aRow = array();
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.(-$iTempId).'"/>';
foreach($this->aZlist as $sLinkedAttCode)
{
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
}
return $oPage->GetTableRow($aRow, $aAttribs);
}
public function UpdateFromArray($oObj, $aData)
{
}
}

View File

@@ -85,6 +85,10 @@ define('LINKSET_TRACKING_LIST', 1); // Do track added/removed items
define('LINKSET_TRACKING_DETAILS', 2); // Do track modified items
define('LINKSET_TRACKING_ALL', 3); // Do track added/removed/modified items
define('LINKSET_EDITMODE_NONE', 0); // The linkset cannot be edited at all from inside this object
define('LINKSET_EDITMODE_ADDONLY', 1); // The only possible action is to open a new window to create a new object
define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
/**
@@ -563,7 +567,7 @@ class AttributeLinkedSet extends AttributeDefinition
return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max"));
}
public function GetEditClass() {return "List";}
public function GetEditClass() {return "LinkedSet";}
public function IsWritable() {return true;}
public function IsLinkSet() {return true;}
@@ -593,6 +597,11 @@ class AttributeLinkedSet extends AttributeDefinition
return $this->GetOptional('tracking_level', LINKSET_TRACKING_LIST);
}
public function GetEditMode()
{
return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
}
public function GetLinkedClass() {return $this->Get('linked_class');}
public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');}

249
js/linksdirectwidget.js Normal file
View File

@@ -0,0 +1,249 @@
// jQuery UI style "widget" for managing 1:n links "in-place"
$(function()
{
// the widget definition, where "itop" is the namespace,
// "directlinks" the widget name
$.widget( "itop.directlinks",
{
// default options
options:
{
input_name: '',
class_name: '',
att_code: '',
submit_to: GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
submit_parameters: {},
labels: { 'delete': 'Delete',
modify: 'Modify...' ,
creation_title: 'Creation of a new object...' ,
create: 'Create...'
}
},
// the constructor
_create: function()
{
var me = this;
this.id = this.element.attr('id');
this.element
.addClass('itop-directlinks');
this.datatable = this.element.find('table.listResults');
this.deleteBtn = $('<button type="button">' + this.options.labels['delete'] + '</button>');
this.modifyBtn = $('<button type="button">' + this.options.labels['modify'] + '</button>');
this.createBtn = $('<button type="button">' + this.options.labels['create'] + '</button>');
this.indicator = $('<span></span>');
this.inputToBeCreated = $('<input type="hidden" name="'+this.options.input_name+'_tbc" value="{}">');
this.toBeCreated = {};
this.inputToBeDeleted = $('<input type="hidden" name="'+this.options.input_name+'_tbd" value="[]">');
this.toBeDeleted = [];
this.element
.after(this.inputToBeCreated)
.after(this.inputToBeDeleted)
.after('<span style="float:left">&nbsp;&nbsp;&nbsp;<img src="../images/tv-item-last.gif">&nbsp;&nbsp;&nbsp;')
.after(this.indicator).after(this.createBtn).after('&nbsp;&nbsp;&nbsp')
.after(this.modifyBtn).after('&nbsp;&nbsp;&nbsp')
.after(this.deleteBtn);
this.element.find('.selectList'+this.id).bind('change', function() { me._updateButtons(); });
this.deleteBtn.click(function() {
$('.selectList'+me.id+':checked', me.element).each( function() { me._deleteRow($(this)); });
});
this.createBtn.click(function() {
me._createRow();
});
this.modifyBtn.hide(); //hidden for now since it's not yet implemented
this._updateButtons();
},
// called when created, and later when changing options
_refresh: function()
{
this._updateButtons();
},
// events bound via _bind are removed automatically
// revert other modifications here
destroy: function()
{
this.element
.removeClass('itop-directlinks');
// call the original destroy method since we overwrote it
$.Widget.prototype.destroy.call( this );
},
// _setOptions is called with a hash of all options that are changing
_setOptions: function()
{
// in 1.9 would use _superApply
$.Widget.prototype._setOptions.apply( this, arguments );
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
{
// in 1.9 would use _super
$.Widget.prototype._setOption.call( this, key, value );
if (key == 'fields') this._refresh();
},
_updateButtons: function()
{
var oChecked = $('.selectList'+this.id+':checked', this.element);
switch(oChecked.length)
{
case 0:
this.deleteBtn.attr('disabled', 'disabled');
this.modifyBtn.attr('disabled', 'disabled');
break;
case 1:
this.deleteBtn.removeAttr('disabled');
this.modifyBtn.removeAttr('disabled');
break;
default:
this.deleteBtn.removeAttr('disabled');
this.modifyBtn.attr('disabled', 'disabled');
break;
}
},
_updateTable: function()
{
var me = this;
this.datatable.trigger("update").trigger("applyWidgets");
this.datatable.tableHover();
this.datatable.find('.selectList'+this.id).bind('change', function() { me._updateButtons(); });
},
_updateDlgSize: function()
{
this.oDlg.dialog('option', { position: { my: "center", at: "center", of: window }});
},
_createRow: function()
{
this.createBtn.attr('disabled', 'disabled');
this.indicator.html('<img src="../images/indicator.gif">');
oParams = this.options.submit_parameters;
oParams.operation = 'createObject';
oParams['class'] = this.options.class_name;
oParams.real_class = '';
oParams.att_code = this.options.att_code;
oParams.iInputId = this.id;
var me = this;
$.post(this.options.submit_to, oParams, function(data){
me.oDlg = $('<div></div>');
$('body').append(me.oDlg);
me.oDlg.html(data);
me.oDlg.find('form').removeAttr('onsubmit').bind('submit', function() { me._onCreateRow(); return false; } );
me.oDlg.find('button.cancel').unbind('click').click( function() { me.oDlg.dialog('close'); } );
me.oDlg.dialog({
title: me.options.labels['creation_title'],
modal: true,
width: 'auto',
height: 'auto',
position: { my: "center", at: "center", of: window },
close: function() { me._onDlgClose(); }
});
me.indicator.html('');
me.createBtn.removeAttr('disabled');
me._updateDlgSize();
});
},
subclassSelected: function()
{
var sRealClass = this.oDlg.find('select[name="class"]').val();
oParams = this.options.submit_parameters;
oParams.operation = 'createObject';
oParams['class'] = this.options.class_name;
oParams.real_class = sRealClass;
oParams.att_code = this.options.att_code;
oParams.iInputId = this.id;
var me = this;
me.oDlg.find('button').attr('disabled', 'disabled');
me.oDlg.find('span.indicator').html('<img src="../images/indicator.gif">');
$.post(this.options.submit_to, oParams, function(data){
me.oDlg.html(data);
me.oDlg.find('form').removeAttr('onsubmit').bind('submit', function() { me._onCreateRow(); return false; } );
me.oDlg.find('button.cancel').unbind('click').click( function() { me.oDlg.dialog('close'); } );
me._updateDlgSize();
});
},
_onCreateRow: function()
{
// Validate the form
var sFormId = this.oDlg.find('form').attr('id');
if (CheckFields(sFormId, true))
{
// Gather the values from the form
oParams = this.options.submit_parameters;
var oValues = {};
this.oDlg.find(':input').each( function() {
if (this.name != '')
{
oParams[this.name] = this.value;
oValues[this.name] = this.value;
}
});
var nextIdx = 0;
for(k in this.toBeCreated)
{
nextIdx++;
}
nextIdx++;
this.toBeCreated[nextIdx] = oValues;
this.inputToBeCreated.val(JSON.stringify(this.toBeCreated));
this.oDlg.dialog('close');
oParams = this.options.submit_parameters;
oParams.operation = 'getLinksetRow';
oParams['class'] = this.options.class_name;
oParams.att_code = this.options.att_code;
oParams.iInputId = this.id;
oParams.tempId = nextIdx;
var me = this;
this.createBtn.attr('disabled', 'disabled');
this.indicator.html('<img src="../images/indicator.gif">');
$.post(this.options.submit_to, oParams, function(data){
me.datatable.find('tbody').append(data);
me._updateTable();
me.indicator.html('');
me.createBtn.removeAttr('disabled');
});
}
},
_onDlgClose: function()
{
this.oDlg.remove();
this.oDlg = null;
},
_deleteRow: function(oCheckbox)
{
var iObjKey = parseInt(oCheckbox.val(), 10); // Number in base 10
if (iObjKey > 0)
{
// Existing objet: add it to the "to be deleted" list
this.toBeDeleted.push(iObjKey);
this.inputToBeDeleted.val(JSON.stringify(this.toBeDeleted));
}
else
{
// Object to be created, just remove it from the "to be created" list
this.toBeCreated[-iObjKey] = undefined;
this.inputToBeCreated.val(JSON.stringify(this.toBeCreated));
}
// Now remove the row from the table
oRow = oCheckbox.closest('tr');
oRow.remove();
this._updateButtons();
this._updateTable();
}
});
});

View File

@@ -532,6 +532,7 @@ try
$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/linksdirectwidget.js");
$oP->add_linked_script("../js/extkeywidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js");
break;

View File

@@ -238,6 +238,30 @@ try
$oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked);
break;
//ui.linksdirectwidget
case 'createObject':
$sClass = utils::ReadParam('class', '', false, 'class');
$sRealClass = utils::ReadParam('real_class', '', false, 'class');
$sAttCode = utils::ReadParam('att_code', '');
$iInputId = utils::ReadParam('iInputId', '');
$oPage->SetContentType('text/html');
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId);
$oWidget->GetObjectCreationDlg($oPage, $sRealClass);
break;
// ui.linksdirectwidget
case 'getLinksetRow':
$sClass = utils::ReadParam('class', '', false, 'class');
$sRealClass = utils::ReadParam('real_class', '', false, 'class');
$sAttCode = utils::ReadParam('att_code', '');
$iInputId = utils::ReadParam('iInputId', '');
$iTempId = utils::ReadParam('tempId', '');
$aValues = utils::ReadParam('values', array(), false, 'raw_data');
$oPage->SetContentType('text/html');
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId);
$oPage->add($oWidget->GetRow($oPage, $sRealClass, $aValues, $iTempId));
break;
////////////////////////////////////////////////////////////
// ui.extkeywidget

View File

@@ -307,7 +307,28 @@ EOF;
return $aXmlToPHP[$sTrackingLevel];
}
/**
* Helper to format the edit-mode for direct linkset
* @param string $sEditMode Value set from within the XML
* Returns string PHP flag
*/
protected function EditModeToPHP($sEditMode)
{
static $aXmlToPHP = array(
'none' => 'LINKSET_EDITMODE_NONE',
'add_only' => 'LINKSET_EDITMODE_ADDONLY',
'actions' => 'LINKSET_EDITMODE_ACTIONS',
'in_place' => 'LINKSET_EDITMODE_INPLACE',
);
if (!array_key_exists($sEditMode, $aXmlToPHP))
{
throw new exception("Edit mode: unknown value '$sTrackingLevel'");
}
return $aXmlToPHP[$sEditMode];
}
/**
* Format a path (file or url) as an absolute path or relative to the module or the app
*/
@@ -584,6 +605,11 @@ EOF;
{
$aParameters['tracking_level'] = $this->TrackingLevelToPHP($sTrackingLevel);
}
$sEditMode = $oField->GetChildText('edit_mode');
if (!is_null($sEditMode))
{
$aParameters['edit_mode'] = $this->EditModeToPHP($sEditMode);
}
$aParameters['depends_on'] = $sDependencies;
}
elseif ($sAttType == 'AttributeExternalKey')