<@doc alias="obj" hierarchy="GMLDOM"> Transactional implementation of GmlObject. Instances of TransObject or any of its derived classes are persistent objects that are managed using transactions. To create instances of TransObject or any of its sub-classes, use respective GMLDOM methods. Do not use constructors for initializing instances of TransObject. (c) SAP AG 2003-2006. All rights reserved. /////////////////////////////////////////////////////////////////////// // CLASS HEADER #INCLUDE[gml:defs.inc] Class TransObject inherit GmlObject; metadata title = 'Transactional Object'; metadata descr = 'Base class for all transactional model entities'; /////////////////////////////////////////////////////////////////////// // PROPERTIES <@doc group="I. Properties">Gets or sets the object notes property notes = ''; metadata for notes = { editor: 'text', group: 'General', priority: 300, label: '#TEXT[XTIT_COMMENTS]', height: 75 } <@doc>Gets or sets the stereotype tags property tags = ''; <@doc>Gets or sets the role of object in the current unit property roleName = ''; <@doc scope="private">Gets the child elements collection //virtual readonly property children = null; // in fact the type is ^Element[id] virtual readonly property children = ^Element[id]; <@doc>Gets or sets the property name at the parent object virtual readonly property parent_prop = ''; override metadata for id = { editor: 'str', group: 'General', priority:100, label: '#TEXT[XTIT_ELEMENT_ID]', visible: 'GETVAR(\'showElementIds\')', active: false } override metadata for name = { editor: 'str', group: 'General', priority: 200, label: '#TEXT[XFLD_NAME]' } // Internal for managing internal property tranlations: virtual property translationKeyMap = {}; /////////////////////////////////////////////////////////////////////// // METHODS <@doc group="II. Methods"> Tests whether this object contains a given child object The child object to test The test result override method contains(child) for (var e=child; e && e.parent && e.parent != this; e=e.parent); return e ? true : false; end <@doc> Creates a new element and inserts it under this object The element class name The property name into which this child will be assigned A collection of property values for initializing the new element Integer that specifies the placement of the new element. If this parameter is specified, the child element will be inserted immediately before the specified index. This parameter is only relevent to properties of type vector The newly created element, or ~null in case of any error This method is sealed and cannot be overridden on sub-classes. If needed, you can override the @gml:GmlObject!onCreate callback method to extend the creation operation. sealed method createElement(className, prop_name, values,before) #DEVTIME[ //BC check if (prop_name == undefined || typeof prop_name != 'string') throw new Error(-1, "Error in createElement: prop_name argument is invalid"); ] if (!values) values = {}; values.id = $ENV.model.newId(); var elem = $ENV.createObject(className, values); if (!elem || !this.insertElement(elem, prop_name ,before)) return null; return elem; end <@doc> Gets a collection of child elements by a specified type. The class of elements to get Indicates whether to perform a deep search The requested child elements collection By default this method will search only the immediate child elements. To search all descendant elements, regardless of their nesting depth, set the ~deep parameter to ~true. method getCollection(className, deep) var E, C=(this.children) ? this.children : {}; if (arguments.length == 3) { E=arguments[2]; } else { E={}; className=$DOM.toQName(className); if (!className || !C) return E; } for (var k in C) { if (className in C[k].Class.precedents) E[k]=C[k]; if (deep && C[k].children) C[k].getCollection(className, true, E); } return E; end <@doc> Gets a child element by Id The Id of the child element to get Indicates whether to perform a deep search The requested child element By default this method will search only the immediate child elements. To search all descendant elements, regardless of their nesting depth, set the ~deep parameter to ~true. method getElement(id, deep) var C=(this.children) ? this.children : {}; var elem=C && C[id] || null; if (elem || !deep || !C) return elem; for (var k in C) { if (!C[k].children) continue; var elem=C[k].getElement(id, true); if (elem) return elem; } return null; end override method getProperty(name) if (arguments.length == 2) { name = $DOM.getAspect$Name(arguments[0], arguments[1]); } var prop=this.Class.persistent[name]; if (this.hasOwnProperty(name)) { if (prop && (prop.type.charAt(0) == 'p') && (typeof this[name] == 'string')) { //check that indeed this is a full ID - and load it this[name] = $ENV.model.getElement(this[name]); } ////// TRANSLATION SUPPORT //////// if (#SYS[SUPPORT_TRANSLATION] && prop && prop.metadata && prop.metadata.translation && prop.metadata.translation.translate && this[name]) { var key = this[name]; var value = this.getTranslation(key); if(!value) { // No translation found for the property. var newValue = TRIM(this[name]); if(!prop.exprType || (prop.exprType == 'String' && newValue && newValue.charAt(0)!= '=')) { try { //Insert it to the translation table: key = this.insertTranslation(prop.metadata.translation.type, newValue); if(key) { // Set the new property and its key internally: this.setTransKey( name, key); } // Setting the property: value = this[name]; this[name] = key; } catch(e) { // Could not insert error: PROMPT(e.description + '- See property \'' + name + '\''); return this[name]; } } else { return this[name]; } } else { // Translation found, update the internal object mapping: if(!prop.exprType || (prop.exprType == 'String' && value.charAt(0)!= '=')) { key = this.getTransKey(name); if(!key && value) { this.setTransKey( name, this[name]) } } } return value; } ////// END TRANSLATION//////// return this[name]; } var P=this.Class.properties[name]; return (P && P.isProperty) ? P.value : null; end sealed method setProperty(name, value, silent) var C=this.Class, P=C.properties[name]; var transType = null; if (!P || P.isReadonly) return false; if (P.isVirtual) { if (value === undefined) delete this[name]; else this[name] = value; return true; } var oldval = P.isStatic ? P.value : this.hasOwnProperty(name) ? this[name] : void(0); if (oldval === value) return true; var msg1 = '#TEXT[XMSG_FAIL_TYPE_CHECK_SETPROPRTY]'; var msg2 = 'when expecting type'; #DEVTIME[ if (P && P.typecheck && (typeof value == 'object') && !ISA(value, P.typecheck)) throw new Error(-1, msg1 +" " + value.Class.fullname + " "+ msg2+ " " + P.typecheck); ] ////// TRANSLATION SUPPORT //////// if (#SYS[SUPPORT_TRANSLATION]) { // Prapare the translation. The actual action will take place in the Transaction phaze. if(P.metadata && P.metadata.translation && P.metadata.translation.translate // make sure it is translatable && typeof value == 'string') { if(!P.exprType || (P.exprType == 'String' && value.charAt(0)!= '=')) { oldval = this.getTranslation(this[name]); if(!oldval) { oldval = this[name]; } transType = P.metadata.translation.type; } } } ////// END TRANSLATION//////// if (P.isStatic) { //TODO: place this in a transaction ?? var oldval = P.value ; C.prototype[name] = value; P.value = value; var evt = document.createEventObject(); evt.type = 'onUpdateObject'; evt.object = this; evt.name = name; evt.value = value; evt.oldval = oldval; $ENV.fireModelEvent(evt); return true; } if (silent) { if (value === undefined) delete this[name]; else this[name] = value; // setDirty needsto know if the change was only on the SVG level - see model.gs var isSVGChange=false; if (this.Class.properties[name].Class.isAspect && "svg" == this.Class.properties[name].Class.prefix) isSVGChange=true; this.setDirty(isSVGChange); return true } var trans = $ENV.recorder.execute('doSetProperty', this, name, value, oldval, transType); return trans ? true : false; end <@doc scope=private>A helper method for assigning a value to a strong property. Only use this method from inside ~insertElement and other core functions ! sealed method assignChild(child, prop_name, before) var p = this.Class.properties[prop_name]; if (!p) { throw new Error(-1, "#TEXT[XMSG_CANT_INSRT_CHILD_INTO_PROP]" +" '" + prop_name + "' "+ "because property does not exist"); } if (!p.isStrong) { throw new Error(-1, "#TEXT[XMSG_CANT_INSRT_CHILD_INTO_PROP]"+" '" + prop_name + "' "+ "because it is not marked strong"); } var t = this.Class.properties[prop_name].type.charAt(0); var o = this[prop_name]; if (t == 'p') this[prop_name] = child; //single object else if (t == 'c') { if (!this[prop_name]) this[prop_name] = {}; this[prop_name][child.id] = child; //collection } else { if (!this[prop_name]) this[prop_name] = []; before = INT(before); if(before <= this[prop_name].length){ this[prop_name].splice(before,0,child); }else{ this[prop_name].push(child); //array } } child.parent_prop = prop_name; end sealed method clearChild(child, prop_name) var index = null ; var p = this.Class.properties[prop_name]; if (!p) { throw new Error(-1, "#TEXT[XMSG_CANNOT_CLEAR_CHILD_IN_PROP]" +" '" + prop_name + "' "+ "because property does not exist"); } if (!p.isStrong) { throw new Error(-1, "#TEXT[XMSG_CANNOT_CLEAR_CHILD_IN_PROP]" +" '" + prop_name + "' "+ "because it is not marked strong"); } var t = this.Class.properties[prop_name].type.charAt(0); if (t == 'p') this[prop_name] = null; //single object else if (t == 'c') delete this.getProperty(prop_name)[child.id]; //collection else { //TODO: test !!! because currently we do not use Array anywhere var arr = this.getProperty(prop_name); //delete from array and shift all remaining elements for (var i = 0; i Inserts a given element under this object The child element to insert The property name to insert the child element to. (Must be a property marked as 'strong') Integer that specifies the placement of the new element. If this parameter is specified, the child element will be inserted immediately before the specified index. This parameter is only relevent to properties of type vector Indicates whether the element was successfully inserted This method is sealed and cannot be overridden on sub-classes. If needed, you can override the @onInsert and onInsertMe callback method to extend the insertion operation. If the child element already belongs to another parent object, then it will be removed from the old parent as part of this operation. This method always results in a single transaction, even if the inserted child is a composite element. The composite element and its sub-elements implicitly belong to the same transaction, and do not contribute separate transaction steps. Similarly, notification events are raised only on the composite element, and not on any of its sub-elements. TIP: When creating a composite element, it is more efficient to create the element and its sub-elements off the model, before inserting the composite element to the model (see Model's @Model!createElement method). sealed method insertElement(child, prop_name, before,subType) try { // check child validity for insertion if (!child) return false; if (!this.children) this.children={}; // initilize children property as empty collection if needed if (child.parent === this) return true; var msg1='#TEXT[XMSG_ERROR_IN_INSERT_ELEMENT]'; #DEVTIME[ if (prop_name == undefined || typeof prop_name != 'string') throw new Error(-1, msg1); ] var p = this.Class.properties[prop_name]; //should be in DEVTIME pragma, but [] problem var msg2='#TEXT[XMSG_FAIL_TYPE_CHECK_INSRT_ELEM]'; var msg3='when expecting type'; #DEVTIME[ if (p && p.typecheck && !ISA(child, p.typecheck)) throw new Error(-1, msg2+" " + child.Class.fullname + " "+msg3+" " + p.typecheck); ] // if this object is floating - do a non-transactional insertion var unit = (this.isa('core.gml:Unit') ? this : this.unit) || null; if (!unit && !child.parent) { child.parent = this; child.unit = null; this.children[child.id] = child; this.assignChild(child, prop_name ,before); if (this.onInsert) this.onInsert(child); if (child.onInsertMe) child.onInsertMe(this); return true; } // send event to test that the element can be inserted var evt = document.createEventObject(); evt.type = 'canInsertElement'; evt.object = this; evt.child = child; evt.unit = unit; evt.prop_name = prop_name; evt.cancel = false; evt.subType = subType; $ENV.fireModelEvent(evt); if (evt.cancel) return false; // if element has no old parent - do a transactional insertion if (!child.parent && $ENV.recorder.execute('doInsertElement', unit, this, child, prop_name,before,subType) != null) return true; // TODO: HOW DO WE KNOW WHERE THE ELEMENT IS REMOVED FROM ??? // test that the element can be removed from old parent var evt = document.createEventObject(); evt.type = 'canRemoveElement'; evt.object = child.parent; evt.child = child; evt.unit = child.unit; evt.subType = subType; evt.cancel = false; $ENV.fireModelEvent(evt); if (evt.cancel) return false; try{ // do a transactional transfer from old to new parent BEGIN('transfer element'); var index; if ($ENV.recorder.execute('doRemoveElement', child.unit, child.parent, child, prop_name, index,subType) != null && $ENV.recorder.execute('doInsertElement', unit, this, child, prop_name,before,subType) != null) COMMIT(); else throw new Error(-1, "Error in insertElement: null returned"); }catch(e){ ROLLBACK(); var unitMsg = child.unit ? ' in unit ' + child.unit.id : '' ; var childId = (child && child.id || '' ); var parentId = child.parent && child.parent.id || '' ; #LOG[4, 'Error while trying to insert child element '+ childId +' to parent ' + parentId + unitMsg+ ", Desc: " + e.description]; return false; } return true; } catch(e) { #LOG[4, e.description]; return false; } end <@doc> Removes a child element from this object The child element to remove The property name to remove the child element from. (Must be a property marked as 'strong') The Id of the child element to remove The property name to remove the child element from. (Must be a property marked as 'strong') Indicates whether the child element was successfully removed The given child element must be contained in the ~prop_name property of this object This method is sealed and cannot be overridden on sub-classes. If needed, you can override the @onRemove callback method to extend the removal operation. This method always results in a single transaction, even if the removed child is a composite element. The composite element and its sub-elements implicitly belong to the same transaction, and do not contribute separate transaction steps. Similarly, notification events are raised only on the removed element, and not on any of its sub-elements. sealed method removeElement(child, prop_name) if (!this.children) return false; if (typeof child == 'string') child = this.children[child]; if (!child) return false; if (!child.parent) return true; if (child.parent != this) return false; var msg1='#TEXT[XMSG_ERROR_IN_REMOVE_ELEMENT]'; #DEVTIME[ if (prop_name == undefined || typeof prop_name != 'string') throw new Error(-1, msg1); ] var p = this.Class.properties[prop_name]; //should be in DEVTIME pragma, but [] problem var msg2='#TEXT[XMSG_FAIL_TYPE_CHECK_REMOVEELEM]'; var msg3='when expecting type'; #DEVTIME[ if (p && p.typecheck && !ISA(child, p.typecheck)) throw new Error(-1, msg2+" " + child.Class.fullname + " "+ msg3 + " " + p.typecheck); ] // if this object is floating - do a non-transactional removal var unit = (this.isa('core.gml:Unit') ? this : this.unit) || null; if (!unit) { if (this.onRemove) this.onRemove(child); if (child.onRemoveMe) child.onRemoveMe(this); child.parent.clearChild(child, prop_name); delete this.children[child.id]; child.id = null; child.parent = null; child.unit = null; return true; } // test that the child element can be removed var evt = document.createEventObject(); evt.type = 'canRemoveElement'; evt.object = this; evt.child = child; evt.unit = unit; evt.cancel = false; evt.prop_name = prop_name; $ENV.fireModelEvent(evt); if (evt.cancel) return false; // do a transactional removal try{ BEGIN('Remove element'); var index; if ($ENV.recorder.execute('doRemoveElement', unit, this, child, prop_name,index) == null) throw new Error(-1, "Error in removeElement: doRemoveElement failed"); // If a usage was removed than update the model index collection if ( ISA(child, 'core.gml:Usage')) $ENV.organizer.updateIndex(); COMMIT(); }catch(e){ ROLLBACK(); var unitMsg = unit ? ' in unit ' + unit.id : '' ; var childId = (child && child.id || '' ); #LOG[4, 'Error while trying to remove child element '+ childId +' from parent ' + this.id +unitMsg+ ", Desc: " + e.description]; return false; } return true; end <@doc> Check if the object or its children have translation implications - currently this means if it has links to Gml:Text objects ~true if it does have translation implications, ~false otherwise method hasTranslation(deep) return(false); //nothing for TransObject. The method is defined here because it is called by hasChildTranslation end <@doc> Check if the object's children have translation implications - currently this means if it has links to Gml:Text objects ~true if it does have translation implications, ~false otherwise method hasChildTranslation() var val, isStrong, props = this.Class.properties; for (var k in props) { if (this.hasProperty(k)) { val = this.getProperty(k); isStrong = props[k].isStrong; if ((val != undefined) && isStrong) { var t = props[k].type.charAt(0); switch (t) { case 'p': if (val.hasTranslation(true)) return(true); break; case 'c': // collection for(var i in val) if (val[i].hasTranslation(true)) return(true); break; case 'v': // vector for(var i in val) if(val[i].hasTranslation(true)) return(true); break; default: return(false); } } } } return(false); //nothing found end <@doc> Check if the object has personalization implications ~true if it does has personalization implications, ~false otherwise method canBePersonalized() return(false); // false by default; 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 This is similar in usage to hasTranslation. The difference is that here the check is static, based on the Class. This means that the result might be wrong, but the performance is very fast. Use this method in cases the actual existance of strings is not visible to the modeler, and performance of hasTranslation might be an issue. method canBeTranslated() return(false); // false by default; end <@doc> Handler method for cloning operation: default implementation copies all properties and clones all children. This method should not be called directly as it does not handle all aspects of the clone operation as fixing references, initialization of the cloned objects and after clone ooperations needed to complete this action. Instead, to clone an object you should either call Model's @gml:Model!cloneElements method or ObjectSet->ObjectSet!clone method. When overriding this method, one should consider that only at the time of GmlObject!onAfterClone method, the object's properties are set properly after fixing references and initializing the object. This parameter is an [OUT] parameter and passed byref: Map of the newly created objects used to fix object references. This map should be populated with newly created objects for any depth of children created indirectly by this clone operation. The ID used for this map should be of the old object from which the new one was cloned Parent object of the newly cloned object The newly cloned object method onClone(elementsMap, parent) var onloadList=[]; if ( !elementsMap ) elementsMap = {}; var copy = $ENV.createRawObject(this.Class.fullname, cloneProperties(this)); var model = this.model || (this.unit? this.unit.model: $ENV.context.model); copy.id = model.newId(); delete copy.unit; elementsMap[this.id] = copy; if (parent) { copy.parent = parent; if ( parent.children == null ) parent.children = {}; parent.children[copy.id] = copy; } // clone children if (this.children) { for (var k in this.children) { var child = this.children[k].onClone(elementsMap, copy); } } //TODO: rewrite so that we clone deep the strong properties, with prop_names ?? return copy; function cloneProperties(elem) { var P=elem.Class.properties, V={}; for (var name in P) { var prop=P[name], value=elem[name]; if (!elem.hasOwnProperty(name) || name== 'id') continue; if ((!prop.isVirtual || (prop.name == 'parent_prop')) && !prop.isStatic) V[name] = CLONE(value); } return V; } end <@doc scope="private"> This method goes over all the object's propertis and translaets all its references to other objects, using the provided elemens map. the elementsMap parameter is created during the onClone operation. (This method is used in ObjectSet, for the clone mechanism) A collection that maps the 'old object's ID to a new object method onFixClonedReferences(elementsMap) { var P=this.Class.properties; for (var name in P) { var prop=P[name], value=this[name]; if (!this.hasOwnProperty(name) || !value) continue; switch (prop.type) { case 'pointer': var ptr = elementsMap[value.id]; if (ptr) this[name]=ptr; break; case 'vector': for (var i=0, len=value.length; i A callback method that is invoked when a child object is inserted into this object The child object that was inserted The transaction context Use the ~trans argument to determine the context under which this method was invoked. This method can be invoked as a result of doing or redoing an insertion transaction or as a result of undoing a removal transaction. The method is invoked before any notification events are raised. Override this method to create or update dependant model objects, as needed. // abstract method /* method onInsert(child , trans) if ( child.isa('core.gml:Usage')){ $ENV.organizer.addUsage(child, trans.unit); }else if(child.isa('#NS[Interactor]') || child.isa('#NS[Port]') || child.isa('#NS[State]') ){ $ENV.organizer.addElement(child,trans.unit); } end */ <@method name="onInsertMe"> A callback method that is invoked when a this object is inserted into a paernt object The parent object that this object was inserted to The transaction context This callback is always called after the @onInsert callback was called for the parent Use the ~trans argument to determine the context under which this method was invoked. This method can be invoked as a result of doing or redoing an insertion transaction or as a result of undoing a removal transaction. The method is invoked before any notification events are raised. Override this method to create or update dependant model objects, as needed. // abstract method <@method name="onRemove"> A callback method that is invoked when a child object is removed from this object The child object that was removed The transaction context Use the ~trans argument to determine the context under which this method was invoked. This method can be invoked as a result of doing or redoing a removal transaction or as a result of undoing an insertion transaction. The method is invoked before any notification events are raised. Override this method to create or update dependant model objects, as needed. // abstract method /* method onRemove(child , trans) if ( child.isa('core.gml:Usage')){ $ENV.organizer.delUsage(child, trans.unit); }else if(child.isa('#NS[Interactor]') || child.isa('#NS[Port]') || child.isa('#NS[State]') ){ $ENV.organizer.delElement(child,trans.unit); } end */ <@method name="onRemoveMe"> A callback method that is invoked when a this object is removed from its parent object The parent object that this object was removed from The transaction context This callback is always called after the @onRemove callback was called for the parent. Use the ~trans argument to determine the context under which this method was invoked. This method can be invoked as a result of doing or redoing a removal transaction or as a result of undoing an insertion transaction. The method is invoked before any notification events are raised. Override this method to create or update dependant model objects, as needed. // abstract method <@method name="onPropagate"> A callback method that is invoked when an object is notified regarding the change of the model. The event object which describes the model's change. The specific object instance is notified when it potentially can be affected by the model's change. Here is the list of the propagated events: @gml:TransObject!onInsertElement | @Port!, @Connectable!, @Link! @gml:TransObject!onRemoveElement | @Port!, @Connectable!, @Link! // abstract method /////////////////////////////////////////////////////////////////////// // EVENTS <@doc group="III. Events"> Queries whether a child element can be inserted into a given object The parent object The child element to be inserted The containing unit The name of the property into which this element should be inserted Set this flag to ~true to cancel the child element insertion event canInsertElement(object, child, unit, prop_name, cancel); <@doc> Notifies that a child element has been inserted The parent object The child element that was inserted The containing unit The name of the property to which this element was inserted event onInsertElement(object, child, prop_name, unit); <@doc> Queries whether a child element can be removed from its parent object The parent object The child element to be removed The containing unit The name of the property from which this element should be removed Set this flag to ~true to cancel the child element removal event canRemoveElement(object, child, unit, prop_name, cancel); <@doc> Notifies that a child element has been removed The parent object The child element that was removed The containing unit The name of the property from which this element was removed event onRemoveElement(object, child, prop_name, unit); <@doc> Queries whether a given element can be cloned. This event may be raised more than once on the same element in a clone transaction. The object to be cloned The collection of objects that are cloned in this transaction Set this flag to ~true to cancel the cloning of this element only, allowing the rest of the clone operation to continue Set this flag to ~true to cancel the entire cloning operation event canCloneElement(object, objectset, cancelall, cancelme); <@doc> Queries whether a given foreign unit can be copied to a given target GML classname of the source foreign unit GML classname of the target Set this flag to ~true to cancel the copy of the src event canCopyForeignUnit (src, target, cancel); <@doc> Fires when an external editor is closed The component which was edited using the editor The id of the editor which was closed The value of the returnValue property set by the editor's window event onExternalEditorClose(object, editorId, returnValue); /////////////////////////////////////////////////////////////////////// // TRANSACTIONS transaction doSetProperty(object, name, value, oldval, transType) do, redo: var v0=value, v1=oldval; undo: var v0=oldval, v1=value; all: if (v0 === undefined) { delete object[name]; } else { object[name] = v0; } object.translationHandling(name, value, v0, v1, transType); // setDirty needsto know if the change was only on the SVG level - see model.gs var isSVGChange=false; if (object.Class.properties[name].Class.isAspect && "svg" == object.Class.properties[name].Class.prefix) isSVGChange=true; object.setDirty(isSVGChange); var evt = document.createEventObject(); evt.type = 'onUpdateObject'; evt.object = object; evt.name = name; evt.value = v0; evt.oldval = v1; $ENV.fireModelEvent(evt); if (name == 'name') { var evt = document.createEventObject(); evt.type = 'onRenameObject'; evt.object = object; evt.newname = v0; evt.oldname = v1; $ENV.fireModelEvent(evt); } end //onlyCheck - execute (~false) or only test if translation will be removed (~true) //return - ~true if trnalsation will be removed, ~false otherwise <@doc scope="private"> handles translations for the doSetProperty transation. Also used to see if planned changed will cause translation loss. The name of the property that is to be modified The new value to assign either old or new value, depends on transaction "direction" (do vs. undo) either old or new value, depends on transaction "direction" (do vs. undo) The type of translation (e.g. XBUTT) ~false does the setProperty action, ~true means we only check if the action leads to translation object loss only used if onlyCheck=true. "enumeration" that tells what will happen to translation as a result of this operation 0=no change 1=translation modified 2=new translation 3=translation deleted sealed method translationHandling(name, value, v0, v1, transType, onlyCheck) // Support for translation: if (!#SYS[SUPPORT_TRANSLATION]) // NO TRANSLATION SUPPORT... return(#[TRANSLATE_NOP]); var key = this.getTransKey(name);// check if the object already has a translation object (gml:Text) try { if (undefined===v0) //proeprty was already removed. Now handle translation if (key) { //if translation does exist, we need to remove it, since proeprty was already removed if (onlyCheck) return(#[TRANSLATE_DEL]); this.removeTranslation(key); this.removeTransKey(name); } else // translation does not exist ==> it won't be deleted... return(#[TRANSLATE_NOP]); if(transType) { // if entered transaction with request to handle translation value = TRIM(value); if(!key && value && value != this.Class.properties[name].value) { // No current translation and value differs from default ==> create translation // Insert the translation to the translation table: if (onlyCheck) return(#[TRANSLATE_NEW]); key = this.insertTranslation(transType, v0); if(key) { // Update the current object mapping: this.setTransKey( name, key); } } else { // either translation already exists, or it does not but value does not differ from default if(value && value != this.Class.properties[name].value) { // if it's the case of new value with existing translation ==> only update translation with new text // Translation already exists, update: if (onlyCheck) return(#[TRANSLATE_MOD]); this.removeTranslation(key); this.removeTransKey(name); key = this.insertTranslation(transType, v0); //key = this.updateTranslation(v0, key); //NadavL - create new text object } else { // For empty property value or a value that equals the default: if (key) { // translation exists but value equals default value ==> delete translation object if (onlyCheck) return(#[TRANSLATE_DEL]); this.removeTranslation(key); this.removeTransKey(name); } if (onlyCheck) return(#[TRANSLATE_NOP]); key = v0; // set field contents to new value } } if (onlyCheck) return(#[TRANSLATE_NOP]); this[name] = key; } else // we entered transaction with OUT a request to handle translation if (key && (!value || value == this.Class.properties[name].value)) { // translation exists but is not required // empty property value or a value that equals the default: if (onlyCheck) return(#[TRANSLATE_DEL]); this.removeTranslation(key); this.removeTransKey(name); } if (onlyCheck) return(#[TRANSLATE_NOP]); } catch(e) { if (onlyCheck) return(#[TRANSLATE_NOP]); // NadavL - used to be return(#[TRANSLATE_DEL]); // changed to due problems related to action addiotnal properites if (key) { // Error - remove existing redundent translation: this.removeTranslation(key); this.removeTransKey(name); } // Insertion failed: PROMPT(e.description + "- See property '" + name + "'"); } end //onlyCheck - execute (~false) or only test if translation will be removed (~true) //return - ~true if trnalsation will be removed, ~false otherwise <@doc> calls translationHandling is "test only" mode The name of the property that is to be modified The new value to assign either old or new value, depends on transaction "direction" (do vs. undo) either old or new value, depends on transaction "direction" (do vs. undo) The type of translation (e.g. XBUTT) only used if onlyCheck=true. ~true if the action will lead to loss of translation obejcts, ~false otherwise sealed method willTranslationBeDeleted(name, value, v0, v1, transType) if (v0==v1) return(0); // if texts are identical ==> SET_INPUT would not have called setProperty ==> act the same return(this.translationHandling(name, value, v0, v1, transType, true)); end <@doc scope="private"> This method goes over all the object's propertis and translates all its references to other objects, using the provided elemens map. the elementsMap parameter is is giving by the called mathod. A collection that maps the 'old object's ID to a new object A collection that maps the 'old object's ID to a new object A collection for all the elements that has been visited, To prevent circular reference . This is generic function that should be used by the core exculsively, and will be rewritten in the next release. In the next release the core will only support cross unit refernece in: 1. Plugs - Port. 2. Usage - Unit. method onFixCrossUnitRef(elementsMap, scope , visited) // Stop Condition: check the scope of the fix, to operate only on the specified scope. if( scope && !inScope(scope ,this ) ) return ; if(!visited) visited = {}; // Stop condition: if the current element has been visited than return. if(visited[this.id]) return; // Update the visited collection. visited[this.id] = true; var P=this.Class.properties; // Iterate on all the properties of the element. for (var name in P) { var prop=P[name], value=this[name]; if (!this.hasOwnProperty(name) || !value) continue; switch ( PREF(prop.type,1) ) { case 'P': // pointer var ptr = elementsMap[$ENV.model.getElementId(value)]; if (ptr) { this[name]=ptr; if(scope && ptr.onFixCrossUnitRef) ptr.onFixCrossUnitRef(elementsMap ,scope ,visited); }else{ if(scope && value.onFixCrossUnitRef) value.onFixCrossUnitRef( elementsMap,scope , visited); } break; case 'V': // vector for (var i=0, len=value.length; i