Mindoo Blog - Cutting edge technologies - About Java, Lotus Notes and iPhone

  • Configure Eclipse 4.6.x with HCL Notes 12.0.1 FP1

    Karsten Lehmann  18 November 2022 22:29:22
    Once again I had to ask HCL development for setup instructions how to launch the HCL Notes Standard Client from an Eclipse IDE, this time for Notes 12.0.1 FP1.

    The instructions I had gotten earlier for Notes 10 have not changed much (I think there's one additional VM argument at the end of the list), but enough so that they did not work anymore.

    The document links to Eclipse Neon 4.6.3, but it's still working for me in later versions, I tried it with Eclipse 2020-09 (4.17.0, 64 bit).


    I used my local installation directory in the text below (C:\Program Files (x86)\HCL\Notes) and the plugin version for com.ibm.rcp.base in Notes 12.0.1 FP1.

    Of course these need to match your local environment.


    1. Download Eclipse Neon (4.6.3) or later from link below:

    http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/neon/3/eclipse-committers-neon-3-win32-x86_64.zip

    2. Get HCL Notes 12.0.1 FP1

    I used the instructions with the 32 bit Notes Client so far.

    3. Go to Windows => Preferences => Java => Installed JREs
    • Add => Standard VM =>
    • JRE home: C:\Program Files (x86)\HCL\Notes\jvm
    • JRE name: “Notes JRE”
    • Select the JRE to point to this and Apply

    4. Go to Windows => Preferences => Plug-in Development => Target Platform
    • Add => default => Target Content
    • Name: “Notes Target”

    4a. In Locations Tab:
    • Add => Directory => Location => C:\Program Files (x86)\HCL\Notes\framework\rcp\eclipse\plugins
    • Add => Directory => Location => C:\Program Files (x86)\HCL\Notes\framework\shared\eclipse\plugins
    • Finish
    • Select the Target platform to point to “Notes Target” and Apply

    4 b. In Content Tab:
    • go through the plugin list
    • for duplicate plugins, deactivate the older one
    • only relevant if you have installed a fixpack

    4 c. In Environment Tab:

    Please select following settings:
    • Operating System: win32
    • Windowing System: win32
    • Architecture: x86
    • Locale: en_US - English (United States)

    5. Go to Windows => Preferences => Run/Debug => String Substitution

    New => Add 2 strings (change plugin version and path based on your setup)


    5a. Name: rcp_home

    Value: C:\Program Files (x86)\HCL\Notes\framework

    5b. Name: rcp_base

    Value: C:\Program Files (x86)\HCL\Notes\framework\rcp\eclipse\plugins\com.ibm.rcp.base_10.0.0.20211117-0921


    OK

    Close this Preferences Window


    6. Put the following file inside the below plugin:

    C:\Program Files (x86)\HCL\Notes\framework\rcp\eclipse\plugins\com.ibm.rcp.base_10.0.0.20211117-0921


    rcp.security.properties



    7. Open Debug Configurations in the ‘Debug Perspective’ => Eclipse Configuration => New

    7a. In the Main tab:


    Name: NotesDebug

    Program to Run =>  Run a product => com.ibm.notes.branding.notes
    Java Runtime Environment => Runtime JRE => Select “Notes JRE” i.e. the one we added in step 3


    7b. In the Arguments tab:


    Program Arguments:

    -clean -console -debug -log -personality com.ibm.rcp.platform.personality -config notes


    VM Arguments:

    -Xquickstart
    -Xss384k
    -Xshareclasses
    -Drcp_home="${rcp_home}"
    -Drcp.install.config=user
    -Dosgi.install.area="${rcp_home}\eclipse"
    -Disa.ignoreESR=true
    -Dcom.ibm.pvc.osgiagent.core.logfileloc="${rcp_home}\rcp"
    -Dcom.ibm.pvc.webcontainer.port=0
    -Declipse.pluginCustomization="${rcp_home}\rcp\plugin_customization.ini"
    -Djava.security.properties="${rcp_base}\rcp.security.properties"
    -Declipse.registry.nulltoken=true
    -Djava.protocol.handler.pkgs=com.ibm.net.ssl.www.protocol
    -Djava.util.logging.config.class=com.ibm.rcp.core.internal.logger.boot.LoggerConfig
    -Dosgi.hook.configurators.exclude=org.eclipse.core.runtime.internal.adaptor.EclipseLogHook
    -Dosgi.framework.extensions=com.ibm.rcp.core.logger.frameworkhook,com.ibm.rds,com.ibm.cds
    "-Xbootclasspath/a:${rcp_base}\rcpbootcp.jar"
    -Xdump:system:events=user


    8. Apply => Debug – This will launch your Notes in Debug mode.

    Ensure that whenever you launch the Notes from Eclipse, there should be no other instance of Notes already running. If so, please close Notes and then launch from Eclipse to debug.

    New release of Open Eclipse Update Site to fix issues with Win/64 Notes Client 12.0.2

    Karsten Lehmann  17 November 2022 23:24:41
    As Jesse blogged there are some issues in the new Windows 64 Bit Notes Client 12.0.2 that break the "Import Local Update Site" functionality of the standard Update Site template of Domino.

    To fix this and other issues, I just uploaded a new release of the Open Eclipse Update Site on OpenNTF.

    Here is the fixlist:

     
    • This release fixes LS errors in the new Windows 12.0.2 64 Bit client that occur because LS tries to load SWT Java classes, but they do not seem to be available in the Windows 64 Bit client (same for Mac/64 which had already been fixed in earlier releases).
    • In addition we fixed feature deletion. You can now delete disabled features, not just enabled features like in 1.1.
    • "IBM" has been replaced with "HCL" in several areas to match the current R11 standard Update Site template of Domino.
    • broken links in the about/using this database pages have been fixed

      Java class line numbers for plugin developers

      Karsten Lehmann  25 October 2022 10:32:42
      This is a shameless copy of Mikkel Heisterberg's blog article (https://lekkimworld.com/2010/04/14/java-class-line-numbers-for-plugin-developers/) to have this helpful information twice in the Google index and save me time when I need it in the future. :-)

      Hiding line numbers in stacktraces for performance reasons is pretty high on my top list of the most stupid ideas in Notes/Domino history.



      If you’ve been tasked with developing and/or debugging Java extensions for the Notes 8 client you know that line numbers has been missing from the stacktraces produced by Notes. This can be a real problem when trying to debug stuff in a production client. There has been some discussion among the ones of us developing these extensions on how to enable these line numbers. The other day this information was provided by Srinivas Rao of IBM and I wanted to publish it here for all to read.


      Line numbers are removed from the classes added to the shared class cache to reduce the memory needed for the memory mapped classes. To re-enable the line numbers, one needs to edit the frameworkrcpdeployjvm.properties file and add comment out the ignorelinenumbers vm argument. However, if the classes have already been added to the JVM shared class cache, then they will have been added without line numbers. Either comment out the shared class cache (which will dramatically affect performance at startup) for temporary work, or shutdown notes and remove the shared class cache so that it can be repopulated with classes with line numbers. Of course, this will also affect the startup performance, but not so much as not having a cache


      These are two of the key lines … to comment them out, add a # to the front of the line


      vmarg.Xnolinenumbers=-Xnolinenumbers

      vmarg.Dshare=-Xshareclasses:name=xpdplat_.jvm, **line cont**
        controlDir=${prop.jvm.shareclasses.loc}, **line cont**
        groupAccess,keep,singleJVM,nonfatal

      jvm.shareclasses.loc=${rcp.data}/.config/org.eclipse.osgi


      The shared class cache is typically located in the data/workspace/.config/org.eclipse.osgi

      Domino crashes with "CheckTheProcesses" message - explanation and workaround for dev environments

      Karsten Lehmann  13 July 2022 10:10:42
      Since Domino 8.5.3, there is a built-in facility that checks if all server processes are running. When the server detects that one has disappeared/crashed, the server triggers an NSD to document this event and crashes/restarts.

      In general, this is a clean approach to keep the server in a consistent state.

      But when you develop Java applications against the Domino Server's Java API, those Java processes are treated as child processes and monitored as well
      It's important to develop the applications in a clean way, so every Thread accessing Domino objects should be initialized with NotesThread.sinitThread() and properly uninitialized with NotesThread.stermThread().

      To ensure that this is also the case when errors occur in code, use try/finally:

      NotesThread.sinitThread();
      Session session = null;
      try {
         session = NotesFactory.createSession();
         // do something with the session object
      }
      catch (NotesException e) {
         e.printStackTrace();
      }
      finally {
         if (session!=null) {
            try { session.recycle(); } catch (NotesException e) {e.printStackTrace();}
         }
         NotesThread.stermThread();
      }

      This works in general, but often during development, you step through the code in debugger and just stop its execution, which produces this annoying result:

      Image:Domino crashes with "CheckTheProcesses" message - explanation and workaround for dev environments

      Fortunately it is possible disable the automatic Domino crash/restart with this Notes.ini variable:

      NO_TERM_EXIT_NO_PANIC=1

      The server still complains about the missing child process, but keeps on running.

      Of course, it would be even better if there was a way to exclude specific processes/process names from the monitoring.

      Domino JNA version 0.9.48: API to read/write Notes workspace, some formula magic and QueryResultsProcessor API

      Karsten Lehmann  1 June 2022 00:43:40
      Today we released version 0.9.48 of Domino JNA as OSGi plugin for XPages developers and on Maven Central.

      Here is a list of new features:
      • API to read and write the Notes workspace (read, create and modify pages and icons, change page order, move replicas on top etc.)
      • Formula execution now supports more than 64k of return data
      • API to apply security to formula execution (e.g. prevent Notes.ini changes)
      • API for QueryResultsProcessor (produces JSON and views)
      • Java 8 date/time support for NotesNote.replaceItemValue(...)
      • New utility class to format view data as markdown table
      • Added method to get agent design doc UNID
      • Added hierarchical recycling (parent/child auto recycle)

      Notes Workspace API

      The new Notes workspace API is pretty feature complete. It let's you read and modify the workspace content and contains functionality that I have long been waiting for in the Notes Client user interface like a method to change the order of tabs.

      Actually I built this for my own purpose because I needed a tool to remove icons that no longer exist.


      To play with the API and as a sample for a standalone application using Domino JNA, I created a small Maven project that you can find here:

      https://github.com/klehmann/domino-jna/tree/develop/workspace-demo

      The application creates a new workspace page:


      Image:Domino JNA version 0.9.48: API to read/write Notes workspace, some formula magic and QueryResultsProcessor API

      and a database icon with a red rectangle:


      Image:Domino JNA version 0.9.48: API to read/write Notes workspace, some formula magic and QueryResultsProcessor API

      We support both the classic 32x32 pixel icons and the new 64x64 true color ones. By passing a NotesDatabase object to NotesWorkspace.addIcon(...), the current DB icon gets extracted from the NSF design and copied into the workspace.


      Please make sure that the Notes Client is not running or in its early startup phase when you modify the workspace. Otherwise the changes will not be visible in the Notes UI and might later be overwritten.


      Formula magic


      Classic formula execution is limited to 64K of return data. We found a way to remove this limit (e.g. to use @DbColumn to read the whole column of a view) and you can even run formulas in a restricted environment, e.g. to prevent them from changing the Notes.ini or setting document items.

      Methods have been added to analyze a formula in order to see if it's time variant, just contains a field name or uses special functions like @DocSiblings or @DocDescendants.


      QueryResultsProcessor API


      The
      QueryResultsProcessor lets you build views across NSF boundaries, have them displayed in the Notes Client or exported in JSON format as string.

      In contrast to the implementation in the legacy Notes.jar and LotusScript API, we provide methods to get the JSON result back in a streaming way so that it can be parsed without storing the whole content in the Java heap.


        Chad Schelfhout’s toolbar code to change document properties

        Karsten Lehmann  26 April 2022 11:10:22
        I just noticed that Chad Schelfhout's website is no longer available, that contained formula code for a toolbar icon to read/edit document properties.
        So here it is to not get lost - the latest version I have.

        Please note that the Notes Client 12 contains Panagenda's Advanced Properties Box plugin to read and compare document properties.

        And Daniel Nashed describes how to get an extended version of the plugin that supports editing as well:
        https://blog.nashcom.de/nashcomblog.nsf/dx/advanced-document-properties-including-edit.htm

        So the code below is probably only relevant anymore for older Notes Client installs or the Basic Client.

        REM {Edit Document Fields 6.0.0 by Chad Schelfhout.};
        REM {Visit http://www.chadsmiley.com/EditDocumentFields for the latest updates};

        REM {Changable constants};
        cEnableConfirmation := @True;
        cFieldHistoryValues := 10;
        cStandardSeparators := ":" : ";" : " ";
        cPromptTitle :@DbTitle + " - " + @ViewTitle;

        REM {Unchangable constants};
        cProfileName :"ChadSmiley Tools";
        cEditLastField := "edfLastField";
        cEditLastFieldDataType := "edfLastFieldDataType";
        cEditLastFieldDataTypeValue := "edfLastFieldDataTypeValue";
        cEditLastSeparator := "%~%";
        cValueListSeparator := "^";
        cFromRawValueSeparator := "%@%";
        cSemicolonReplace := "#SC#";
        cMaxSearchForSelectedDocs := 5520;
        cMaxUpdatedDocuments := 1000;
        cArraySeparator := ";";
        cNoteEntryLength := 11;
        cPromptNewLineOne := @Char(13);
        cPromptNewLineTwo := cPromptNewLineOne + cPromptNewLineOne;
        cPromptTab :@Char(9);
        cCategoryNoteID := "NT00000000";
        cTextExtractList := "Text Left":"Text Left Back":"Text Right":"Text Right Back";
        cNoPromptList := cTextExtractList:"Remove Field":"Unique":"Sort Ascending":"Sort Descending":"Implode":"Explode":"Proper Case Text":"Proper Case Text Multi Value":"Lower Case Text":"Lower Case Text Multi Value":"Upper Case Text":"Upper Case Text Multi Value":"Password Convert":"Trim":"Trim then Unique";
        cErrorCheckCode := "@Implode( @Unique( @Explode( NoteIDList : ErrorNoteID ; cArraySeparator ; @False ) ) )";
        cErrorInformation := "\"Error documents: \" + @Implode( @Unique( @Explode( ErrorNoteIDList ; cArraySeparator ; @False ) ) ; \", \" ) + cPromptNewLineOne + \"Not updated documents: \" + @Implode( @Unique( @Explode( ErrorNoteIDList ; cArraySeparator ; @False ) ) ; \", \" )";


        REM {Data types|@Function execution};
        DataTypesCombo := @Explode(
        "Integer|@TextToNumber( RawValue )$"+
        "Integer Multi Value|@TextToNumber( @Explode( RawValue ; Separator; @True) )$"+
        "Date|@ToTime( RawValue )$"+
        "Date Multi Value|@ToTime( @Explode( RawValue ; Separator; @True) )$"+
        "Text|@Text( RawValue )$"+
        "Text Multi Value|@Text( @Explode( RawValue ; Separator; @True) )$"+
        "Text Left|@Left( @Text( @GetField( EditField[ef] ) ) ; ExtractValue )$"+
        "Text Left Back|@LeftBack( @Text( @GetField( EditField[ef] ) ) ; ExtractValue )$"+
        "Text Right|@Right( @Text( @GetField( EditField[ef] ) ) ; ExtractValue )$"+
        "Text Right Back|@RightBack( @Text( @GetField( EditField[ef] ) ) ; ExtractValue )$"+
        "Trim|@Trim( @Text( @GetField( EditField[ef] ) ) )$"+
        "Trim then Unique|@Unique(@Trim( @Text( @GetField( EditField[ef] ) ) ) )$"+
        "Name|RawValue$"+
        "Name Multi Value|RawValue$"+
        "Common Name|@Name( [CN]; RawValue )$"+
        "Common Name Multi Value|@Name( [CN]; @Explode( RawValue ; \":\"; @True ) )$"+
        "Upper Case Text|@UpperCase( @Implode( @Text( @GetField( EditField[ef] ) ) ) )$"+
        "Lower Case Text|@LowerCase( @Implode( @Text( @GetField( EditField[ef] ) ) ) )$"+
        "Proper Case Text|@ProperCase( @Implode( @Text( @GetField( EditField[ef] ) ) ) )$"+
        "Upper Case Text Multi Value|@UpperCase( @Explode( @Text( @GetField( EditField[ef] ) ) ; Separator; @True ))$"+
        "Lower Case Text Multi Value|@LowerCase( @Explode( @Text( @GetField( EditField[ef] ) ) ; Separator; @True) )$"+
        "Proper Case Text Multi Value|@ProperCase( @Explode( @Text( @GetField( EditField[ef] ) ) ; Separator; @True) )$"+
        "Replace Substring|@ReplaceSubstring( @GetField( EditField[ef] ); FromRawValue ; RawValue )$"+
        "Replace|@Explode( @Replace( @GetField( EditField[ef] ) ; FromRawValue ; RawValue ) ; Separator ; @True )$"+
        "Implode|@Implode( @Text( @GetField( EditField[ef] ) ) ; Separator )$"+
        "Explode|@Explode( @Text( @GetField( EditField[ef] ) ) ; Separator; @True )$"+
        "Formula|@Eval( RawValue )$"+
        "Abbreviate Name|@Name([Abbreviate]; RawValue )$"+
        "Abbreviate Name Multi Value|@Name( [Abbreviate]; @Explode( RawValue ; Separator; @True ) )$"+
        "Password Set|@Password( RawValue )$"+
        "Password Convert|@Password( @GetField( EditField[ef] ) )$"+
        "Remove Field|@DeleteField$"+
        "Unique|@Unique(@GetField( EditField[ef]))$"+
        "+ Append Values|@If(" +
        "     @GetField(EditField[ef]) = \"\"; RawValue;" +
        "     @Contains(DefaultDataType; \"Date\");" +
        "      @If( @IsError( @ToTime( RawValue ) ) ;" +
        "       \"\" ;" +
        "       @SetField( EditField[ef] ; @GetField(EditField[ef]) : @TextToTime( @Explode( RawValue ; Separator ) ) ) ) ;" +
        "     @Contains(DefaultDataType; \"Integer\" );" +
        "      @If( @IsError( @TextToNumber( @Explode( RawValue ; Separator ) ) ) ;" +
        "       \"\" ;" +
        "       @SetField( EditField[ef] ; @GetField(EditField[ef]) : @TextToNumber( @Explode( RawValue ; Separator ) ) ) ) ;" +
        "     @SetField( EditField[ef] ; @GetField(EditField[ef]) : @Explode( RawValue ; Separator ) ) )$"+
        "Sort Ascending|@Sort(@GetField(EditField[ef]) ; [Ascending] )$"+
        "Sort Descending|@Sort(@GetField(EditField[ef]); [Descending])" ; "$" );

        DataTypes := @Word( DataTypesCombo ; "|" ; 1 );
        DataTypesAction := @Word( DataTypesCombo ; "|" ; 2 );

        REM {Get a listing of all the fields on the current document};
        List := @Sort( @DocFields );

        REM {Look for last field modified in Profile Doc};
        FieldList := @Explode( @GetProfileField( cProfileName ; cEditLastField ; @UserName ) ; cArraySeparator ; @True ) ;

        REM {Get the list of forms and field that was updated using Edit Document Fields};
        FieldListForms := @Word( FieldList ; cEditLastSeparator ; 1 );
        FieldListField := @Word( FieldList ; cEditLastSeparator ; 2 );
        FieldListLastIndex := @Member( Form; FieldListForms );
        REM {If the FieldListLastIndex is greater than zero then set the last field to the what was in the profile document};
        @If( FieldListLastIndex > 0;
         @Do( LastField := FieldListField[ FieldListLastIndex ];
          FieldList := @ReplaceSubstring( FieldList ; FieldList[ FieldListLastIndex ] ; "" ) );
         LastField :="" );

        REM {Prompt for which field needs to be updated. Loop until a field is selected or 'Cancel' is selected};
        @DoWhile(
         EditField := @Prompt( [OkCancelEditCombo] ; cPromptTitle ; "Select the field you wish to alter or enter a new field to add:" ; LastField ; @Trim( @Unique( List : LastField ) ) );
         EditField = "" );
        EditFieldPromptTitle := "Change '" + EditField + "' in " + cPromptTitle;

        REM {This will allow the retrieval of the data type of the field that was last selected. Data is stored like Form+Field%~%DataType.};
        FormFieldList := @Explode( @GetProfileField( cProfileName ; cEditLastFieldDataType ; @UserName ) ; cArraySeparator ; @True ) ;
        FormFieldListFormField := @Word( FormFieldList ; cEditLastSeparator ; 1 );
        FormFieldListDataType := @Word( FormFieldList ; cEditLastSeparator ; 2 );
        FormFieldListFormulaCode := @ReplaceSubstring( @Word( FormFieldList ; cEditLastSeparator ; 3 ) ; cSemicolonReplace ; ";" );
        FormFieldListIndex := @Member( Form + EditField; FormFieldListFormField );
        @If( FormFieldListIndex > 0;
         @Do( DefaultDataType := FormFieldListDataType[ FormFieldListIndex ];
          FormFieldListFormulaCode := FormFieldListFormulaCode[ FormFieldListIndex ];
          FormFieldList := @ReplaceSubstring( FormFieldList ; FormFieldList[ FormFieldListIndex ] ; "" ) );
         DefaultDataType :="" );

        REM {If there was no data type used for the field on the form the try to determine the data type};
        DefaultDataType :=
         @If( DefaultDataType != "" ;
          DefaultDataType ;
          @If(
           @IsNumber( @GetField( EditField ) ) ;
            @If( @Count( @GetField( EditField ) ) > 1 ;
             "Integer Multi Value" ;
             "Integer" ) ;
           @IsTime( @GetField( EditField ) ) ;
            @If( @Count( @GetField( EditField ) ) > 1 ;
             "Date Mult iValue" ;
             "Date" ) ;
           @If( @Count( @GetField( EditField ) ) > 1 ;
             "Text Multi Value" ;
             "Text" )
           )
         );

        REM {If the data type is a type of error then select the data type of text};
        DefaultDataType := @IfError( DefaultDataType ; "Text" );

        REM {Prompt for which data type you would like the data to be. This needs to be done before value prompt to determine if the Picklist or any prompting needs to be used.};
        DataType := @Prompt( [OkCancelList] ; EditFieldPromptTitle; "Please select the correct data type or action for field: " + EditField + "."; DefaultDataType ; DataTypes );

        REM {The DataTypeAction will contain the formula that will be executed to retrieve the new value};
        DataTypeAction := DataTypesAction[ @Member( DataType ; DataTypes ) ];

        REM {If formula was used on this field then use that instead of the fields value. Format the original value as text because the @Prompt command requires text.};
        OriginalValue := @If( DataType = "Formula" & DefaultDataType = "Formula" & FormFieldListFormulaCode != "" ;
         FormFieldListFormulaCode ;
         @If( @Contains( DefaultDataType ; MultiValue ) ;
          @Implode( @Text( @GetField( EditField ) ) ; cArraySeparator );
          @Text( @GetField( EditField ) ) )
         );

        REM {This will allow the retrieval of history of values of the field. Data is stored like Form+Field+DataType%~%ValueList.};
        FormFieldListDataTypeValues := @Explode( @GetProfileField( cProfileName ; cEditLastFieldDataTypeValue ; @UserName ) ; cArraySeparator ; @True ) ;
        FormFieldListFormFieldDataType := @Word( FormFieldListDataTypeValues ; cEditLastSeparator ; 1 ) ;
        FormFieldListValuesLists := @Word( FormFieldListDataTypeValues ; cEditLastSeparator ; 2 ) ;
        FormFieldListDTIndex := @Member( Form + EditField + DataType; FormFieldListFormFieldDataType );
        @If( FormFieldListDTIndex > 0;
         @Do( FormFieldListValuesList := FormFieldListDataTypeValues[ FormFieldListDTIndex ];
          FormFieldListValuesList := @ReplaceSubstring( @Trim( @Explode( FormFieldListValuesLists[ FormFieldListDTIndex ] ; cValueListSeparator ; @False ) ) ; cSemicolonReplace; ";" );
          FormFieldListDataTypeValues := @ReplaceSubstring( FormFieldListDataTypeValues ; FormFieldListDataTypeValues[ FormFieldListDTIndex ] ; "" ) );
         FormFieldListValuesList :="" );

        REM {Prompt for additional fields and determine the string that they are searching for.};
        @If( DataType = ("Replace Substring":"Replace" ) ;
         @Do( EditField := @Unique( EditField : @Prompt( [OkCancelListMult] ; cPromptTitle ; "Select any addtional fields you wish to alter:" ; EditField ; List ) );
          FromRawValue := @Prompt( [OkCancelEditCombo] ; EditFieldPromptTitle ; "Enter or select the text (case sensitive) to search for in: " + @Implode( EditField ; ", " ) + "." ; "" ; @Unique( @Word( FormFieldListValuesList ; cFromRawValueSeparator ; 2 ) ) ) );
         @Do( EditField := EditField;
          FromRawValue := "" )
         );

        REM { With the Edit combo there will be a list of standard seperators to choose from.};
        Separator := @If( DataType = ("Implode":"Explode" ) ;
         @Prompt( [OkCancelEditCombo] ; cPromptTitle ; "Enter or select the " + @If( DataType = "Implode" ; "separator" ; "separators" ) + ":"  ; "" ; @Unique( @If( FormFieldListValuesList = "" ; cStandardSeparators  ;  FormFieldListValuesList : cStandardSeparators ) ) );
         cArraySeparator );

        REM {Determine the string to search for};
        ExtractValue :@If( DataType = cTextExtractList ;
         @Prompt( [OkCancelEditCombo] ; cPromptTitle ; "Enter or select the search string or string length:" ; @Subset( FormFieldListValuesList ; 1 ) ; @Unique( FormFieldListValuesList ) );
         "" );

        REM {Based on what type of data is being entered different prompts will happen if any at all.};
        RawValue := @If(
         @Contains( DataType ; "Name Multi Value" ) ; @PickList( [Name] );
         @Contains( DataType ; "Name" ) ; @PickList( [Name] : [Single] );
         DataType = ( cNoPromptList ) ; "" ;
         @Contains( DataType ; "Multi Value" ) ; @Prompt( [OkCancelEditCombo] ; EditFieldPromptTitle; "Enter or select the new desired value for: " + @Implode( EditField ; ", " ) + "." + cPromptNewLineTwo + "Seperated with ; for each value." ; OriginalValue ; @Unique( OriginalValue : FormFieldListValuesList ) ) ;
         @Contains( DataType ; "+ Append Values" ) ; @Prompt( [OkCancelEditCombo] ; EditFieldPromptTitle; "Enter or select values to append: " + @Implode( EditField ; ", " ) + "." + cPromptNewLineTwo + "Seperated with ; for each value." ; "" ; @Unique( FormFieldListValuesList ) ) ;
         DataType = ("Replace Substring":"Replace" ) ; @Prompt( [OkCancelEditCombo] ; EditFieldPromptTitle ; "Enter or select the text to repalce with in: " + EditField + "." ; "" ; @Unique( @Word( FormFieldListValuesList ; cFromRawValueSeparator ; 1 ) ) ) ;
         DataType = "Formula" ; @Do( @DoWhile(
          OriginalValue := @Prompt( [OkCancelEditCombo] ; EditFieldPromptTitle ; "Enter or select the new desired formula for: " + EditField + "." ; OriginalValue ; @Unique( OriginalValue : FormFieldListValuesList ) );
          tempReturnCheck := @CheckFormulaSyntax( OriginalValue );
          @If( tempReturnCheck != "1"; @Prompt( [Ok] ; "Invalid Formula - " + EditFieldPromptTitle ;
           "Invalid Formula entered: " +
           cPromptNewLineTwo + cPromptTab + "Error: " + cPromptTab + cPromptTab + @Text( tempReturnCheck ) +
           cPromptNewLineOne + cPromptTab + "Formula: " + cPromptTab +  cPromptTab + OriginalValue ) ; "" );
         tempReturnCheck != "1" );
         OriginalValue );
         @Prompt( [OkCancelEditCombo] ; EditFieldPromptTitle ; "Enter or select the new desired value for: " + EditField + "." ; OriginalValue  ; @Unique( OriginalValue : FormFieldListValuesList ) )
         );

        REM {Store Field in Profile doc};
        @SetProfileField( cProfileName ; cEditLastField ; @Unique( @Trim( FieldList : ( Form + cEditLastSeparator + EditField[1] ) ) ); @UserName );

        REM {Store Data Type of Field in Profile doc};
        @SetProfileField( cProfileName ; cEditLastFieldDataType ;
         @Unique( @Trim( FormFieldList : ( Form + EditField[1] + cEditLastSeparator + DataType + cEditLastSeparator +
          @ReplaceSubstring(
           @If( DataType = "Formula" ; RawValue ; FormFieldListFormulaCode ) ;
           ";" ; cSemicolonReplace ) ) ) ) ;
         @UserName );

        REM {Store Data Value of Field in Profile doc};
        @SetProfileField( cProfileName ; cEditLastFieldDataTypeValue ;
         @Unique( @Trim( FormFieldListDataTypeValues : ( Form + EditField[1] + DataType + cEditLastSeparator +
          @Implode(
           @Subset(
            @Unique(
            @ReplaceSubstring(
             @If( DataType = ("Implode":"Explode" ) ; Separator ;
              DataType = cTextExtractList ; ExtractValue ;
              DataType = ( "Replace Substring":"Replace" ) ; RawValue + cFromRawValueSeparator + FromRawValue ;
              RawValue ) : FormFieldListValuesList  ;
             ";" ; cSemicolonReplace ) ) ;
            cFieldHistoryValues );
           cValueListSeparator ) ) ) ) ;
         @UserName );REM {If multi docs selected, only process those checked ... an unchecked doc cannot be NavNextSelected};
        @Command([NavNextSelected]);
        @UpdateFormulaContext;

        REM {Store all Note IDs before manipulation in case field modifications cause categorized views or sorted columns to reorganize};
        NoteIDList :@Text( @NoteID );
        ErrorNoteIDList := "";
        @Command([NavNextSelected]);
        @UpdateFormulaContext;

        REM {Start Looping Selected documents to gather all the documents that need to be updated.};
        @While( ( @Left( NoteIDList ; cNoteEntryLength ) != ( @Text( @NoteID + cArraySeparator ) ) ) & ( @Length( NoteIDList ) < cMaxSearchForSelectedDocs ) ;
         NoteIDList := NoteIDList + cArraySeparator + @Text( @NoteID );
         NoteIDList := @ReplaceSubstring( NoteIDList ; cCategoryNoteID + cArraySeparator ; "" );
         @Command([NavNextSelected]);
         @UpdateFormulaContext
        );
        REM {Remove all category Note IDs};
        NoteIDList :@ReplaceSubstring( NoteIDList ; cCategoryNoteID ; "" );
        REM {Remove all duplicate Note IDs};
        NoteIDList :@Unique( @Explode( NoteIDList ; cArraySeparator ; @False ) );
        @StatusBar( "Found " + @Text( @Elements( NoteIDList ) ) + " documents." );
        NotNoteIDList := "";

        REM {Determine if the document should be updated.};
        tmpPrompt := @Implode( "The following information will be used to update the " + @Text( @Elements( NoteIDList ) ) + " document" + @If( @Elements( NoteIDList ) > 1 ; "s" ; "" ) + "." + cPromptTab + cPromptTab +
         cPromptNewLineTwo + cPromptTab + "Field:" + cPromptTab + cPromptTab + EditField +
         cPromptNewLineOne + cPromptTab + "Data type/action: " + cPromptTab + DataType +
         cPromptNewLineOne + cPromptTab +
          @If(  DataType = ("Implode":"Explode" ) ; "Separator:  " + cPromptTab + Separator ;
            DataType = ("Text Left":"Text Left Back":"Text Right":"Text Right Back" ) ; "Search string:  " + cPromptTab + ExtractValue ;
            DataType = ("Replace Substring":"Replace" ) ; "Search string:  " + cPromptTab + FromRawValue + cPromptNewLineOne + cPromptTab + "Replace string:  " + cPromptTab + RawValue ;
            DataType = cNoPromptList;
            "" ;
            "Value:  " + cPromptTab + cPromptTab + @Text( RawValue ) ) +
         cPromptNewLineTwo + "Would you like to continue?" );
        @If( cEnableConfirmation ;
         @Do(
           @StatusBar( @Explode( tmpPrompt ; cPromptNewLineOne ; @True ) );
          @If( @Prompt( [YesNo]; EditFieldPromptTitle ;
           tmpPrompt );
           "" ; @Return ( "" ) ) );
         "" );

        REM {Loop through selected docs taking each NoteIDList out of the list as it is processed};
        DocUpdateCount := 0;
        DocNavigationCount := 0;
        @While( DocUpdateCount < @Elements( NoteIDList ) ;

         @If( @TextToNumber( @Text( @DocumentUniqueID ) ) != 0 ;
          @Do(
           NoteIDList := @Replace( NoteIDList ; @NoteID ; "" ) ;
           NotNoteIDList := NotNoteIDList : @NoteID;
           @For( ef := 1; ef <= @Elements( EditField ); ef := ef + 1;
            formulaResult := @Eval( DataTypeAction );
        REMark := " **REM** The values entered above will be applied to all selected doc. If data conversion doesn't work then don't set field.";
            @If( @IsError( formulaResult );
             @Do(
              tmpPrompt := "Error with NoteID of " + @NoteID + ". Continue?" + cPromptTab +
              cPromptNewLineOne + cPromptTab + "Error: " + cPromptTab + cPromptTab + @Text( FormulaResult ) +
              cPromptNewLineOne + cPromptTab + "Formula: " + cPromptTab +  cPromptTab + DataTypeAction +
              cPromptNewLineOne + cPromptTab + @If( @Contains( DataTypeAction ; "EditField[ef]" ) ; "EditField[ef]:" ; "Field:" + cPromptTab ) + cPromptTab + EditField[ef] +
              cPromptNewLineOne + cPromptTab + "Data type/action: " + cPromptTab + DataType +
              cPromptNewLineOne + cPromptTab +
              @If(  DataType = ("Replace Substring":"Replace" ) ; "FromRawValue:  " + cPromptTab + @Text( FromRawValue )+ cPromptNewLineOne + cPromptTab + "RawValue:  " + cPromptTab + @Text( RawValue ) ;
                DataType = cNoPromptList;
                "" ;
                "RawValue:  " + cPromptTab + @Text( RawValue ) );
             @StatusBar( @Explode( tmpPrompt ; cPromptNewLineOne ; @True ) );
             @If( @Prompt( [YesNo] ;"Error - " + EditFieldPromptTitle ; tmpPrompt ) ;
               ErrorNoteIDList := ErrorNoteIDList+ cArraySeparator + @Text( @NoteID );
              @Return( @If( @Eval( cErrorCheckCode ) != "" ;
               @StatusBar( @Explode( @Eval( cErrorInformation ) ; cPromptNewLineOne ; @True ) ):
               @Prompt( [Ok] ; "Unable to Update - " + EditFieldPromptTitle ; @Eval( cErrorInformation ) );
              "" ) ) ) );
            @SetField( EditField[ef] ; formulaResult )
            )
           );
           @If( DocNavigationCount > cMaxUpdatedDocuments ;
             NoteIDList := "";
            @Do(
             DocUpdateCount := DocUpdateCount + 1;
             @Command([NavNextSelected]);
             @UpdateFormulaContext;
        REMark := " **REM** If we haven't processed all docs yet but the current doc is not in the NoteIDList list, keep looping ... if cnt exceeds MaxUpdatedDocuments assume infinite loop and stop ";
             @If( DocUpdateCount < @Elements( NoteIDList ) & ( !@Member( @NoteID ; NoteIDList ) ) & ( !@Member( @NoteID ; NotNoteIDList) );
              @While( (! @Member( @NoteID ; NoteIDList ) & DocNavigationCount < cMaxUpdatedDocuments );
               @Command([NavNextSelected]);
               @UpdateFormulaContext;
               DocNavigationCount := DocNavigationCount + 1);
             "")
            )
           )
          );
         @Do( @Command([NavNextSelected]);
          @UpdateFormulaContext )
         )
        );

        @If( @Implode( @Unique( @Explode( NoteIDList : ErrorNoteIDList ; cArraySeparator ; @False ) ) ) != "" ;
         @StatusBar( @Explode( @Eval( cErrorInformation ) ; cPromptNewLineOne ; @True ) ):
         @Prompt( [Ok] ; "Unable to Update - " + EditFieldPromptTitle ; @Eval( cErrorInformation ) );
         "" );

        @StatusBar( "Navigated through " + @Text( DocUpdateCount + DocNavigationCount ) + " documents." );
        @StatusBar( "Performed '" + DataType + "' for '" + @Implode( EditField ; ", " ) + "' field" + @If( @Elements( EditField ) > 1; "s " ; " " ) + "on " + @Text( DocUpdateCount ) + " document" + @If( DocUpdateCount > 1 ; "s" ; "" ) + "." )


          Ralf Petter’s blog about Notes, Eclipse and Expeditor back online

          Karsten Lehmann  20 December 2021 14:06:16
          Yesterday I noticed that Ralf Petter's blog "Everything about IT" is not online anymore. Ralf died years ago from cancer and his blog contains invaluable tips around Notes, Eclipse and Expeditor development.

          In June 2017 when I heard about his death, I downloaded all blog articles and kept them on my local disk.

          Now that the official blog is gone, I uploaded them to our server, added them to the Google index and made them available here:

          https://ralfpetter-blog-mirror.mindoo.de

          Unfortunately I did not create copies of his free tools and they are not on OpenNTF or Github.

          I could find the ZIP archive with the latest DocumentSpy plugin in the Wayback machine and fixed the download link in this article:

          https://ralfpetter-blog-mirror.mindoo.de/www.everythingaboutit.eu/p/install-document-spy.html

          If anybody has a copy of these three tools, please send them to me:

          https://ralfpetter-blog-mirror.mindoo.de/www.everythingaboutit.eu/p/install-extension-spy.html

          https://ralfpetter-blog-mirror.mindoo.de/www.everythingaboutit.eu/p/install-todoactivator.html

          https://ralfpetter-blog-mirror.mindoo.de/www.everythingaboutit.eu/p/install-shortcutbuttonbar-enabler.html

            Mindoo CMS on Domino: Some examples

            Karsten Lehmann  27 July 2021 07:42:43
            Mindoo CMS is a Domino powered web content management system that we developed as part of a large project for a local print shop to build a web authoring system for the articles of their local newspapers with approval workflow, custom spellchecker with dictionary stored in Domino (based on http://hunspell.github.io/) and various data exports (e.g. responsive UI and EPUB for subscribers, QuarkXPress and Adobe InDesign for printing machines).

            The CMS does not just display the final articles, it's driving the whole web application to compose and approve articles as well.

            Here is a list of the main features:
            • responsive user interface for desktop and mobile browsers
            • live editing of websites: as logged in user, just shift-click on any test in the live website and edit it or add content
            • full support of the Domino access model, e.g. to built corporate intranets with different layout/content for target groups
            • very flexible templating system based on custom Handlebars tags
            • no developer skills needed for authors
            • compose web site pages out of building blocks predefined by the template editor with unrestricted complexity
              (e.g. add base page layout, sections for text/images, gallery block and gallery images with meta data; data model is 100% defined within the CMS)
            • automatic history of all CMS pages, files and templates: compare current state with any previous versions and revert back changed as needed
            • implemented/deployed as OSGi plugins on Domino
            • extensible to add custom CMS tags via OSGi extension points

            Here are some references of web sites that we developed and that are powered by Mindoo CMS:


              News Update from Mindoo Land: Domino JNA / JNX

              Karsten Lehmann  26 July 2021 13:15:43
              In a pandemic (in particular with two young kids of 5 and 7 years), times flies by even faster than in regular times, so it's no surprise that this blog has not seen many updates for a very long time.

              Just wanted to let you know that we are still very busy, leveraging and improving the Domino AppDev platform.

              The biggest project I have been working on in 2020 and so far in 2021 has to do with our Domino JNA codebase. For the project itself, we released 11 new versions since the beginning of 2020.

              We added functionality that we needed for our own development projects and the project got additional traction because it was used by the HCL Innovation Labs initially for Project Keep, now called HCL Domino REST API and in public beta for a few week.

              After the first few internal Keep versions, the HCL Innovation Labs team decided that having a modern Java API for Domino would not just be relevant for their own projects but for every developer out there and that this should (hopefully) become the new standard for Java development on Domino.

              That's why we teamed up with Jesse Gallagher to build something even better than Domino JNA - called Domino JNX.

              Let's call it Domino JNA on steroids. We took the codebase of Domino JNA and built a powerful API that is both easy to use and has even more features than Domino JNA.

              Some examples:
              • automatic garbage collection for Domino C handles (based on Java's PhantomReference)
              • leverages modern Java language features like Generics, lambda expressions, the Stream API and Optional's
              • more APIs, e.g. to read the database design and current efforts to provide write access as well
              • JSON serialization support
              • extensible, e.g. to store your own objects in Domino documents

              JNX is the foundation for the HCL Domino REST API which itself is the foundation for other cool projects like the OpenClient initiative that will provide a native EWS/JMAP API for Domino and therefore native access for Microsoft Outlook, the Apple Mail client and others.

              There are ongoing efforts to give you, the Domino developer community, access to the JNX API (not just as part of the existing public beta release of the Domino REST API).

              So stay stuned for upcoming announcements! :-)

                OpenNTF May 2021 Webinar: recent Mindoo ToDoManager changes for Notes 12

                Karsten Lehmann  20 May 2021 21:10:19
                Today I took part in OpenNTF's May webinar on recent project updates. I presented the results of my efforts to leverage new functionality of the upcoming Notes 12 client to make our Mindoo ToDoManager application more responsive and adaptable to the available screen real estate.

                The application now autodetects the size of the Notes/Nomad client window on startup and picks the best application layout: you either see all four quadrants

                Image:OpenNTF May 2021 Webinar: recent Mindoo ToDoManager changes for Notes 12

                or the mobile optimized narrow layout with a single quadrant at a time:

                Image:OpenNTF May 2021 Webinar: recent Mindoo ToDoManager changes for Notes 12


                Notes 12 provides two new @formulas @ResolutionWidth / @ResolutionHeight to read the dimension of the current UI form or UI view.
                And you get a new form/view event "onsize" that gets triggered on window resize. Enter the formula @Command([RelayoutWindow]) here and your layout gets recomputed when the window is resized by the user, e.g. hide/when or subform formulas that depend on the form dimension might produce a different output.

                To be honest I was fighting a bit to get this new functionality to work reliable in the latest R12 beta 3 client. The TodoManager proves that responsive layouts for Notes applications are indeed possible.

                But I reported a bunch of issues in the beta program that hopefully get fixed until the final R12 release to make this easier to use.
                E.g. my Designer installation does not save the code that I enter in the "onsize" event. And the resolution formulas often seem to be computed too early, e.g. you still get UI view dimensions back after you have opened a doc in a view.

                While I was updating the OpenNTF project download with the latest version, I also added some documentation and references on the project summary page. I highly recommend watching the two videos that are linked in the text if you haven't done already. They were recorded in 2007, but are still very inspiring.

                Anyway, I hope you like the new version. Here is the updated project description:


                Four Quadrant Theory

                The TodoManager is based on Covey's Four Quadrant theory that is described in this article:
                Time management strategies for busy people using the 4-quadrant method
                It provides a graphical concept of todo management where you place your todos in a 2x2matrix of four quadrants. By doing that you decide whether your topic is important/ not important and due soon/ not due soon.
                Following the order of the quadrants is key to organizing your life:
                Start with quadrant 1 (important/ due soon), continue with 2 (important/ not due soon), 3 (not important/ due soon) and end with todos in quadrant 4 (not important/ not due soon). In each quadrant, pick the ugliest task first.
                I got to know Covey's theory when I was watching Randy Pausch's Last Lecture and his Lecture about Time Management. Both recordings are highly recommended to put your daily struggles and project deadlines into perspective:
                Carnegie Mellon Professor Randy Pausch (Oct. 23, 1960 - July 25, 2008) gave his last lecture at the university Sept. 18, 2007, before a packed McConomy Auditorium, months before he died from pancreatic cancer and reflects on how much he achieved his childhood dreams.

                Usage

                Use the “+” icon to create new todos and decide in which quadrant (=Notes folder) it should be moved on first save.
                Use drag and drop or context menu actions to move your tasks between quadrants (numbers “1”, “2”, “3”, “4” are drop targets) or into the recycle bin.

                Responsive UI

                The TodoManager application has a responsive user interface that works on desktop Notes Client, HCL Nomad clients on tablets/phones and even the new Nomad web client. By providing two different application layouts it adapts to the available screen real estate: one layout that displays all 4 quadrants at once and a second one where you only see a single quadrant and have hotspots to change quadrants in the top navigation.
                On Notes 12 we leverage new @formulas to query the current window dimension while still being backward compatible with older Notes Client versions. That means that even in the desktop Notes Client you get a different user experience when the client window width is not very wide. The window size is detected on application startup.

                Installation

                Just copy the provided application template to your server, sign it and create a new database instance. Then give yourself editor access in the ACL and all others no access.
                There are currently no background agents to enable.