<@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' });