@doc alias="interactor" hierarchy="GMLDOM">
Represents tightly coupled unit of interaction, that enables the user to view application data and to carry out related tasks.
Interactors are displayed in rectangular regions and are arranged using a hierarchy of containers to form the user interface display
(c) SAP AG 2003-2006. All rights reserved.
#ALIAS[g=core.svg:GmlDrawing]
#INCLUDE[svg:defs.inc]
///////////////////////////////////////////////////////////////////////
// CLASS HEADER
Class Interactor inherit Configurable;
attach InteractorLayout;
metadata title = '#TEXT[XTIT_INTERACTOR]';
metadata descr = '#TEXT[XTOL_INTERACTOR]';
metadata namestem = 'Interactor';
metadata configurationType = 'core.gml:BOInfoshape';
//metadata configurationType = ''; //TODO: see if we can optimize so that no BOInfoshape is created in Interactor creation
// Depreciated in AP5
//metadata defaultControls = {} ;
// Depreciated in AP5
//metadata allowedControls = [] ;
attach svg:Bipolar override {
fillColor: '#F2F5F8',
strokeColor: '#6183A9',
textColor: '#6183A9',
geometry: {
resizeMode: #[SVG_KEEP_WIDTH],
rotateMode: #[SVG_ROTATE_FULL|SVG_FLIP_VBODY],
defWidth: 80,
defHeight: 60,
minWidth: 80,
minHeight: 60,
poleStart: 20,
poleEnd: 20,
padding: [0,10,0,10]
},
frameParts: [
{type:'rect', x:'@x1', y:'@y1', width:'@w', height:'@h', rx:10, fill:'@fillColor', stroke:'currentColor', 'stroke-width':2}
],
bodyParts: [
{type:'image', href:'@Class.metadata.icon32', x:-16, y:-8.5, width:32, height:32},
{type:'text', action:'rename', text:'((@name).length > $ENV.maxNameLength ? (@name).substring(0,$ENV.maxNameLength)+"..." : @name )', x:0, y:'@y1+12', 'font-size':'90%', fill:'@textColor', stroke:'none', 'text-anchor':'middle'},
{type:'text', text:'(@tags ? "<<"+@tags+">>" : "")', x:0, y:'@y2-2', 'font-size':'75%', fill:'@textColor', 'text-anchor':'middle'}
]
}
///////////////////////////////////////////////////////////////////////
// PROPERTIES
/*<@doc scope="private">
DEPRECIATED. A pointer to ControlSet object that holds interactor's controls
Don't use this property. Use the @getControls() method instead.
*/
// Deperciated in AP5
// strong property controls = ^ControlSet;
<@doc scope="private">
A hierarchial collection of the interactor's controls.
Don't use this property directly. Use the @getControls() method instead.
strong property uicontrols = ^Control[id];
<@doc>Position, in format 'X Y', of this object in its parent UI container, in parent client coordinates.
property pos = '0 0';
<@doc>Size, in format 'W H', of this object on the screen.
property size = '320 240';
<@doc>Defines the index of this object in its parent object layout calculation.
property index = 0;
<@doc>Specifies if this object is drawn in the Layout Board. Design-time only.
readonly property showInLayout = true;
<@doc>Defines interactor's toolbar, if any.
strong property toolbar = ^Toolbar;
<@doc>Indicates whether to show the title bar, if applicable
property showTitlebar = true;
<@doc>
Indicates whether to show the toolbar, if applicable
Event if this property is ~false, the @toolbar property can have valid toolbar defintion.
In such case, the toolbar will not be displayed.
property showToolbar = false;
metadata for showToolbar = {
editor: 'check',
group: 'General',
label: '#TEXT[XCKL_HAS_TOOLBAR]'
}
<@doc>Indicates whether to show the paging bar, if applicable
property showPagingbar = false;
<@doc>Column Layout specific. Gets or sets interactor's colspan
property colspan = 3;
<@doc>The actions of this interactor
strong property actions = ^Action[id];
<@doc scope="private">
Holds a mapping between field id to controls, which is bound to that field.
Don't use this property directly. Use the @getField2ControlMapping() method instead.
virtual property field2Controls = null ;
<@doc scope="private">
Holds a mapping between an action id to a control id.
Don't use this property directly. Use the @getAction2ControlMapping() method instead.
virtual property action2Control = null;
///////////////////////////////////////////////////////////////////////
// METHODS
override method onInsertMe(parent, trans)
this.supercall();
$ENV.organizer.addElement(this, parent);
end
override method onRemoveMe(parent, trans)
this.supercall();
$ENV.organizer.delElement(this, parent);
end
<@doc>
Get Interactor controls.
Defines if the method will also return inner controls, residing in control groups.
The collection of controls.
method getControls(deep)
if (!deep) return this.uicontrols;
var controls = {};
addInnerControls(this);
return controls;
function addInnerControls(parent) {
var ctls = parent.getControls();
for (var iter in ctls) {
var ctl = ctls[iter];
controls[iter] = ctl;
if (ISA(ctl, 'core.gml:ControlGroup')) addInnerControls(ctl);
}
}
end
override method onCreate()
this.supercall();
this.createElement('core.gml:Inplug', 'plugs', {name:'in'});
this.createElement('core.gml:Outplug', 'plugs', {name:'out'});
this.createElement('core.gml:DirectPlug', 'plugs');
this.createElement('core.gml:Toolbar', 'toolbar');
end
//
// AP6: For AP5 model compatibitily - if there's no toolbar - create one.
// NOTE: If this will be handled by a migration step - remove this method
//
override method onLoad()
this.supercall();
if (this.toolbar == null) this.createElement('core.gml:Toolbar', 'toolbar');
end
<@doc>
A callback method that is called if this object's @gml:RefInfoshape! is modified, or the @gml:BOInfoshape object it points to is modified.
the type of change of infoshape
An object containing data related to the change in the infoshape
See more details on the parameters passed to this method in @Infoshape!notifyOnChange.
Override this method to handle changes in this object as a result of changes in the related infoshape.
override method notifyOnInfoshapeChange(changeType, changeData)
this.supercall();
switch (changeType)
{
case #[INFOSHAPE_CHANGE_NONE]:
break;
case #[INFOSHAPE_CHANGE_FIELD_ADD]:
this.onBOFieldsAdd(changeData.fields);
break;
case #[INFOSHAPE_CHANGE_FIELD_DELETE]:
this.onBOFieldsRemove(changeData.fields);
break;
case #[INFOSHAPE_CHANGE_FIELD_UPDATE]:
this.onBOFieldUpdate(changeData.field, changeData.evt);
break;
case #[INFOSHAPE_CHANGE_REF_REPLACED]:
//if point to same BO infoshape - do nothing
var old_ref = $ENV.model.infoshape_pool.getRefInfoshape(changeData.old_id);
var new_ref = $ENV.model.infoshape_pool.getRefInfoshape(changeData.new_id);
var oldBO = old_ref && old_ref.getBOInfoshape() || null;
var newBO = new_ref && new_ref.getBOInfoshape() || null;
if (oldBO && newBO &&
oldBO.getUniqueID() == newBO.getUniqueID())
break;
this.onBOInfoshapesReplace(oldBO, newBO);
break;
case #[INFOSHAPE_CHANGE_BO_REPLACED]:
var oldBO = $ENV.model.infoshape_pool.getBOInfoshape(changeData.old_bo);
var newBO = $ENV.model.infoshape_pool.getBOInfoshape(changeData.new_bo);
this.onBOInfoshapesReplace(oldBO, newBO);
break;
default:
throw new Error(-1, "Interactor.notifyOnInfoshapeChange - invalid changeType " + changeType);
}
end
<@doc>
A callback function which been called whenever fields are been removed from the BOInfoshape
An array of fields object
method onBOFieldsRemove(fields)
if (!fields || !fields.length) return;
try{
BEGIN('Delete controls due to fields deletion in BOInfoshape');
// remove control for deleted fields
for (var i = 0; i< fields.length; ++i)
this.removeControlsByField(fields[i]);
COMMIT();
}catch(e){
ROLLBACK();
}
end
<@doc>
A callback funtction which been called whenever fields are been added to the BOInfoshape
An array of fields object
method onBOFieldsAdd(fields)
// Commented out due to request from the pattern side
/*if (!fields || !fields.length) return;
try{
// add default control for new fields
BEGIN('Add contrls due to fields been added in the BOInfoshape');
for (var i = 0; i< fields.length; ++i)
this.addDefaultControl(fields[i]);
COMMIT();
}catch(e){
ROLLBACK();
}*/
end
<@doc>
A callback function which been called whenever a field has been update in the BOInfoshape
A field object
An event object
method onBOFieldUpdate(field,evt)
// TODO - define logic of field update...
return;
end
<@doc>
A callback function which is executed whenever BOInfoshape is replaced for the interactor
The old BOInfoshape
The new BOInfoshape
method onBOInfoshapesReplace(oldBO, newBO)
if (!oldBO && !newBO) return onFinish();
if (!oldBO && newBO) return onFinish();
if (oldBO && !newBO) {
// this.removeAllControls();
return onFinish();
}
var controls = this.getControls(true);
var oldFields = oldBO.getFields();
var newFields = newBO.getFields();
var fieldsByAppName = {};
for (var id in newFields){
fieldsByAppName[newFields[id].getAppName()] = newFields[id];
}
try{
BEGIN('Updating Controls due to BOInfoshapes replace');
for (var id in controls){
if(!ISA(controls[id],'gml:SimpleControl')) continue;
var oldField = controls[id].getBoundField(oldBO);
if (oldField){
appName = oldField.getAppName(); // get app name from the BO
} else {
appName = controls[id].value.replace(/^=@(.+)$/,function (a,b){ return(b);}); // app name is stored on the value property
}
var newField = fieldsByAppName[ appName ];
// if there is no field in the new BOInfoshape with the same appName than replace the field ID with the field name
if(!newField) {
//this.deleteControl(controls[id]);
controls[id].setBoundField(appName); // instead of delete - store the field name - more firendly and can be used for later matching with new service
continue;
}
// there is a corresponding field in the new BOInfoshape than change the value property of that field.
controls[id].setBoundField(newField.id);
}
COMMIT();
}catch(e){
ROLLBACK();
throw new Error(-1 , e.description ) ;
}
return onFinish();
function onFinish(){
this.field2Controls = null;
}
end
/*
<@doc>
Add interactor default control according to the field passed to the method.
When this method is used, the Control created is strongly bound to the Field,
i.e. Control's 'field' property is set to point to @gml:Field. Such control is automatically
updated/deleted when the field/infoshape is changing.
This method cannot be overriden. In order to change the control type selection
per field type, extend 'createDefaultControl' rule.
The field to be attached to the new control.
The newly created control, or ~null in the case of any error.
// DEPRECATED in AP6.5
sealed method addDefaultControl(field)
return RULE('createDefaultControl', this.unit, this, field);
end
*/
<@doc>
Check if the object has personalization implications
~true if it does has personalization implications, ~false otherwise
override method canBePersonalized()
return(true); // This Class can be personalized in RT by the end user (ByD application user)
end
<@doc>
Gets the controls allowed for this interactor with respect to a bound field
method defineInputType(field)
var typeControls = RULE('getControlsPerAppType', this.unit, field.getAppType());
if(ISEMPTY(typeControls)){
#LOG[1, 'Error - Missing control definitions for a data type'];
return null;
}
var allowedControlsLst = this.getAllowedControls();
var jointControls = [];
if(ISEMPTY(allowedControlsLst)){
jointControls = typeControls;
}
else{
// Get the intersection of the two lists
for (var i in typeControls){
for (var j in allowedControlsLst){
if (typeControls[i] == allowedControlsLst[j]){
jointControls.push(typeControls[i]);
break;
}
}
}
}
var len = jointControls && jointControls.length; if (!len) return null;
return jointControls;
method createDefaultControl(field)
var ctlClassName = null;
var allowedControlsLst=[], typeControls=[];
var vType = null;
if(!(field && ISA(field, 'dev:IField')))
{
#LOG[4, 'Error - expecting a field but got a wrong paramter - Cannot create bound control'];
return;
}
var type = field.getAppType();
if(!type)
{
#LOG[4, 'Error - type definition for a field - Cannot create bound control'];
return;
}
var appTypes = RULE('getAppTypesList', this.unit);
if(!appTypes)
{
#LOG[4, 'Failed to get application type list (RULE) - Cannot create bound control'];
return
}
// Check that the field returned a valid type:
for(var i=0; i
Gets the field2Controls mapping.
Field id to control mapping
Used for lazy evalution only.
The mapping only includes controls which are strongly mapped to field,
i.e. control's 'field' property is not ~null and points to a valid @gml:Field object
method getField2ControlsMapping()
if (this.field2Controls && !this.dirtyMapping) return this.field2Controls ;
var mapping = {};
addInnerControls(this);
addInnerControls(this.toolbar);
this.field2Controls = mapping;
this.dirtyMapping = false;
return this.field2Controls;
function addInnerControls(parent) {
var ctls = parent.getControls();
for (var iter in ctls) {
var ctl = ctls[iter];
var field = ctl.getProperty('fieldRef');
if (field && field.id) {
if (!mapping[field.id]) mapping[field.id] = {};
mapping[field.id][ctl.id] = ctl;
continue;
}
if (ISA(ctl, 'core.gml:ControlGroup')) addInnerControls(ctl);
}
}
end
<@doc>
Remove controls that are attached to the field.
The field which is strongly attached to a control
Returns ~true if the control has been deleteted
method removeControlsByField(field)
var fieldId = field && field.id || null ;
if (fieldId == null) return false ;
var mapping = this.getField2ControlsMapping();
var controls = mapping[fieldId];
if (controls == null) return false;
try{
BEGIN('Removing controls by field ' + fieldId);
for (var id in controls){
this.deleteControl(controls[id]);
}
COMMIT();
return true;
}catch(e){
ROLLBACK();
return false;
}
end
<@doc scope="private">
Delete a control from the interactor.
The control to be deleted.
Flag to decide if children controls are to be deleted
Returns ~true if the control has been deleted.
method deleteControl(control,noChildren)
try{
BEGIN('remove control from the group/interactor');
if (!control) return false;
var mapping = this.getField2ControlsMapping();
if (ISA(control, 'core.gml:ControlGroup') && !noChildren) {
var controls = control.getControls();
for (var iter in controls) {
var ctl = controls[iter];
this.removeActionIfExists(ctl);
$ENV.recorder.execute('doRemoveControl', this, control, ctl, mapping);
}
}
var group = control.parent;
if (!ISA(group, 'core.gml:ControlGroup')) group = null;
this.removeActionIfExists(control);
$ENV.recorder.execute('doRemoveControl', this, group, control, mapping);
COMMIT();
return true;
}catch(e){
#LOG[4,'Error while removing control from the interactor, '+ e.description ];
ROLLBACK();
}
end
<@doc scope="private">
Updates the field to controls mapping.
The bound control
The old field which the control was bound to.
The new field which the control will be bound to.
method updateMapping(control , oldField, newField)
var mapping = this.getField2ControlsMapping();
$ENV.recorder.execute('doUpdateMapping',this, control, oldField, newField,mapping);
end
<@doc scope="private">
Checks if the given control has an action, and if it does, removes the action
The control object
method removeActionIfExists(control)
if (ISA(control, 'core.dev:IActionControl') && control.hasAction()) {
var action = this.getActionByControl(control);
this.removeAction(action, control);
}
end
<@doc scope="private">
Create a duplicate control and insert it to the Interactor.
The control class name to be created.
A group to which this new control will be inserted. If ~null the new control is inserted as a direct child of Interactor
A collection of property values for initializing the new Control
The control that was addded to the Interactor, or ~null on failure.
method createDuplicateControl(controlClass, group, values,type,id)
if (controlClass == null) return null;
if (!values) values = {};
if (!values.id) values.id = $ENV.model.newId();
return this.finalizeCreateControl(controlClass, group, values,type);
end
<@doc>
Create a new control and insert it to the Interactor.
The control class name to be created.
A group to which this new control will be inserted. If ~null the new control is inserted as a direct child of Interactor
A collection of property values for initializing the new Control
The control that was addded to the Interactor, or ~null on failure.
method createControl(controlClass, group, values,type)
if (controlClass == null) return null;
if (!values) values = {};
values.id = $ENV.model.newId();
return this.finalizeCreateControl(controlClass, group, values,type);
end
<@doc scope="private">
Create a new control and insert it to the Interactor.
The control class name to be created.
A group to which this new control will be inserted. If ~null the new control is inserted as a direct child of Interactor
A collection of property values for initializing the new Control
The control that was addded to the Interactor, or ~null on failure.
method finalizeCreateControl(controlClass, group, values,type)
var mapping = this.getField2ControlsMapping();
var control = $ENV.createObject(controlClass, values);
if (control == null) return false;
$ENV.recorder.execute('doInsertControl', this, group, control, mapping,type);
return control;
end
<@doc >
get properties not to be copied to target control.
The old control to be copied from
The control class name to be created.
collection of irrelevant properties
method getIrrelevantProperties(sourceControl,targetType)
var result={};
//configEditorId is property that caches the control properties editor smartform elements and should not be copied when changing control type or moving it to a different interactor class
result["configEditorId"]="configEditorId";
return result;
end
<@doc >
make new control from old control values.
The old control to be copied from
The control class name to be created.
method copyControl(control,controlType,parent)
var properties = control.Class.properties;
var controlClass = CLASS(controlType);
var group = ISA(controlClass, 'core.gml:ControlGroup');
var values = {};
var irrelevant = this.getIrrelevantProperties(control,controlType);
for (var prop in properties){
//if property doesn't exist in target control don't copy it
if (!(prop in controlClass.properties)) continue;
//don't copy the class property
if (prop=="Class") continue;
// if property exists on control
if (control[prop]){
// if property is irrelevant
if (prop in irrelevant){
// don't copy irrelevant simple property
if (typeof irrelevant[prop] =="string") continue;
this.deleteComplexIrrelevantProperties(control,prop,irrelevant[prop],control[prop]);
}
if (group){
//Group controls 'readOnly' property should be false and disabled
if (prop=="readOnly") continue;
//Group controls 'disable' property should be false and disabled except for buttonGroup
if (prop=="disable" && !ISA(controlClass, "com.sap.tc.UIP.dt.base:ButtonGroup")) continue;
}
//copy relevant properties to new control
values[prop]=control[prop];
}
}
var group=(parent!=this) ? parent : null;
var newControl = this.createDuplicateControl(controlType ,group, values,"changeType");
//set property bag items parent
for(var id in newControl.propertyBag){
newControl.propertyBag[id].parent=newControl;
}
//set index property
newControl.setProperty("index",control.getProperty("index"));
//set translation property
var items =this.parent.translationTable.items;
for (var item in items){
if (control.id==items[item].objID){
//replace translation pointer to new control id
items[item].objID=newControl.id;
//change translation type
if (ISA(controlClass,"core.gml:Button"))
items[item].type="XBUT";
else
items[item].type="XFLD";
}
}
return newControl;
end
method canChangeType(control,controlType)
if (!control || !controlType) return false;
var controlTypeClass= CLASS(controlType);
// if group with children don't allow change
if (ISA(control,'core.gml:ControlGroup') && COUNT(control.getProperty('controls'))>0) return false;
//if contained inside group don't allow change to section group
if (ISA(control.parent,'core.gml:ControlGroup') && ISA(controlTypeClass,"core.gml:Section")) return false;
// if control is in the allowed controls list with respect to the bound field
var boundField = control.getBoundField();
var allowedControls=[];
if (boundField)
allowedControls = this.defineInputType(boundField);
else
allowedControls = this.getAllowedControls();
for (var i in allowedControls){
if (controlType==allowedControls[i]) return true;
}
return false;
end
<@doc >
Changes The type of the control.
The old control to be replaced
The control class name to be created.
method changeControlType(control,controlType)
if (!this.canChangeType(control,controlType)) return;
var newControl=null;
try{
//if there is an action attached to the control
if (ISA(control, 'core.dev:IActionControl') && control.hasAction()) {
var action = control.parent.getActionByControl(control);
// if the target control doesn't support action
if (!ISA(CLASS(controlType), 'core.dev:IActionControl')){
var msg='#TEXT[XMSG_DEL_ACTION_CONTROL]'.replace('{0}', controlType);
if (CONFIRM(msg,'Yes No')<=0) return null;
this.removeControl(control);
newControl = this.copyControl(control,controlType,this);
}
else{
this.removeControl(control);
newControl = this.copyControl(control,controlType,this);
this.insertAction(action,newControl);
}
}
else{
this.removeControl(control);
newControl = this.copyControl(control,controlType,this);
}
} catch(e){
#LOG[4,'Error while changing control type '+ e.description ];
}
return newControl;
end
<@doc scope="private">
delete irrelevant complex properties from control
delete irrelevant properties from the control - used by change control type and by paste control.
The object which contains irrelevant properties
The control which contains the object that holds certain irrelevant properties
method deleteComplexIrrelevantProperties(control,prop,irrelevantComplexProperty,controlComplexProperty)
for (var irrelevantSubProperty in irrelevantComplexProperty) {
for (var controlSubProperty in controlComplexProperty){
if (irrelevantSubProperty==controlComplexProperty[controlSubProperty].name){
//special handling for propertyBagItems which needs to be removed via the PropertyBag API
if (prop=="propertyBag")
control.setPropertyBagItem(irrelevantSubProperty);
else delete controlComplexProperty[controlSubProperty];
}
}
}
end
<@doc>
Tell if the object or it's expected children can have properties that can be translated.
~true if strings are possible, ~false otherwise
override method canBeTranslated()
return(true); // false by default;
end
<@doc>
move translation to target unit.
The source translation table
The target translation table
method moveTranslation(sourceTable,targetTable)
if (sourceTable.id==targetTable.id) return;
//move own properties
for (var i in sourceTable.items){
if (sourceTable.items[i].objID==this.id) targetTable.insertElement(sourceTable.items[i],"items");
}
//move translation for controls
if (this.getControls){
var controls = this.getControls(true);
for (var i in controls) controls[i].moveTranslation(sourceTable,targetTable,[controls[i]]);
}
//move translation for toolbar controls
if (this.toolbar){
var controls = this.toolbar.getControls && this.toolbar.getControls(true);
for (var i in controls) controls[i].moveTranslation(sourceTable,targetTable,[controls[i]]);
}
end
<@doc>
paste control to the target interactor.
If the control already belongs to another parent , then it will be removed from the old parent as part of this operation.
The control to be pasted
the interactor to which the control is to be pasted
method moveControl(control,targetInteractor)
if (!$ENV.triggerEvent("canInsertElement",targetInteractor,control,control.unit,"uicontrols","pasteEvent")) return;
//if control is being moved to different interactor class
if (targetInteractor.Class!=this.Class){
// delete irrelevant properties before copying the control to new interactor
var irrelevant = targetInteractor.getIrrelevantProperties(control,control.Class.fullname);
for (var prop in irrelevant){
if (typeof irrelevant[prop]=="string") delete control[prop];
else this.deleteComplexIrrelevantProperties(control,prop,irrelevant[prop],control[prop]);
}
}
//move translation to target unit
control.moveTranslation(this.unit.translationTable,targetInteractor.unit.translationTable,[control]);
//move actions to target interactor
targetInteractor.moveAction(this.getActionByControl(control),control);
//insert control to target interactor
targetInteractor.insertControl(control);
//recalculate dynExpContext
control.resetDynExpContext();
end
<@doc>
Insert control to the interactor/group.
If the control already belongs to another parent , then it will be removed from the old parent as part of this operation.
The control to be added.
A group to which this new control will be inserted. If ~null the new control is inserted as a direct child of Interactor
Return true iff the indertion of the control succeeded
method insertControl(control, group)
if (control == null) return false;
var mapping = this.getField2ControlsMapping();
try{
BEGIN('Insert a control to the group/interactor');
//If the control is already in an interactor/group then first delete it from the parent
if(control.parent){
if(ISA(control.parent,'core.gml:ControlGroup')){
control.parent.removeControl(control, false);
}else {// the parent is interactor
control.parent.removeElement(control,'uicontrols');
}
}
$ENV.recorder.execute('doInsertControl', this, group, control, mapping);
COMMIT();
}catch(e){
var target = (group)? 'group':'interactor';
#LOG[4,'Error while inserting control to the ' + target+ ', '+ e.description ];
ROLLBACK();
}
return true;
end
<@doc>
Get controls by field id.
The Id of the field attached to the control.
Returns all the controls that are bound to the field.
method getControlsByField(fieldId)
var mapping = this.getField2ControlsMapping();
return mapping[fieldId];
end
<@doc>
Removes all controls.
method removeAllControls()
var controls = this.getControls();
try
{
BEGIN();
for(var id in controls)
{
this.deleteControl(controls[id]);
}
COMMIT();
}
catch (e)
{
ROLLBACK();
}
end
<@doc>
Remove control from the interactor.
DEPRECATED: use instead removeControlsByField method. Remove Control from the interactor by the field that it's strongly bind to.
The Field attached to the control.
Remove Control from the interactor
The control to be deleted.
Flag to decide if children controls are to be deleted
Returns ~true if the control has been deleted.
method removeControl(elem,noChildren)
try {
BEGIN('remove control from the group/interactor');
if (ISA(elem,'core.gml:Field')) {
return this.removeControlsByField(elem) ;
}
if (ISA(elem, 'core.gml:Control')) {
this.deleteControl(elem,noChildren);
}
COMMIT();
} catch(e){
#LOG[4,'Error while removing control from the interactor, '+ e.description ];
ROLLBACK();
}
return true;
end
<@doc>
Returns the actions of this Interactor
The collection of actions in this interactor
method getActions()
return this.actions;
end
<@doc>
Get action by id
The id of an action
The action which matches the given id
method getActionById(id)
return this.actions[id];
end
<@doc>
move action to this interactor
The action to move
The control will be moved
method moveAction(action,control)
this.insertAction(action,control);
if (ISA(control, 'core.gml:ControlGroup')){
for (var id in control.getControls()){
var actionControl = control.getControls()[id];
this.moveAction( actionControl.interactor.getActionByControl(actionControl),actionControl);
}
}
end
<@doc>
Insert an action to the interactor
The action to be added.
A group to which this new control will be inserted. If ~null the new control is inserted as a direct child of Interactor
Return true iff the indertion of the control succeeded
method insertAction(action, control)
if (!action || !control || !ISA(control, 'core.dev:IActionControl')) return false;
try {
BEGIN('Insert an action to the interactor');
// If the control already has an action, remove it first
if (control.hasAction())
this.removeAction(this.getActionByControl(control), control);
this.insertElement(action, 'actions');
$ENV.recorder.execute('doInsertAction', this, action, control);
COMMIT();
}catch(e){
#LOG[4,'Error while inserting an action to the interactor, '+ e.description];
ROLLBACK();
}
return true;
end
transaction doInsertAction(interactor, action, control)
do,redo:
interactor.getAction2ControlMapping()[action.id] = control.id;
control.setProperty('action', action.id);
undo:
control.setProperty('action', '');
delete interactor.getAction2ControlMapping()[action.id];
end
<@doc>
Remove an action from the interactor.
The control to be deleted.
Returns ~true if the control has been deleted.
method removeAction(action, control)
if (!action || !control || !ISA(control, 'core.dev:IActionControl')) return false;
try {
BEGIN('Remove an action from the interactor');
this.removeElement(action, 'actions');
$ENV.recorder.execute('doRemoveAction', this, action, control);
COMMIT();
}catch(e){
#LOG[4,'Error while removing the action from the interactor, '+ e.description];
ROLLBACK();
}
return true;
end
transaction doRemoveAction (interactor, action, control)
do,redo:
control.setProperty('action', '');
delete interactor.getAction2ControlMapping()[action.id];
undo:
interactor.getAction2ControlMapping()[action.id] = control.id;
control.setProperty('action', action.id);
end
<@doc>
Gets the control associated with the given action
An action id
Returns the control associated with the given action
method getControlByActionId(actionId)
if (!actionId) return null;
var controlId = this.getAction2ControlMapping()[actionId];
var control = this.uicontrols[controlId];
if (!control) { // check the controls in the toolbar
control = this.toolbar && this.toolbar.getControlById(controlId) || null;
}
return control;
end
<@doc>
Gets the action associated with the given control
A control id
Returns the action associated with the given control
method getActionByControl(control)
if (!control || !ISA(control, 'core.dev:IActionControl')) return null;
return this.actions[control.getProperty('action')];
end
<@doc scope="private">
Gets the Action2Control mapping
Action id to control id mapping
method getAction2ControlMapping()
if (null != this.action2Control)
return this.action2Control;
// build the map
this.action2Control = {};
for (var k in this.uicontrols) {
var control = this.uicontrols[k];
if (!control || !ISA(control, 'core.dev:IActionControl') || !control.hasAction()) continue;
var actionId = control.getProperty('action');
this.action2Control[actionId] = control.id;
}
if (this.toolbar && this.toolbar.controls) {
for (var j in this.toolbar.controls) {
var control = this.toolbar.controls[j];
if (!control || !ISA(control, 'core.dev:IActionControl') || !control.hasAction()) continue;
var actionId = control.getProperty('action');
this.action2Control[actionId] = control.id;
}
}
return this.action2Control;
end
// NOTE: Replace by RULE
transaction doRemoveControl (interactor, group, control, mapping)
do,redo:
if (group) {
group.removeControl(control);
}
else {
interactor.removeElement(control, 'uicontrols');
}
var field = control.getProperty('fieldRef');
if (field && field.id) {
var controls = mapping[field.id];
delete controls[control.id];
if( ISEMPTY(mapping[field.id]) ) delete mapping[field.id] ;
}
control.interactor = null;
undo:
// NOTE: Change this when 'uicontrols' will be renamed to 'controls'
var prop_name = (group) ? 'controls' : 'uicontrols';
var obj = (group) ? group : interactor;
if (!obj.insertElement(control, prop_name)) return;
var field = control.getProperty('fieldRef');
if( field && field.id) {
if(!mapping[field.id])mapping[field.id] = {};
mapping[field.id][control.id] = control;
}
control.interactor = interactor;
end
transaction doInsertControl(interactor, group, control, mapping,subType)
do,redo:
var prop_name = (group) ? 'controls' : 'uicontrols';
var obj = (group) ? group : interactor;
if (!obj.insertElement(control, prop_name,null,subType)) return;
control.setInteractor(interactor);
//var field = control.getProperty('fieldRef');
var field = control.getBoundField();
control.fieldRef = field;
if (field && field.id) {
if(!mapping[field.id])mapping[field.id] = {};
mapping[field.id][control.id] = control;
}
if(ISA(control, 'core.gml:SimpleControl'))
{
// This part was added in order to evantually persist default values for property bag items.
// This is Patterns/A1S request - mickey hoter June06:
// i040512: Also enforce propertybag creation after type change
if(ISEMPTY(control.propertyBag) || subType=="changeType")
// Create the default only when no property bag defined (only in control creation).
// This way the property bag will be retained when moving controls in/out of groups.
control.enforcePropBagCreation();
}
undo:
if (group) {
group.removeControl(control);
}
else {
interactor.removeElement(control, 'uicontrols');
}
var field = control.getProperty('fieldRef');
control.fieldRef = null;
if (field && field.id) {
var controls = mapping[field.id];
delete controls[control.id];
if( ISEMPTY(mapping[field.id]) ) delete mapping[field.id] ;
}
if(ISA(control, 'core.gml:SimpleControl'))
{
// This part was added in order to evantually persist default values for property bag items.
// This is Patterns/A1S request - mickey hoter Jume06:
for(var iter in control.propertyBag)
{
delete control.propertyBag[iter];
}
}
control.interactor = null;
end
transaction doUpdateMapping(interactor, control, oldField, newField,mapping)
all:
var oID = oldField && oldField.id || '' ;
var nID = newField && newField.id || '';
var cID = control && control.id || '' ;
do,redo:
var controls = mapping[oID];
if ( controls) delete controls[cID];
if( ISEMPTY(mapping[oID]) ) delete mapping[oID];
if (nID != '') {
if(!mapping[nID] ) mapping[nID] = {};
mapping[nID][cID] = control;
}
undo:
var controls = mapping[nID];
if ( controls) delete controls[cID];
if(oID != ''){
if(!mapping[oID] ) mapping[oID] = {};
if( ISEMPTY( mapping[nID]) ) delete mapping[nID];
mapping[oID][cID] = control;
}
end
listen onShapeMenu for gml:Interactor
menu.append();
if (object.getProperty('showToolbar'))
menu.append({button:'#NS[OPEN_TOOLBAR_EDITOR]'});
menu.append();
menu.append({button:'OPEN_FIELDS_SELECTOR'});
end
/// DEPRECATED
////////////////////////////////////////////////////////////////
<@doc>
Remove the control that is attached to the field.
The field which is strongly attached to a control
Returns ~true if the control has been deleteted
method removeControlByField(field)
WRITE('DEPRECATED removeControlByField');
var fieldId = field && field.id || null ;
if (fieldId == null) return false ;
var mapping = this.getField2ControlMapping();
var controls = mapping[fieldId];
var control = null;
if (controls == null) return false;
for ( var id in controls){
control = controls[id];
break;
}
if ( control == null) return false;
var group = control.parent;
if (!ISA(group, 'core.gml:ControlGroup')) group = null;
this.removeActionIfExists(control);
$ENV.recorder.execute('doRemoveControl', this, group, control, mapping);
return true;
end
/*<@doc scope="private">
DEPRECIATED. Add to the interactor default controls according to the fields passed to the method.
The fields to be attached to the new control.
The newly created controls, or ~null in the case of any error.
*/
/*
// Depreciated in AP5
method addDefaultControls(fields)
if (!fields) return ;
var controls = [] ;
// TODO: Handle the case that the interactor is floating.
for(var id in fields){
controls.push(this.addDefaultControl(fields[id]));
}
return controls;
end
*/
<@doc scope="private">
Holds a mapping between the field id to a control.
Don't use this property directly. Use the @getField2ControlMapping() method instead.
virtual property field2Control = null ;
/*<@doc scope="private">
DEPRECATED Gets the allowed controls accourding to the Interactor type and the field type
The field Id
An array that contains all the allowed controls
*/
/*
// Depreciated in AP5
method getFieldAllowedControls(field)
if(!field) return null;
var C=this.Class.metadata.allowedControls;
var control = this.getControl( field.getProperty('id') );
var A = [];
for (var id in C) {
var controlClass = CLASS(C[id]);
// Exclude the current control class.
if (control && controlClass && ( controlClass.fullname == control.Class.fullname)) continue;
if (field.primitive in controlClass.metadata.allowedFieldTypes ) {
A.push(C[id]);
}
}
return A;
end
*/
/*<@doc scope="private">Multi select/clear for all fields in the Infoshape.
Indicates whether to select or to clear fields
Returns ~true if the fields were updated successfully
*/
/*
// Depreciated in AP5
method updateAllFields(value)
currentInfoShape = this.getInfoshape();
var allFields = currentInfoShape.getFields();
if (ISEMPTY(allFields)) return true;
for (var field in allFields) {
var f = allFields[field];
if (value == 'true') {
control = this.addDefaultControl(f);
}else {
control = this.removeControl(f);
}
}
return true;
end
*/
/*<@doc scope="private">
Indicates whether the field is selected by the @gml:Interactor
The Id of the field to be checked.
return true iff the field is selected
*/
/*
// Depreciated in AP5
method isFieldSelected(fieldId)
return this.getControl(fieldId)!= null;
end
*/
<@doc scope="private">
Gets the field2Control mapping.
Field id to control mapping
Used for lazy evalution only.
The mapping only includes controls which are strongly mapped to field,
i.e. control's 'field' property is not ~null and points to a valid @gml:Field object
method getField2ControlMapping()
WRITE('DEPRECATED -getField2ControlMapping ');
if (this.field2Control) return this.field2Control ;
var mapping = {};
addInnerControls(this);
this.field2Control = mapping;
return this.field2Control;
function addInnerControls(parent) {
var ctls = parent.getControls();
for (var iter in ctls) {
var ctl = ctls[iter];
var field = ctl.getProperty('field');
if (field) {
mapping[field.id] = ctl;
continue;
}
if (ISA(ctl, 'core.gml:ControlGroup')) addInnerControls(ctl);
}
}
end
method canInsertControl(control,subType)
var allowedControlsLst = this.getAllowedControls();
var flag = false;
for (var i in allowedControlsLst) {
if (ISA(control,allowedControlsLst[i])){
flag=true;
break;
}
}
if (ISA(control, 'core.gml:ControlGroup')){
for (var j in control.getControls()){
if (!this.canInsertControl(control.getControls()[j],subType)) return false;
}
}
return flag;
end
method canRemoveControl(control,subType)
return true;
end
<@doc>
Get control by field id.
The Id of the field attached to the control.
The field attached to the control.
method getControl(fieldId)
WRITE('DEPRECATED - getControl');
var mapping = this.getField2ControlsMapping();
var controls = mapping[fieldId];
for ( var id in controls)
return controls[id];
end
<@doc>
Get list of control classes that can be added to this interactor, as children of the group (parameter).
The element that will be the parent of the added controls
A collection of names of the control-classes that can be added to the group.
method getAllowedControls(parent)
end
<@doc>
getToolbarAllowedControls.
The group that will host the newly created control.
A collection of names of the control-classes that can be added to the group.
method getToolbarAllowedControls(group)
var list = [];
list.push('gml:Button');
return list;
end
listen canInsertElement
if (evt.subType=="pasteEvent" && ISA(evt.child,"core.gml:Control") && evt.object) {
//invoke the canInsertControl of the interactor if the controlgroup didn't implement it
var parent = evt.object;
while(parent && !parent.canInsertControl) parent=parent.parent;
if (parent && parent.canInsertControl) evt.cancel= !parent.canInsertControl(evt.child,evt.subType);
}
end
listen canRemoveElement
if (evt.subType=="cutEvent" && ISA(evt.child,"core.gml:Control") && evt.object) {
//invoke the canRemoveControl of the interactor if the controlgroup didn't implement it
var parent = evt.object;
while(parent && !parent.canRemoveControl) parent=parent.parent;
if (parent && parent.canRemoveControl) evt.cancel= !parent.canRemoveControl(evt.child,evt.subType);
}
end
///////////////////////////////////////////////////////////////////////
// RULES
Configure RULES
$ENV.extendRule('onAfterConnect', {
constraint:'(target isa gml:Interactor and (link isa (#NS[BindLink] , #NS[DataLink] )) )',
behavior: function() {
if (!target.getInfoshape()) return;
if (ISEMPTY(target.getInfoshape().getFields()) ) return;
var res = MODAL("#URL[gml:common.FieldsSelectorDlg.htm]" ,{'interactor': target},true);
},
comments:'⇒ Interactor'
});