go to post Timothy Leavitt · Jun 13, 2018 It's not the prettiest, but I think the simplest solution would be to avoid navigating to the parent object entirely:Add a Foobar property to EmbedObj.Via a row/object trigger in ContainerObj, propagate changes to Foobar to EmbedObj_Foobar.As an initial step for data population in existing records, run SQL: update ContainerObj set EmbedObj_Foobar = FoobarBase your SQLComputeCode on the copy of Foobar in the serial class.
go to post Timothy Leavitt · Jun 12, 2018 Re: extending method keywords, you can't do that at this time, but a useful approximation is structuring a comment - for example: /// @MyKeyword MyValue ClassMethod TestOne() { // Implementation } And then looking at the %Dictionary.MethodDefinition / %Dictionary.CompiledMethod documentation in a generator method. (But it looks like you might already be on to that with @AutoGenerated.) Re: making first compilation work, this works for me, by making the first compilation automatically trigger a second one when needed: ClassMethod GenerateMethods() As %Status [ CodeMode = objectgenerator ] { For i=1:1:%class.Methods.Count() { #dim method As %Dictionary.MethodDefinition = %class.Methods.GetAt(i) Continue:((method.Description["@AutoGenerated") || (method.CodeMode="objectgenerator")) Do %class.Methods.Insert(##class(util.TestGenerator).Generate(method.Name_"DoSomethingElse",%class.Name)) } If (%class.Methods.%IsModified()) { Do ##class(%Projection.AbstractProjection).QueueClass(%class.Name) } Quit %class.%Save() } Test code: Class util.Driver { ClassMethod Run() { do ##class(%Dictionary.MethodDefinition).IDKEYDelete("util.Test","TestOneDoSomethingElse") do ##class(%Dictionary.MethodDefinition).IDKEYDelete("util.Test","TestTwoDoSomethingElse") do $system.OBJ.UnCompile("util.*") do $system.OBJ.Compile("util.*","ck") } }
go to post Timothy Leavitt · Jun 8, 2018 If you want to count (or otherwise traverse) all the elements in a multidimensional array, you can use $Query - here's a sample with method that does that: ClassMethod Run() { Set a(1) = "blue" Set a(1,3,5,2) = "navy" Set a(2) = "red" Set a(2,2) = "crimson" Set a(3) = "yellow" Write ..Count(.a) } ClassMethod Count(ByRef pArray) As %Integer [ PublicList = pArray ] { Set count = 0 Set ref = $Query(pArray("")) While (ref '= "") { Set count = count + 1 Set ref = $Query(@ref) } Quit count }
go to post Timothy Leavitt · May 30, 2018 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 o = ##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 }
go to post Timothy Leavitt · May 30, 2018 One option is to increase $zstorage (see documentation) to let the process use more memory; since it's just a one-time import, this may be simplest. Another option is to set the UsePPGHandler property to true (1) on the instance of %XML.Reader - this has a negative impact on performance.
go to post Timothy Leavitt · May 18, 2018 Thanks for pointing that out, but it's not really ideal for my use case, since it's not a % class and explicitly documented "Only to be used in the context of DataCheck." I think I'd also like to permit extended global references, which my original approach does.
go to post Timothy Leavitt · May 18, 2018 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 } }
go to post Timothy Leavitt · May 9, 2018 With a dynaForm, this would look like: Class DC.Demo.ZenLocalizationModel Extends %ZEN.DataModel.ObjectDataModel { Property Descrição As %String(ZENATTRS = "requiredMessage:obrigatório.") [ Required ]; } And: Class DC.Demo.ZenLocalization Extends %ZEN.Component.page { XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ] { <page xmlns="http://www.intersystems.com/zen"> <dataController id="myController" modelClass="DC.Demo.ZenLocalizationModel" /> <dynaForm controllerId="myController" injectControls="before"> <submit caption="Enviar" /> </dynaForm> </page> } } For more information on how to customize data model behavior with property parameters, see the class reference for %ZEN.DataModel.objectModelParameters.
go to post Timothy Leavitt · Apr 30, 2018 For completeness, here's an extension of the above example that actually works the way you'd expect (using PublicList to handle scope, and New to avoid leaking any variables): Class DC.Demo.Indirection { ClassMethod Driver() [ PublicList = x ] { New x Do ..Run() Do ..RunSets() Do ..Run() } ClassMethod Run() [ PublicList = x ] { Set x = 5 Try { Write @"x" } Catch e { Write e.DisplayString() } Write ! Try { Xecute "write x" } Catch e { Write e.DisplayString() } Write ! } ClassMethod RunSets() [ PublicList = x ] { Set x = 5 Set @"x" = 42 Write x,! Xecute "set x = 42" Write x,! } } Results: USER>d ##class(DC.Demo.Indirection).Driver() 5 5 42 42 5 5 USER>w USER> But that doesn't mean it's a good idea to do things like this.
go to post Timothy Leavitt · Apr 30, 2018 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.
go to post Timothy Leavitt · Apr 30, 2018 Most of the time, you don't really need to use XECUTE/indirection/etc - there is usually a better way to accomplish the same thing. Furthermore, in modern coding practices, XECUTE and name indirection probably will not work the way you expect, because they can only access public variables (not those in the scope of your method). For example, given the following class: Class DC.Demo.Indirection { ClassMethod Run() { Set x = 5 Try { Write @"x" } Catch e { Write e.DisplayString() } Write ! Try { Xecute "write x" } Catch e { Write e.DisplayString() } } ClassMethod RunSets() { Set x = 5 Set @"x" = 42 Write x,! Xecute "set x = 42" Write x,! } } The output when run in terminal is: USER>Do ##class(DC.Demo.Indirection).Run() <UNDEFINED> 9 zRun+3^DC.Demo.Indirection.1 x <UNDEFINED> 9 zRun+9^DC.Demo.Indirection.1 x USER>d ##class(DC.Demo.Indirection).RunSets() 5 5 USER>d ##class(DC.Demo.Indirection).Run() 42 42 Your code certainly should not rely on such scoping behavior. There are ways around this (for example, adding [ PublicList = x ] to the method signature, using New, etc.), but they are messy and difficult to understand and maintain. In your particular case, using a local array would probably be a better approach - for example: set x(counter) = DIAGS Although if you provided more context for why you think you need to dynamically set variables, the community might have other suggestions.
go to post Timothy Leavitt · Apr 27, 2018 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.
go to post Timothy Leavitt · Apr 26, 2018 What Ensemble version are you running? (In Terminal, write $zv)
go to post Timothy Leavitt · Mar 29, 2018 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; }; } } }
go to post Timothy Leavitt · Mar 28, 2018 There are also complete details of this and other differences in Studio Extension support between Studio and Atelier in the Atelier documentation.
go to post Timothy Leavitt · Mar 28, 2018 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.)
go to post Timothy Leavitt · Mar 20, 2018 What are you trying to do? This? USER>Set rm=##class(%Regex.Matcher).%New("[0-9]","Word 123456") USER>Write rm.ReplaceAll(".") Word ...... Or: USER>Set rm=##class(%Regex.Matcher).%New("[0-9]+","Word 123456") USER>Write rm.ReplaceAll(".") Word .
go to post Timothy Leavitt · Feb 6, 2018 USER>d $System.OBJ.GetPackageList(.classes,"%CSP.Documatic") zw classes classes("%CSP.Documatic.CubeInfo")="" classes("%CSP.Documatic.PrintClass")=""