At a low level, there are two options:

Pipes / command pipes - useful if you want to be able to read command output OR write to the command. Unfortunately, you have to pick one or the other. To answer Nikita's question - maybe there's some tricky way around this by building a command that redirects stdout to a file, which is read using a separate device?

$zf(-1) $zf(-2) - these are a little bit easier to use. $zf(-1) spawns a child process and waits for it to return; $zf(-2) doesn't wait.

Needless to say, if you're building these into an application, make sure that you're careful about building user input into the commands that you execute. wink

System-wide:

Set ^%oddENV("callererrorinfo")=1

Or for the current namespace:

Set ^%oddENV("callererrorinfo",$namespace)=1

For more information, see macro definitions in %occEnvironment.inc. 

You can also set this global to 2 instead of 1, which has the side effect of including a full stack trace when a %Status is converted to a human-readable string (e.g., via $System.Status.GetErrorText()). This is handy for debugging, but probably not something to use on a production system (since it could end up exposing more information about your code than you'd really want).

I can't think of a built-in stored procedure for this, but it would be relatively simple to create one to wrap $System.Encryption.SHAHash(bitlength, text).

For example, approximating HASHBYTES with the features built in to $System.Encryption:

Class DC.Demo.SQL
{

ClassMethod HashBytes(pAlgorithm As %String, pText As %String) As %Binary [ SqlProc ]
{
    // Note: will result in <ILLEGAL VALUE> if pAlgorithm is not supported
    Quit $Case($ZConvert(pAlgorithm,"L"),
        "md5":$System.Encryption.MD5Hash(pText),
        "sha":$System.Encryption.SHA1Hash(pText),
        "sha1":$System.Encryption.SHA1Hash(pText),
        "sha2_256":$System.Encryption.SHAHash(256,pText),
        "sha2_512":$System.Encryption.SHAHash(512,pText))
}

}

Use (in SAMPLES):

select top 5 Name,DC_Demo.SQL_HashBytes('sha2_512',DOB) from Sample.Person

For a more direct approach, see the class query %SYS.ProcessQuery:PPG - it's not [SqlProc] so you need to call it the old-fashioned way.

For example:

Terminal 1:

USER>w $j
10740
USER>s ^||demo(2)=5,^||demo=2
USER>zw ^||demo
^||demo=2
^||demo(2)=5

Terminal 2:

%SYS>set rs = ##class(%ResultSet).%New("%SYS.ProcessQuery:PPG")
%SYS>set sc = rs.Execute("demo",10740)
%SYS>while rs.%Next() { do rs.%Print() }
demo 2
demo(2) 5

Generally speaking, the parameters defined in %ZEN.DataModel.objectModelParameters can be used to customize the appearance/behavior/really anything about the controls in a dynaForm. One such parameter is:

/// Optional.
/// id of a group component that the control used for this property
/// should be added to. This provides a way to control layout.<br>
/// If not defined, the control is added directly to the <class>dynaForm</class>.
Parameter ZENGROUP As STRING;

For example, given the model class:

Class DC.Demo.ZenDynaForm.Model Extends %ZEN.DataModel.ObjectDataModel
{

Property FirstName As %String(ZENGROUP = "nameGroup");

Property MiddleInitial As %String(ZENGROUP = "nameGroup");

Property LastName As %String(ZENGROUP = "nameGroup");

Property HomePhone As %String(ZENGROUP = "phoneGroup");

Property CellPhone As %String(ZENGROUP = "phoneGroup");

}

The following <dynaForm> will display the three name fields in one horizontal row, and the two phone fields in another:

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

/// This XML block defines the contents of this page.
/// Setting XMLNamespace turns on StudioAssist for this XML block.
XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen">
<dataController id="myController" modelClass="DC.Demo.ZenDynaForm.Model" />
<dynaForm controllerId="myController">
<hgroup id="nameGroup" />
<hgroup id="phoneGroup" />
</dynaForm>
</page>
}

}

Here's some relevant documentation, if I'm understanding your question correctly: http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GZAP_customization

There are two topics covered here: custom Zen components, which have their own HTML rendering, and composite components, which are simply reusable groupings of other Zen components.

These really wouldn't be "system-defined" - you'd have them in a separate XML namespace and they'd be distinct from the "system-defined" Zen component classes, but they can be used in the same page alongside standard Zen components.

Hi Laura - not sure if you ever figured this  out, but here's an example using a generic OnDrawCell delegator method. The specific example is intended to run against a table in the SAMPLES namespace.

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

/// This XML block defines the contents of this page.
/// Setting XMLNamespace turns on StudioAssist for this XML block.
XData Contents [ XMLNamespace = "http://www.intersystems.com/zen" ]
{
<page xmlns="http://www.intersystems.com/zen" height="100%">
<tablePane tableName="Sample.Company">
<column colName="ID" hidden="true" />
<column colName="Name" />
<column colName="Mission" OnDrawCell="DrawMissionCell" seed="90" />
</tablePane>
</page>
}

/// <var>pSeed</var> is the maximum length to show in the cell.
Method DrawMissionCell(pTable As %ZEN.Component.tablePane, pName As %String, pSeed As %String) As %Status
{
    Set tFullText = %query(pName)
    Set tTitle = ""
    Set tStyle = ""
    Set tLine = $Extract(tFullText,1,pSeed)
    If (tLine '= tFullText) {
        Set tStyle = "font-style: italic;"
        Set tTitle = tFullText
        Set tLine = tLine_" ..."
    }
    &html<<div style="#(tStyle)#" title="#(..EscapeHTML(tTitle))#">#(..EscapeHTML(tLine))#</div>>
    Quit $$$OK
}

}

An OnDrawCell delegator method generally just renders HTML for the table cell. In this case, if the text in the cell is greater than some specified length (the "seed" attribute of the column, which is passed to the OnDrawCell delegator method), then the text in the column is italicized and the tooltip is set to the full text. Otherwise, the cell is shown as usual with no tooltip. Note that the HTML escaping is done at the very end - you wouldn't want to truncate the text in the middle of an escape sequence.

You could make this method simpler and always have it show the tooltip, if you'd like. It's also generic (no hard-coded column name), so you could put it in a Zen template class and reuse it across multiple pages (probably after giving it a better name).

Another alternative would be subclassing %ZEN.Component.tablePane and overriding %DrawTable to add zen expression support to cellTitle, but unfortunately that method is quite ugly and monolithic. OnDrawCell is the most clear and maintainable option.

It looks like some of this may have come from https://community.intersystems.com/post/example-connecting-cach%C3%A9-web-service-php

The remainder of that example may also be useful, for demonstrating how to use the correct SOAPACTION (which the error message you have refers to). This entails using __soapCall with an array of options including the expected SOAPACTION value.

// Object with parameter names and values for the SOAP call
$request = new stdClass();
$request->id = $id;

// This goes in an associative array with key FindPersonSoapIn (see WSDL)
$params = array();
$params['FindPersonSoapIn']=$request;

// In the array of options for the soap call, soapaction must be specified since there's no WSDL to refer to.
// PHP provides a default value of <uri>#<method>, but that's incorrect.
$options = array('soapaction'=>$uri.'/SOAP.Demo.FindPerson');

// Actually call the web service
$result = $client->__soapCall("FindPerson",$params,$options);

The problem with using the strategy from that code snippet on breakpoints is that they're separate persistent objects - the approach in the code snippet is to make a temporary change to the object that will be exported, but this doesn't seem to work (after a few attempts with different approaches) for breakpoints without breaking things in Studio.

Having project items sorted alphabetically is helpful to avoid conflicts in source control with multiple people working on the same project at the same time.

Example:

User A adds a file. User B adds a file. User A commits their change. User B goes to commit their change and can't because a conflict is detected due to User A's change to the same line of code (because both added new project items at the end of the list).

Some indirect answers that may be useful:

I don't tend to use the call stack view in the Studio debugger. For many cases where stack information is useful for debugging, I'll typically just add this to my code:

do LOG^%ETN 

Then run it, and then look in the application error log (visible in Management Portal at System Operation > System Logs > Application ErrorLog, or via do ^%ER) to see what the stack/variables were when it was called.

Also, you should be able to clear out locks (perhaps cautiously) in the management portal from System Operation > Locks > Manage Locks.

Here's a snippet from my Studio Extension (a subclass of one of the more standard extensions) that you might use and adapt in yours. It deals with a few other things that /diffexport misses - the timestamp, and the order of project items (which will normally have newly-added items at the bottom, out of alphabetical order). Unfortunately, I haven't found a way to handle breakpoints - probably the easiest way to get rid of those would be applying an XSLT to the file after it's been exported, which is pretty bad.

Method OnAfterSave(InternalName As %String, Object As %RegisteredObject) As %Status
{
    Set tFileName = ..ExternalName(InternalName)
    If tFileName = "" {
        Quit $$$OK
    }
    
    Set tName = $Piece(InternalName,".",1,*-1)
    Set tExt = $ZConvert($Piece(InternalName,".",*),"U")
    
    // Special handling for projects to ensure that newly-added items don't show up at the bottom of the XML export.
    // This tends to cause meaningless diffs (at best) and conflicts (at worst)
    If (tExt = "PRJ") {
        Set tProject = ##class(%Studio.Project).%OpenId(tName,,.tSC)
        If $IsObject(tProject) {
            // Save the project for real (we'll be in %OnAfterSave for the project when this happens,
            // but %Studio.SourceControl.Interface protects against <FRAMESTACK> by moving %SourceControl
            // to tmp, so this should be perfectly fine).
            // If the project is not saved, the items will be in the wrong order.
            If tProject.%Save() {
                // Reload the project. We need to save first to be sure all ProjectItem changes are commited.
                // This will load them up freshly, in the normal order.
                Do tProject.%Reload()
            }
            // Clear a few properties, since /diffexport won't be respected.
            // This won't actually be saved, but these things shouldn't be in the export to disk.
            Set tProject.LastModified = ""
            Set tProject.Target = ""
            Set tProject.TargetType = ""
        }
    }
    Quit ##super(.InternalName,.Object)
}

If you're looking to add RegEx-based validation to a property, see this post.

If you're looking to do RegEx matching in a query... I don't think there's a built in function for this (!), but it's easy enough to do in a stored procedure:

ClassMethod MatchesRegEx(pText As %String, pRegEx As %String) As %Boolean [ SqlProc ]
{
    Quit $Match(pText,pRegEx)
}

See class documentation for Security.Applications.

Example:

Class DC.Demo.DeepSeeEnable
{

ClassMethod SetDSFlag(pApplicationName As %String, pValue As %Boolean = 1) As %Status
{
    Set tSC = $$$OK
    Try {
        New $Namespace
        Set $Namespace = "%SYS"
        
        Set tApplication = ##class(Security.Applications).%OpenId(pApplicationName,,.tSC)
        If $$$ISERR(tSC) {
            Quit
        }
        
        Set tApplication.DeepSeeEnabled = pValue
        Set tSC = tApplication.%Save()
    } Catch e {
        Set tSC = e.AsStatus()
    }
    Quit tSC
}

}

Use:

USER>s sc = ##class(DC.Demo.DeepSeeEnable).SetDSFlag("/csp/user",1)
 
USER>w sc
1

Hi Sebastian,

In the past, when I've tried to be creative and make <dataCombo> work more like a <select>, the better solution has been to just use a <select>, possibly customized a little bit to make it behave nicer with changing query parameter values. What's the reasoning for using a <dataCombo> rather than a <select> in your case?

Here's some custom component code that might serve as a basis for a full solution. The trick is setting editable="true", overriding findSelectedItem to select the best match to the input text, and calling findSelectedItem after changes, using the built-in timer for the sake of simplicity. Compare to a normal dataCombo for reference.

/// dataCombo subclass with limited support for responding to keyboard events
Class DC.Demo.ZEN.Component.dataCombo Extends %ZEN.Component.dataCombo [ System = 3 ]
{

///  This is the XML namespace used for library components.
Parameter NAMESPACE = "http://www.intersystems.com/zen/dc/demo";

/// Always editable, of course.
Property editable As %ZEN.Datatype.boolean [ InitialExpression = 1 ];

/// Onclick, show the dropdown.
Property onclick As %ZEN.Datatype.eventHandler [ InitialExpression = "zenThis.showDropdown();" ];

/// Find and select item within the dropdown that matches current control value.
/// This is called when the dropdown appears to make sure that the current
/// item is highlighted.
ClientMethod findSelectedItem(force, update, select) [ Language = javascript ]
{
    force = ('undefined'!=force)?force:false;
    update = ('undefined'!=update)?update:true;
    select = ('undefined'!=select)?select:false;
    var inputValue = this.findElement('input').value.toUpperCase();
    this.keyMode = true;
    if (force||this.isDropdownVisible) {
        var count = this.getOptionCount();
        var dVal = new Array();
        for (var idx = 0; idx < count; idx++) {
            dVal[idx] = this.getOptionText(idx).toUpperCase();
            if ((inputValue <= dVal[idx])&&((idx == 0)||(inputValue > dVal[idx-1]))) {
                this.selectItem(idx,update,select);
                break;
            }
        }
    }
}

/// Start (or restart) timer normally used by "timer" mode (overridden to apply to all modes)
/// Users should not call this method.
ClientMethod startTimer() [ Internal, Language = javascript ]
{
    this.clearTimer();
    this.actionTimerId = self.setTimeout("zenPage.getComponent("+this.index+").timerHandler()",this.delay);
}

/// Clear timer normally used by "timer" mode (overridden to apply to all modes)
/// Users should not call this method.
ClientMethod clearTimer() [ Internal, Language = javascript ]
{
    if (this.actionTimerId) {
        self.clearTimeout(this.actionTimerId);
        this.actionTimerId = null;
    }
}

/// Timer event handler normally used by "timer" mode (overridden to apply to all modes)
/// Users should not call this method.
ClientMethod timerHandler() [ Internal, Language = javascript ]
{
    if (this.isDropdownVisible) {
        // refresh drop down only if searchKeyLen is not defined!
        if ((this.searchKeyLen != '') && (this.searchKeyLen > 0)) {
            this.renderDropdown();
        } else {
            this.findSelectedItem(); //Just find the selected item.
        }
    }
    else {
        this.showDropdown();
    }
}

/// Change handler for input control.
/// Users should not call this method.
ClientMethod inputChangeHandler() [ Internal, Language = javascript ]
{
    this.invokeSuper('inputChangeHandler');
    
    //If input was cleared, and the actual control value was changed, then clear the value and notify.
    var input = this.findElement('input');
    if (input.value == '') {
        if (this.getValue() != '') {
            this.setValue('');
            this.onchangeHandler();
        }
    }
}

/// Notification that this component is about to stop being modal.
ClientMethod onEndModalHandler(zindex) [ Language = javascript ]
{
    this.findSelectedItem(true,false,true);
    this.invokeSuper('onEndModalHandler',arguments);
}

}

In case you're unfamiliar with custom components: you can include this one in a Zen page like this:

<page xmlns="http://www.intersystems.com/zen" xmlns:demo="http://www.intersystems.com/zen/dc/demo">
<demo:dataCombo sql="select 'your query here'/>
</page>

Or like this:

<page xmlns="http://www.intersystems.com/zen" >
<dataCombo xmlns="http://www.intersystems.com/zen/dc/demo" sql="select 'your query here'" />
</page>

This just looks like a bug - the generated code is:

 Do tInstaller.Import(tNSName,tInstaller.Evaluate("${AddonDir}/AddClasses.xml"),"ck","5202","5373","0")

While you would expect:

 Do tInstaller.Import(tNSName,tInstaller.Evaluate("${AddonDir}/AddClasses.xml"),"ck","5202,5373","0")

Here's a possible workaround (untested, but the generated code looks better):

<Var Name="AddClassesErrors" Value="5202,5373" />
<If Condition='#{##class(%File).Exists("${AddonDir}/AddClasses.xml")}'>
  <Import File="${AddonDir}/AddClasses.xml" IgnoreErrors="${AddClassesErrors}" Flags="ck" />
</If>

EDIT: actual workaround (see discussion below) is to use #{<COS_expression>} (see documentation).

<Var Name="AddClassesErrors" Value="#{&quot;5202,5373&quot;}" />
<If Condition='#{##class(%File).Exists("${AddonDir}/AddClasses.xml")}'>
  <Import File="${AddonDir}/AddClasses.xml" IgnoreErrors="${AddClassesErrors}" Flags="ck" />
</If>

Here's a solution that works for me:

s string="http://www.google.com"
set matcher=##class(%Regex.Matcher).%New("(\b(https?|ftp)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]*[-A-Za-z0-9+&@#/%=~_|])",string)
w matcher.ReplaceAll("<a href='$1' target='_blank'>$1</a>")

Key changes:

  • Remove the enclosing / /gim - this is part of the regex syntax in JS.
  • Add two a-z ranges for case insensitivity
  • Remove JS escaping of slashes (not necessary)

Actually, this seems to just work (as part of a %CSP.REST subclass, used in a properly configured web application - the Management Portal UI disables the "CSP Files Physical Path" field if you enter a dispatch class, but will save it if you add the physical path first):

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<!-- ... various Routes/Forwards like in a typical REST API ... -->
<!-- Other routes: pass through to file system -->
<Route Url="/(.*)" Method="GET" Call="ServeStaticFile" />
</Routes>
}

ClassMethod ServeStaticFile(pPath As %String) As %Status
{
    #dim %request As %CSP.Request
    Do %request.Set("FILE",%request.Application_pPath)
    Quit ##class(%CSP.StreamServer).Page()
}

If there's nothing more to it than that... smiley

Looking at that documentation, one difference between LIST and GROUP_CONCAT is that GROUP_CONCAT lets you specify the separator, while LIST always uses a comma.

If you wanted to use a different separator, and your data will never contain commas, then it's as easy as (for example):

select home_city as "City", count(*) as "Count", REPLACE(LIST(Name),',',' ') as "Names"
from sample.person
group by home_city

If "your data will never contain commas" is a bad assumption (as it is in the case of Name in Sample.Person), the solution is to use %DLIST and $ListToString.

select home_city as "City", count(*) as "Count", $ListToString(%DLIST(Name),' ') as "Names"
from sample.person
group by home_city

%DLIST builds a $ListBuild list, and $ListToString joins the list elements with the specified separator.

%DLIST is useful in other cases too - for example, if your data might contain commas and you want to iterate over the aggregated data after running dynamic SQL.