cardo.Button2

Das Modul cardo.Button wurde für cardo4 völlig neu geschrieben. Die bisherigen Ansätze mit XML/XSD wurden durch einen Mix aus GeoSQL/Scriban/Markdown ersetzt.

Die beiden Module koexistieren, eine automatische Übernahme der Pläne ist nicht möglich.

cardo.Button ist dabei als eigene, interaktiv zu startende, Anwendung verfügbar und auch als "Engine", die von cardo Anwendungen eingebunden werden kann.

Objekte

Plan-Definition

Der Begriff "Plan" ist Nutzern des Moduls bekannt und wird hier weiterverwendet. Ein "Plan" beschreibt die Ausführung von Datenabfragen und auch die Formatierung der Ausgabe.

cardo.Button gibt nicht direkt die Speicherorte von Plänen vor. Diese können bspw. im Dateisystem oder auch in einem anwendungsintegrierten "Wilma" abgelegt werden.

Ein Plan im neuem cardo.Button ist dabei ein einfaches Textdokument, welches mit der cardo Template-Engine, die seinerseits auf Scriban basiert, gelesen wird.

Als Besonderheit gilt die Konvention, dass am Anfang der Datei die Metadaten zu dem Plan hinterlegt werden müssen. Dabei handelt es sich um ein JSON-Objekt vom Typ IButtonPlanDefinition, welches in einem Kommentar-Block am Anfang stehen muss. Hierzu ein einfaches Beispiel.:

{{##~~
{
  title:"Mein erster Plan",
  args:{
    "tableName":{
       title:"Name der Tabelle",
       type:"Text",
       isRequired:true
    }
  }
}	
~~##}}

Ergebnisse der Plan-Ausführung

Ein Plan kann eine Liste von Ergebnissen liefern. Grundlegend sind folgende Typen vorgesehen:

  • Text (HTML, oder PlainText)
  • Instanz eines cardo Wellknowntype (interaktive Datentabelle, nur in der cardo Umgebung nutzbar)
  • PDF Dokument
  • Daten (JSON)
  • Daten (XML)

In cardo gibt es eine Ausgabekomponente, die den Antworttyp auswertet und eine entsprechende Oberfläche dafür generiert.

Durchgestrichen dargestellte Formate sind geplant, aber noch nicht implementiert.

"BO"

Wie bei allen über die cardo Template-Engine verwendeten Techniken gibt es diverse Erweiterungen die bereitgestellt werden (z.B. uxGeo ...) und das Business-Objekt, kurz "BO", welches immer zur Verfügung steht.

Das "BO" in Button enthält selber keine Daten, stellt aber Eigenschaften und Methoden zur Interaktion mit der Plan-Ausführung zur Verfügung.

Eigenschaften

"TableRowLimit" (10.000)
BO.TableRowLimit = number | null

Das generelle Zeilenlimit für Datenabfragen. Der Standardwert ist 10.000. Wird das Limit erreicht, wird der Plan mit einem Fehler abgebrochen.

"Args"
BO.Args = readonly IDictionary<object>

Hier sind alle im Plan definierten "args" streng typisiert hinterlegt. Die Werte werden vor der Ausführung entsprechend der Plan-Definition validiert und ggf. konvertiert.

"Format"
BO.Format = readonly Types.OutputFormat

Wenn der Plan mehrere Ausgabeformate definiert hat, ist hier das aktuell ausgewählte Format hinterlegt.

"StringContentInterpretation" (PlainText)
BO.StringContentInterpretation = "PlainText" | "Markdown"| "Html" | "DocumentParts" |"None"

Legt fest, wie die Textausgabe des Templates interpretiert wird. Mögliche Werte:

  • PlainText: Text wird HTML-Encodeded zurückgeliefert, Umbrüche als br

  • Markdown: Text wird von einem Markdown-Converter in HTML konvertiert

  • Html: Text wird unverändert zurückgeliefert

  • DocumentParts: Das Ergebnis enthält unsere IDU spezifische Dokumentauszeichnungen und wird zu einem PDF Dokument konvertiert.

  • None: Die Textausgabe des Plans wird ignoriert.

Methoden

"DbSQL()"
BO.DbSQL(dbAlias: string) : ISQLBuilderBase

Gibt die Instanz eines Datenbank-Abfrage-Objektes zurück, initialisiert mit einer Datenbankverbindung aus den cardo Einstellungen.

Die Datenbankverbindung muss dabei mit dem Attribut "ButtonDataSource" versehen sein.

Die Datenbankverbindung wird mit dem Aufruf der Methode erstellt und beim Beenden des Plans automatisch geschlossen.

Sie sollten hier Datenbankverbindungen anlegen, mit einem Benutzer mit möglichst wenigen Berechtigungen.

Methode "GeoSQL()"
BO.GeoSQL() : ISQLBuilderBase

Gibt die Instanz eines Datenbank-Abfrage-Objektes zurück, hier immer mit Iwan7 als Datenquelle (GeoSQL Abfragen). Gegenüber der DbSQL Instanz ist hier noch zusätzlich die Methode MapLayerTagsToLayerName verfügbar.

Datenabfragen (ISQLBuilderBase)

Abfragen können aus Datenbanken oder über Iwan7 GeoSQL erfolgen.

Für das Ausführen der Abfragen wird ein Objekt bereitgestellt (BO.DbSQL und BO.GeoSQL), welches Parameter in Abfragen ermöglicht und über sichere Hilfsfunktionen zum Konstruieren von Abfragen bereitstellt.

Wichtig: Setzen Sie nie Abfragen direkt mit vom Benutzer vorgegeben Werten zusammen. Sie ermöglichen sonst Sicherheitsprobleme aus dem Bereich SQL-Injection

Die wesentlichen Methoden diese Objektes sind:

  • Generieren von Prädikaten (Vergleichen), setzten der Werte als Parameter. Die unten stehenden Methoden haben jeweils die Signatur

    op(columName:string, value:object) : string
    

    Zurückgeliefert wird dabei ein Fragment, welches in der Abfrage eingesetzt werden kann. I.d.R. wird dazu ein Datenbank-Parameter erstellt und in dem Abfrageobjekt hinterlegt.

    Der Spaltenname wird durch die Methode nicht extra behandelt. Sollte es sich beim dem Namen um einen Benutzerwert handeln, verwende unbedingt die Methode QuoteIdent().

    Folgende Prädikate gibt es derzeit:

    • EQ(colName:string, value:object) : string
    • IN(colName:string, values:object[]) : string
    • LIKE(colName:string, value:string) : string
    • ILIKE(colName:string, value:string) : string
    • INTERSECTS(colName:string, geometryValue:object) : string
  • Einfügen von Parametern, setzten eines Wertes als Parameter, zurückliefern des Platzhalters in der Abfrage

    • AddParam(value:object) : string
    • AddParamGeom(geometryValue:object) : string

    Allgemeine Hinweise zu Parametern

    • Bei Iwan7 werden keine Parameter verwendet, es wird aber garantiert, dass die Werte mit GeoSQL kompatibel sind und korrekt maskiert werden.
    • Werte vom Typ Geometrie werden verschiedenen Formaten interpretiert, bspw. EWKT, GML, oder als echtes Geometrie-Objekt
  • Maskieren von Objektnamen

    Die Methode QuoteIdent(ident: string, allowMultiPart:bool = false) : string maskiert den Identifikator (Tabellenname etc., Spaltenname) bei Bedarf entsprechend der Regeln der zugrunde liegenden Datenbank und sollte immer verwendet werden, wenn Teile der Abfrage aus Benutzereingaben stammt.

    allowMultiPart gibt dabei an, ob mehrteilige Bezeichner erwartet werden (bspw.: "Schema"."Tabelle").

  • Ausführen der Abfrage

    Die Datenabfrage wird mit einer der Execute** Methoden ausgeführt. Nach dem Ausführen werden die evtl. zuvor hinzugefügten Parameter wieder entfernt.

    Beachte auch die Eigenschaft BO.TableRowLimit

    Die Methoden unterscheiden sich im Generieren der Rückgabe.

    • ExecuteStoreResult(sql: string) : Table

      Hier wird das Ergebnis direkt als Objekt vom Typ cardo.Core.WellKnownType.TableData in dem Plan hinterlegt, ein Zugriff auf die Ergebnisse aus dem Plan heraus ist nicht vorgesehen.

      Eine bereits zuvor abgespeichertes Ergebnisobjekt wird durch dieses ersetzt.

      In cardo wird diese Tabelle dann mit der bekannten Tabellenansicht angezeigt.

    • ExecuteAsTable(sql: string) : PiB.Presentation.TemplateEngine.ux.TableSnapshotResult

      Hier wird ein Snapshot der Tabelle erstellt und an das Script des Plans zur weiteren Verarbeitung zurückgegeben.

  • Nur GeoSQL: Layernamen anhand eines Alias ermitteln

    MapLayerTagsToLayerName(src: IDictionary<string>, errorWhenNotFound:boolean = true) : IDictionary<string>

    In dieser Methode wird in src ein Objekt übergeben, der Key ist ein selber vergebener Name, der Wert des "Alias" einer Ebene im cardo Themenbaum.

    Das Ergebnis enthält dann in value zu dem gleichen Key den ermittelten Ebenennamen oder null, wenn zu dem Alias kein Name ermittelt werden konnte (wenn errorWhenNotFound mit false übergeben wurde, sonst kommt in diesem Falle eine Fehlermeldung).

Anhang

Typdefinitionen

IButtonPlanDefinition
	declare namespace IduIT.cardo.Core.CoreModules.Button.Types.Client
	{

		/**
		 */
		export interface IButtonArgumentClientMetadata extends IduIT.cardo.Core.CoreModules.Button.Types.IButtonArgumentMetadataBase
		{
			/**
			 * Das sind die Daten aus einer Lookup-Definition
			 */
			lookupRecords?: IduIT.Core.Data.Lookup.ILookupRecord[] | "lazy" | null;
		}

		/**
		 */
		export interface IButtonPlanClientDefinition extends IduIT.cardo.Core.CoreModules.Button.Types.IButtonPlanDefinitionBase
		{
			/**
			 * Argumente, die zum Starten (erforderlich) sind
			 */
			args?: IDictionary<IduIT.cardo.Core.CoreModules.Button.Types.Client.IButtonArgumentClientMetadata> | null;
		}
	}

	declare namespace IduIT.cardo.Core.CoreModules.Button.Types
	{

		/**
		 * Spezialisierungen der UI für die Typen in den Argumenten
		 */
		export enum KindSpecTypes
		{
			/**
			 */
			None = 0,
			/**
			 * Typ DateTime: nur den Date-Part anzeigen
			 */
			DateOnly = 1,
			/**
			 * Typ String: TextArea (statt TextField)
			 */
			TextArea = 2,
		}

		/**
		 * Definition eines "Reports" / ButtonPlan
		 */
		export interface IButtonPlanDefinitionBase
		{
			/**
			 * Die innerhalb der Implementierung der ButtonEngineBase eindeutige Kennung.
			 */
			reportId: string;

			/**
			 * Der Anzeigename, nicht optional (wenn leer, dann wird der Wilma-Paragraph Titel verwendet)
			 */
			title: string;

			/*
			* optionaler Name einer Gruppe, wird in der Standardansicht ausgewertet.
			* Wenn nicht vorhanden, oder null, dann wird der Node-Titel aus Wilma verwendet.
			*/
			groupName?:string|null;

			/**
			 * Mögliche Ausgabeformate
			 */
			formats?: IduIT.cardo.Core.CoreModules.Button.Types.OutputFormat[] | null;

			/**
			 * Beschreibung, optional
			 */
			htmlDescription?: string | null;

			/**
			 * Liste von Gruppen/Nutzer, für die das sichtbar ist
			 * Es gilt: 
			 * - leeres Array, dann für niemanden sichtbar
			 * - null (oder gar nicht vorhanden), für jeden sichtbar
			 * - sonst: Login-ID des Nutzers oder eine der Gruppen ist enthalten (der Vergleich erfolgt case-insensitiv)
			 * 
			 * Beachte:
			 * Wenn der Aufruf via Public-URL erfolgt, dann ist das Verhalten so, dass eine Leere Liste den Zugriff verweigert, d.h.
			 */
			visibleFor?: string[] | null;

		}

		/**
		 * Definition eines Argumentes für einen Report
		 */
		export interface IButtonArgumentMetadataBase
		{
			/**
			 * Der Anzeigename, nicht optional
			 */
			title: string;
			/**
			 * Beschreibung, optional
			 */
			htmlDescription?: string | null;
			/**
			 * Der technische Datentyp
			 */
			type: IduIT.GeoLib.Core.DataTypeType;

			/**
			* Spezielle Hinweise für die Eingabefelder, je nach Datentyp
			*/
			typeHint?: IduIT.cardo.Core.CoreModules.Button.Types.KindSpecTypes | null;

			/**
			 * Eine Liste von Werte
			 */
			isArray: boolean;
			/**
			 * Gibt an, ob dieses Argument optional ist
			 */
			isRequired: boolean;
		}
	}

	declare namespace IduIT.cardo.Core.CoreModules.Button
	{
		/**
		 */
		export interface IButtonResult
		{
			/**
			 */
			readonly values: IduIT.cardo.Core.CoreModules.Button.IButtonResult.IResult[];
		}
		/**
		 * Das ist die Basis-Klasse der Button-Engine, mit Ajax-Methoden, die von der Standard-Gui verwendet werden.
		 * Die Lizenz für das Modul ist nur an der der cardo Anwendung IduIT.cardo.Core.Applications.Button.ButtonApplication ,
		 * so dass die Engine auch ohne Lic verwendet werden kann.
		 * Bsp.: serverseitige Verwendung:
		 * 
		 * partial class FooApplication
		 * {
		 * [IduIT.Core.Web.Ajax.AjaxProxyObject("bisButtonRemotingProxyProperty")]
		 * public IduIT.App.BisSax.Reports.BisButtonRemoting ButtonRemoteing => new IduIT.App.BisSax.Reports.BisButtonRemoting();
		 * }
		 */
		export const ButtonEngineBase: IduIT.cardo.Core.CoreModules.Button.ButtonEngineBase.IAjax;
	}
	declare namespace IduIT.cardo.Core.CoreModules.Button.IButtonResult
	{
		/**
		 */
		export interface IResult
		{
			/**
			 */
			readonly ct: IduIT.cardo.Core.CoreModules.Button.ResultValueInterpretation;
			/**
			 */
			readonly v: any;
		}
	}

	declare namespace IduIT.cardo.Core.CoreModules.Button.Types.Server
	{
		export type TLookupSourceTaggedUnion = 
			  { query:ILookupSourceTaggedUnion.ILookupQuerySource } 
			| { records:IduIT.Core.Data.Lookup.ILookupRecord[] }
			| { enumTypeName:string };
		/**
		 */
		export interface IButtonArgumentServerMetadata extends IduIT.cardo.Core.CoreModules.Button.Types.IButtonArgumentMetadataBase
		{
			/**
			 * Das ist eine Union aus Records und Query
			 */
			lookup?: IduIT.cardo.Core.CoreModules.Button.Types.Server.TLookupSourceTaggedUnion | null;
		}
		/**
		 */
		export interface IButtonPlanServerDefinition extends IduIT.cardo.Core.CoreModules.Button.Types.IButtonPlanDefinitionBase
		{
			/**
			 * Argumente, die zum Starten (erforderlich) sind
			 */
			args?: IDictionary<IduIT.cardo.Core.CoreModules.Button.Types.Server.IButtonArgumentServerMetadata> | null;
		}
	}
	declare namespace IduIT.cardo.Core.CoreModules.Button.Types.Server.ILookupSourceTaggedUnion
	{
		/**
		 * Eine Abfrage auf eine Ebene oder Datenbank
		 */
		export interface ILookupQuerySource
		{
			/**
			* Der Name der Wertespalte
			 */
			valueColumnName: string;

			/**
			* Der Name der Anzeigespalte (muss ungleich der Werte-Spalte sein)
			 */
			labelColumnName: string;

			/**
			* Die Abfrage, die valueColumnName und labelColumnName liefern muss
			 */
			query: string;

			/**
			* Hier sind 3 Angaben möglich:
			* - der Name einer administrativen Ebene (Lxxx)
			* - Schlüsselwort "AppDb" (unter Beachtung Groß/Kleinschreibung (nicht in der Standard-Button Anwendung verfügbar)
			* - der Alias einer in cardo hinterlegten Datenbank (mit der Option Button-DataSource)
			* */
			source: string;
		}
	}

	namespace IduIT.GeoLib.Core
	{
		/**
		 */
		export enum DataTypeType
		{
			/**
			 */
			Null = 0,
			/**
			 */
			Boolean = 1,
			/**
			 */
			SingleByte = 2,
			/**
			 */
			Int16 = 3,
			/**
			 */
			UInt16 = 4,
			/**
			 */
			Int32 = 5,
			/**
			 */
			UInt32 = 6,
			/**
			 */
			Int64 = 7,
			/**
			 */
			UInt64 = 8,
			/**
			 */
			Double = 9,
			/**
			 */
			DateTime = 10,
			/**
			 */
			Text = 11,
			/**
			 */
			Binary = 12,
			/**
			 */
			Geom = 13,
			/**
			 */
			Object = 14,
			/**
			 */
			GuidType = 15,
		}
	}

Beispiel-Plan

{{##
	{
		title:"Test-Plan",
		htmlDescription:null,
		formats:["PDF","Html","NativeCardoType"],
		args:{
		 "tableName":{
		 	title:"Name der Tabelle",
			htmlDescription:"",
			type:"Text",
			isArray:false,
			isRequired:true
		 }
		}
	}	
##}}
{{
	BO.StringContentInterpretation = "Markdown"

	$sqlBld = BO.GeoSQL();

	$sql = $"
		SELECT
			*
		FROM
		 	{$sqlBld.QuoteIdent(ident:BO.Args.tableName,allowMultiPart:true)}
		WHERE 
			{$sqlBld.EQ("numcol",1)}
			OR col = {$sqlBld.AddParam(1)}
			OR {$sqlBld.EQ("str","Test")}
			OR {$sqlBld.EQ("str",null)}
			OR {$sqlBld.EQ("bool",true)}
			OR {$sqlBld.EQ("bool",false)}
			OR {$sqlBld.IN("lst",["a","b"])}
			OR {$sqlBld.LIKE("lst","%test")}
			OR {$sqlBld.LIKE("lst","t%t")}
			OR {$sqlBld.ILIKE("lst",["a","b"])}
			OR {$sqlBld.INTERSECTS("geom","srid=4326;POINT(5 5)")}
		"
	$sql
}}

Abrufen von Plänen über den Http-Handler

Für den Zugriff auf die Pläne steht ein Http-Handler unter dem Endpunkt "services/button/" zur Verfügung.

Es gibt drei Methoden:

  • list
  • describe
  • run

List - Abrufen aller verfügbaren Reports

Endpunkt: services/button/list:

Ergebnis:

{
	availablePlans: IButtonPlanDefinition[];
	warnings: string[]|null;
}

Describe - Beschreibung zu einem Report

Endpunkt: services/button/describe/{reportId}:

Ergebnis:

IButtonPlanDefinition

siehe für die Typdefinition IButtonPlanDefinition

Run - Ausführen eines Reports

Endpunkt: services/button/rum/{reportId}/{format}/

ReportId ist die ReportID aus der IButtonPlanDefinition (bei Wilma-basierten Plänen ist die reportId immer die UniqueId des Paragraphen), Format ist einer der Werte des Enums OutputFormat.

  • bei GET werden die Argumente im Query-String übergeben (?key=value&key2...).

    Sonderfall: Bei Array müssen dieses als JSON Array angegeben werden, Bsp.: flst=['144167___000500013','144167___000620003']

  • bei POST wird ein JSON Objekt erwartet.

    Body vom Typ Application/json Ein JSON Object, die Properties entsprechen den Parameternamen

      {
      	flst:['144167___000500013','144167___000620003']
      }
    

Liefert der Plan keine Daten, wird der Statuscode 204 No Content geliefert.

Wenn der Plan mehrere Antworttypen erzeugt, wird der zum angefordertem Format verwendet, kann keines ermittelt werden, wird Fehler 500 geliefert.

Beispielaufruf, ausführen des Plans mit der Report-Id xxxxxxxxxxxxx und einem Parameter "flst", als Array von Strings:

http://IhrCardoServer/net4/services/button/run/xxxxxxxxxxxxx/pdf?flst=/html?flst=['144167___000500013','144167___000620003']

Eine MultiPart-Antwort wird derzeit nicht generiert.

Beispiel

Argumente

Hier sind einige Beispiele für Argumente der Berichte.


Zuletzt geändert: 06.11.2025 09:23:58 (erstmals erstellt 06.11.2025) // Alias: "Button2"