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>

That isn't valid XML - I think it'd need to be:

 <Var Name="AddClassesErrors" Value="&quot;,5202,5373,&quot;" />

The &quot; makes it smarter about it being one string, and the extra commas should make it work with the test condition in %Installer.Install:Import:

 ((","_pIgnoreErrors_",")[(","_$P($system.Status.GetErrorCodes(tSC),",")_","))

This is messy! sad

EDIT: Better option:

 <Var Name="AddClassesErrors" Value="#{&quot;5202,5373&quot;}" />

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)

I did, but I added it as a comment rather than an answer, so I can't mark it as the accepted answer. Regardless, I followed up this morning, and have been advised that the Management Portal's behavior is a bug and may be "fixed" in the future. The preferred solution would be additional configuration, either at the webserver level or adding more web applications.

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