The key point here is how to get the value of the "DE" property parameter from the OPTREGISTER class definition into the "DE" property of the corresponding property. The naming does not do that automatically. Generally, property parameters (like DE, DICLookUp, XMLNAME) are used for code generation, either in methods (the third example below), projections (like XML), or for validation of datatypes (e.g., %String MINLEN/MAXLEN).

Assuming XML export is done with (for example):

Set = ##class(Test.SendExternalModel.OPTREGISTER).%New()
Do o.XMLExport(,",literal,indent")

There are at least three options. I'd recommend the third, but the first two are perhaps easier to understand.

1) In a method of OPTREGISTER (perhaps %OnNew), instantiate the various properties and set the DE property of each of them. In this case, the DE property parameter is no longer needed. For example:


Method %OnNew() As %Status [ Private, ServerOnly = 1 ]
{
  Set ..THBZ = ##class(Test.SendExternalModel.DE2).%New()
  Set ..THBZ.DE = "DEX71.41.007.01"
  Set ..GHFS = ##class(Test.SendExternalModel.DE2).%New()
  Set ..GHFS.DE = "DEX71.41.008.01"
  Set ..GHF = ##class(Test.SendExternalModel.DE1).%New()
  Set ..GHF.DE = "DEX71.41.009.01"
  Set ..ZLF = ##class(Test.SendExternalModel.DE1).%New()
  Set ..ZLF.DE = "DEX71.41.009.01"
  Set ..QTF = ##class(Test.SendExternalModel.DE1).%New()
  Set ..QTF.DE = "DEX71.41.009.01"
  Set ..WDBZ = ##class(Test.SendExternalModel.DE2).%New()
  Set ..WDBZ.DE = "DEX71.41.010.01"
  Set ..GHKSDM = ##class(Test.SendExternalModel.DE2).%New()
  Set ..GHKSDM.DE = "DE08.10.025.00"
  Quit $$$OK
}

2) Have DE1/DE2 extend %SerialObject rather than %RegisteredObject, and define a constructor in DE1. (DE2 can have inheritance simplified a bit, although perhaps not in the context of your full application.) Then, define properties in OPTREGISTER with an InitialExpression - this is passed to the constructor for the serial object. This approach may not actually make sense in the context of your full application.

PropertyParameters:

Class Test.Common.PropertyParameters Extends %XML.PropertyParameters [ ProcedureBlock ]
{

Parameter HDSD As STRING;

Parameter DE As STRING;

Parameter DICLookUp As STRING;

}

DE1:

Class Test.SendExternalModel.DE1 Extends (%SerialObject, %XML.Adaptor) [ PropertyClass = Test.Common.PropertyParameters ]
{

Parameter XMLIGNOREINVALIDTAG = 1;

Parameter XMLIGNORENULL = 1;

Property DE As %String(XMLNAME = "de", XMLPROJECTION = "ATTRIBUTE");

Method %OnNew(pDE As %String = "") As %Status [ Private, ServerOnly = 1 ]
{
  Set ..DE = pDE
  Quit $$$OK
}

Storage Default
{
<Data name="DE1State">
<Value name="1">
<Value>DE</Value>
</Value>
</Data>
<State>DE1State</State>
<StreamLocation>^Test.SendExternalModel.DE1S</StreamLocation>
<Type>%Library.CacheSerialState</Type>
}

}

DE2:

Class Test.SendExternalModel.DE2 Extends Test.SendExternalModel.DE1
{

Property Display As %String(XMLNAME = "display", XMLPROJECTION = "ATTRIBUTE");

Storage Default
{
<Data name="DE2State">
<Value name="1">
<Value>DE</Value>
</Value>
<Value name="2">
<Value>Display</Value>
</Value>
</Data>
<State>DE2State</State>
<StreamLocation>^Test.SendExternalModel.DE2S</StreamLocation>
<Type>%Library.CacheSerialState</Type>
}

}

OPTREGISTER:

Class Test.SendExternalModel.OPTREGISTER Extends (%RegisteredObject, %XML.Adaptor) [ PropertyClass = Test.Common.PropertyParameters ]
{

Parameter XMLIGNOREINVALIDTAG = 1;

Parameter XMLIGNORENULL = 1;

Parameter XMLNAME = "OPTREGISTER";

Property THBZ As Test.SendExternalModel.DE2(DE = "DEX71.41.007.01", DICLookUp = "Y", XMLNAME = "THBZ") [ InitialExpression = "DEX71.41.007.01", Required ];

Property GHFS As Test.SendExternalModel.DE2(DE = "DEX71.41.008.01", DICLookUp = "Y", XMLNAME = "GHFS") [ InitialExpression = "DEX71.41.008.01", Required ];

Property GHF As Test.SendExternalModel.DE1(DE = "DEX71.41.009.01", XMLNAME = "GHF") [ InitialExpression = "DEX71.41.009.01", Required ];

Property ZLF As Test.SendExternalModel.DE1(DE = "DEX71.41.009.01", XMLNAME = "ZLF") [ InitialExpression = "DEX71.41.009.01", Required ];

Property QTF As Test.SendExternalModel.DE1(DE = "DEX71.41.009.01", XMLNAME = "QTF") [ InitialExpression = "DEX71.41.009.01", Required ];

Property WDBZ As Test.SendExternalModel.DE2(DE = "DEX71.41.010.01", DICLookUp = "Y", XMLNAME = "WDBZ") [ InitialExpression = "DEX71.41.010.01" ];

Property GHKSDM As Test.SendExternalModel.DE2(DE = "DE08.10.025.00", DICLookUp = "Y", XMLNAME = "GHKSDM") [ InitialExpression = "DE08.10.025.00", Required ];

}

3) Given all of your original class definitions, add a generator method in OPTREGISTER (or a parent class) that sets the "DE" property of any properties of type DE1/DE2 (or perhaps any properties that have your custom property parameters class) based on the DE property parameter in the containing class's definition. Ensure this method is called prior to XML export. (Might be useful if you need more fine-grained control - i.e., omitting the WDBZ element if it has no content.) This would be a great approach if you have lots of similar classes like these, to avoid repeating (1) every time. A really simple implementation (without any extra sanity checking for property types, etc.), equivalent to the first example, would look like:

Method %OnNew() As %Status [ CodeMode = objectgenerator, Private, ServerOnly = 1 ]
{
  Set tKey = ""
  For {
    #dim tProperty As %Dictionary.CompiledProperty
    Set tProperty = %compiledclass.Properties.GetNext(.tKey)
    If (tKey = "") {
      Quit
    }
    Set tDE = tProperty.Parameters.GetAt("DE")
    If (tDE '= "") {
      Do %code.WriteLine(" Set .."_$$$QN(tProperty.Name)_" = ##class("_tProperty.Type_").%New()")
      Do %code.WriteLine(" Set .."_$$$QN(tProperty.Name)_".DE = "_$$$QUOTE(tDE))
    }
  }
  Do %code.WriteLine(" Quit $$$OK")
  Quit $$$OK
}

In the possible absence of a built-in class for such a purpose, this seems to work:

Class DC.Demo.GlobalReference Extends %String [ ClassType = datatype ]
{

/// 511 is an upper bound for the maximum length of a global reference - see:
/// <a href="https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GGBL_structure#GGBL_structure_maxsubscrlen">Maximum Length of a Global Reference</a>
Parameter MAXLEN = 511;

ClassMethod IsValid(%val As %CacheString) As %Status [ ServerOnly = 0 ]
{
    Set tOldZReference = $ZReference
    Set tSC = $$$OK
    Try {
        Set $ZReference = %val
    } Catch e {
        // The above SET will throw a <SYNTAX> exception for an invalid global reference
        Set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Invalid global reference: %1",%val))
    }
    Set $ZReference = tOldZReference
    Quit tSC
}

}

This can be done with the requiredMessage property of %ZEN.Component.control. There are two ways to accomplish this:

1. Just add the requiredMessage attribute

Class DC.Demo.ZenLocalization Extends %ZEN.Component.page
{

XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen">
<form>
<text label="Descrição" required="true" requiredMessage="obrigatório." />
<submit caption="Enviar" />
</form>
</page>
}

}

Problem is, you'd need to do that in a lot of different places. Instead, you could...

2. Use custom components that subclass built-in control types.

Sample component:

Class DC.Demo.Component.text Extends %ZEN.Component.text [ System = 3 ]
{

/// Feel free to customize this.
Parameter NAMESPACE = "https://community.intersystems.com/post/change-language-required";

/// Value displayed in alert box by the form <method>validate</method>
/// method when this control is required and does not have a value.<br>
/// This is a localized value.
Property requiredMessage As %ZEN.Datatype.caption [ InitialExpression = "obrigatório." ];

}

Sample page using component:

Class DC.Demo.ZenLocalization Extends %ZEN.Component.page
{

XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen" xmlns:custom="https://community.intersystems.com/post/change-language-required">
<form>
<custom:text label="Descrição" required="true" />
<submit caption="Enviar" />
</form>
</page>
}

}

I was hoping there would be a way to use Zen's localization features to do this, but it seems like that isn't an option, unfortunately;"required." is hard-coded as the InitialExpression for requiredMessage in %ZEN.Component.control, and may only be localized within a page using the component if a non-default value is specified.

There was an issue with database compaction in 2014.1.1 that could cause database degradation - see this alert: https://www.intersystems.com/support-learning/support/product-news-alerts/support-alert/alert-database-compaction/

Given that, I would recommend contacting the experts in the Worldwide Response Center (InterSystems Support) to help investigate. Running an integrity check on the database you compacted would be a good start - see https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_integrity#GCDI_integrity_verify_portal.

Another slightly more lightweight approach:

Class DC.Demo.combobox Extends %ZEN.Component.combobox [ System = 3 ]
{

Parameter NAMESPACE = "http://community.intersystems.com/demo";

/// Notification that this component is about to become modal.
ClientMethod onStartModalHandler(zindex) [ Language = javascript ]
{
    this.invokeSuper('onStartModalHandler',arguments);
    this.fixLinks(this.getDropDownDiv());
}

/// Look for all children of specified element, and change links with href="#" to also have onclick = "return false;"
ClientMethod fixLinks(element) [ Language = javascript ]
{
    for (var i = 0; i < element.children.length; i++) {
        this.fixLinks(element.children[i]);
    }
    if (element.getAttribute("href") == "#") {
        element.onclick = function() { return false; };
    }
}

}

We specifically chose not to support this capability in Atelier, other than for the special case of launching web pages. As an implementation of Studio Source Control, running an executable on the client only makes sense in cases where the "server" and the "client" are always the same, and for that model the source control plugins publicly and freely available in the Eclipse ecosystem are far superior in terms of support and usability.

I would be interested to hear more details of your use case. What version control system are you using? Git?

(Note to other readers: "UserAction = 3" and other details of the Studio Extension framework are documented here.)

There are two good approaches to this, both relying on an onchange event handler on the first dataCombo.

The first, simpler solution would be to just have an onchange event handler set the value directly:

<dataCombo id="Grupo_Producto" name="Grupo_Producto" dataBinding="bdGrupo" size="40" 
 label="(*) Grupo" dropdownWidth="400" dropdownHeight="100" loadingMessage="Cargando..."
 columnHeaders="Codigo, Nombre"
 queryClass="Innova.CL.Core.BD.Configuracion.Operacional.Grupo" queryName="obtieneGrupoProd"
 sqlLookup="SELECT Nombre FROM Innova_CL_Core_BD_Configuracion_Operacional.Grupo Where ID = ?"
 onchange="zen('Linea_Producto').setProperty('parameters',1,zenThis.getValue());"
 > 
</dataCombo>

However, if you have multiple components on the page that depend on the current value of Grupo_Producto, it might be simpler to use a property of the page that has the value of the component copied to it when that value changes. This is handy because you can reference the value in a Zen expression, e.g., #(%page.GrupoProducto)#, rather than needing to copy the same value to lots of places. Here is an example with two dataCombos in the SAMPLES namespace:

Class DC.Demo.ComboBox Extends %ZEN.Component.page
{

Property homeState As %ZEN.Datatype.string;

XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen">
<dataCombo id="homeState" label="State"
sql="select distinct Home_State from Sample.Person order by Home_State"
onchange="zenPage.dataComboChanged(zenThis)"
/>
<dataCombo id="homeCity" label="City"
sql="select distinct %exact Home_City from Sample.Person where Home_State = ? order by Home_City">
<parameter value="#(%page.homeState)#" />
</dataCombo>
</page>
}

ClientMethod dataComboChanged(component) [ Language = javascript ]
{
    // Update the page property's value.
    // Note: page property and component ID don't necessarily need to match, but it's convenient if they do.
    // (Imagine a cascade of dataCombos with dependent values.)
    zenPage.setProperty(component.id,component.getValue());
    
    // Possibly also call refreshContents() on components referencing properties that may have changed,
    // and/or clear values that may have been invalidated by the change.
    // dataCombo does not need this, but other components (e.g., tablePane) would.
}

}

I can't speak to a fix, but a few thoughts:

  • There is a critical difference between Studio and Atelier projects: Atelier projects are not even present on the server / in the database. This may prevent them from being used interchangeably with Studio projects for purposes of your source control extension and change control processes.
    • Because of this difference, it would probably not be a good idea to populate ^||%Studio.Project with the name of the project in Atelier.
  • One option might be updating your source control extension (perhaps adding new menu items and UserAction/AfterUserAction handling for them) to support creating a %Studio.Project and associating/dissociating files with one, even from Atelier. This would support mixed use of Studio and Atelier and make migration simpler.
  • If you want to be able to detect in your source control extension code whether it is being called from Studio or Atelier, you can look at the StudioVersion property (defined in %Studio.Extension.Base). Particularly, this will contain the string "Atelier" if invoked from Atelier.

A nice enhancement for Atelier might be supporting a project definition that is shared between the client and server.

If you're really interested in efficiency, one option might be put the list into a local array with $ListNext and then iterate over the array in reverse (either with a decrementing counter in a for loop, or using $order(array(sub),-1)). For example, rather than:

For i=$ListLength(tInputList):-1:1 {
    Set tValue = $ListGet(tInputList,i)
    // Do something with tValue
}

You could use something like the following (which accounts for null values in the $ListBuild list by skipping that index in the integer-subscripted array):

Kill tArray
Set tPointer = 0
While $ListNext(tInputList,tPointer,tValue) {
    If $Data(tValue) {
        Set tArray($i(tArray)) = tValue
    } Else {
        Set tArray = tArray + 1
    }
}

Set tKey = ""
For i=$Get(tArray):-1:1 {
    If $Data(tArray(i),tValue) {
        // Do something with tValue
    }
}

For "large enough" lists (the threshold for this depends on the length of the list elements), the second approach will be faster. (In testing, this performs better than putting the reversal of the original $ListBuild list into another $ListBuild list.)

More generally speaking: if you have a list that you only append to the end of (rather than needing to insert/shift elements randomly in the middle of it), and will need to iterate over it in reverse, a local array with integer subscripts is a better choice of data structure than a $ListBuild list.

To expand on Sean's answer from the comment section, perhaps try the numeric character reference for the fullwidth comma:

columnHeaders="House number&#65292;apartment"

Another option would be to create a custom Zen component that extends %ZEN.Component.dataCombo and handles delimiters differently for the columnHeaders property. Unfortunately, this approach would require overriding %DrawDropDownContents, which is an enormous method and flagged as [Internal] - best avoided, in my opinion!

I don't know of any magic $zf/$zu function or API to do this. Looking at StackOverflow, a common approach seems to be parsing the output of "tasklist" filtered by executable name and/or window title.

Here's a classmethod to get the PIDs of all processes with a given executable name (Windows-only, of course):

Class DC.Demo.WindowsProcessList
{

ClassMethod GetPIDsByExecutable(pExecutable As %String = "", Output pPIDList As %List) As %Status
{
    Set tSC = $$$OK
    Set tFileCreated = 0
    Set pPIDList = ""
    Try {
        Set tSC = ##class(%Net.Remote.Utility).RunCommandViaZF("tasklist /V /FO CSV /NH",.tTempFileName,,,0)
        $$$ThrowOnError(tSC)
        Set tFileCreated = (tTempFileName '= "")
        Set tRowSpec = "ROW(ImageName VARCHAR(255),PID INT)"
        Set tResult = ##class(%SQL.Statement).%ExecDirect(,
            "call %SQL_Util.CSV(,?,?)",
            tRowSpec,tTempFileName)
        
        // For debugging (if tRowSpec changes):
        // Do tResult.%Display()
        
        If (tResult.%SQLCODE < 0) {
            Throw ##class(%Exception.SQL).CreateFromSQLCODE(tResult.%SQLCODE, tResult.%Message)
        }
        
        // Note: could instead call tResult.%NextResult() to get a result set with column names as properties.
        While tResult.%Next(.tSC) {
            $$$ThrowOnError(tSC)
            If ($ZConvert(tResult.%GetData(1),"L") = $ZConvert(pExecutable,"L")) {
                Set pPIDList = pPIDList_$ListBuild(tResult.%GetData(2))
            }
        }
        $$$ThrowOnError(tSC)
    } Catch e {
        Set tSC = e.AsStatus()
    }
    
    // Cleanup
    If tFileCreated {
        Do ##class(%Library.File).Delete(tTempFileName)
    }
    Quit tSC
}

}

To provide server-side enforcement as well (from a security perspective, in addition to what you have client-side to provide a nicer experience for well-behaved users), you should be able to use %CSP.SessionEvents with OnLogin overridden to check a global (or something) and return an error set %session.EndSession = 1 if logins are disabled. This class would need to be set up for the web application(s) through which your application is accessed - see "Event Class" here.

When working with files, it's much better to use %Stream classes rather than an instance of %Library.File. If you're looking to get the attributes of a file/directory, the Attributes method in %Library.File is probably your best option. According to the class reference (which has all the details), it returns a combination of bits, the meaning of which varies depending on the operating system. This is kind of ugly, but here's an example on Windows:

USER>set attributes = ##class(%Library.File).Attributes("C:\Users\tleavitt\AppData\")
 
USER>w attributes
8210

USER>for i=0:1:16 { w:$zboolean(attributes,2**i,1) 2**i,! }
2
16
8192

According to the class reference, this is a directory (16) that is hidden (2) and not indexed (8192).

%Library.File also has some OS-independent classmethods that may be easier to use for simpler things like seeing if a file is read-only.

In a bit of testing, I was able to see this by using the wrong protocol - i.e., navigating to the web socket directly in the browser, or using http or https in the client code when it should be ws or wss. The "client code" in the documentation you linked should go in a separate page (CSP or just plain old HTML/JS) from the class that extends %CSP.WebSocket, and navigating to /csp/user/MyApp.MyWebSocketServer.cls (as in that example) directly will not work.

If that isn't clear or doesn't solve the problem, could you share your code and your Caché version ($zv)? Your web server and browser version may also be relevant, since web sockets are fairly new.

The Caché installation directory is easy:

USER>w $System.Util.InstallDirectory()
c:\intersystems\healthshare_4\

The OS version is OS-dependent.

On Windows only:

USER>write $System.Util.GetOSVersionInfo()
6.1.7601

On Unix:

USER>set sc = ##class(%Net.Remote.Utility).RunCommandViaZF("uname -r",,.tOutput) write tOutput
2.6.32-220.el6.x86_64

USER>set sc = ##class(%Net.Remote.Utility).RunCommandViaZF("lsb_release -a",,.tOutput) write tOutput
LSB Version:    :core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch
Distributor ID: RedHatEnterpriseServer
Description:    Red Hat Enterprise Linux Server release 6.2 (Santiago)
Release:        6.2
Codename:       Santiago