Editor Implementation

Code Effects business rules editor is a FREE web-based component and must be implemented in a web application. All current .NET web platforms as well as client frameworks such as React, Angular, and Vue are supported.

Here are the basic steps to implement the Code Effects rule editor:

  • Reference the latest version of the CodeEffects.Rule.Editor assembly from NuGet in your .NET web project.

  • Reference the rule editor in your client-side. The Editor’s client functionality is available as npm package:

    • CodeEffects.Editor.js – the main JavaScript file that defines all client-side editor functionality.
    • CodeEffects.Common.css – the core CSS file of the editor.
    • CodeEffects.Light.css – the Light (default) theme. A new Dark theme will be introduced soon.

    The main script has been modularized in order to support all existing installations, both as a module or as a plain script, without the need to develop and maintain multiple npm packages.

    • Either import the main script as a module (React, Angular, Vue, etc):
    
      // If using the npm bundle
      import { $rule, $ce } from "codeeffects";
    
      // If referencing the main script as a file
      import { $rule, $ce } from "../YourScriptsFolder/CodeEffects.js";
    
    • ... or use the <script> tag to reference the main script in your markup (notice the use of the module value as the script's type):
      <script type="module" src="/YourScriptsFolder/CodeEffects.Editor.js?AvoidBrowserChashing"></script>
    
  • The editor requires .NET code on the server to generate settings, manage rules, and optionally evaluate them. You can use any type of server API to transfer rule data between the server and the client. The following excerpt from our ASP.NET demo project shows the server-side rule evaluation portion. Refer to the demo project for the rest of the rule-management functionality.

using CodeEffects.Rule.Common.Attributes;
using CodeEffects.Rule.Editor;
using CodeEffects.Rule.Engine;

namespace CodeEffects.Demo.Asp
{
	public static class Api
	{
		public static void MapEndpoints(WebApplication app)
		{
			// ....... most of the code of the demo class is omitted for clarity

			// Evaluates the rule against data from client
			app.MapPost("/api/evaluate", (Request req) =>
			{
				var editor = GetControl();

				editor.RuleNameIsRequired = false; // No need to validate the rule name

				// Load the rule's Json sent from the client into the editor
				editor.LoadClientData(req.Rule);

				var response = new Response();

				if(editor.IsEmpty)
				{
					response.IsRuleEmpty = true;
					response.Output = "The rule is empty";
				}
				else if(!editor.IsValid)
				{
					response.IsRuleValid = false;
					response.Output = "The rule is invalid";

					// Send invalid rule elements to the client
					response.ClientInvalidData = editor.GetClientInvalidData();
				}
				else
				{
					// Get the rule XML
					var ruleXml = editor.GetRuleXml();

					// Create an instance of the Evaluator.
					// This compiles your rule into IL
					var ev = new Evaluator<SourceObject>(ruleXml);

					// Evaluate the rule against the source instance
					// that came from the client's Test Form
					bool success = ev.Evaluate(req.Source);

					// Output the evaluation result
					response.Output = success ?
						"The rule evaluated to TRUE" :
						"The rule evaluated to FALSE";

					// Send the possibly updated source back to the client
					// So the test form could display the updated values
					response.Source = req.Source;
				}

				return Results.Ok(response);
			});

			private static Control GetControl()
			{
				// Use the ID of the div element that contains the editor
				var editor = new Control("divEditor");

				// Set the editor to use your source object
				editor.SourceType = typeof(SourceObject);

				editor.Mode = RuleType.Execution;
				editor.ShowHelpString = true;
				editor.ShowToolBar = true;

				return editor;
			}
		}
	}

	// Demo's source object
	public class SourceObject
	{
		public int Id { get; set; } = 0;
		public string? Name { get; set; }

		[Field(DisplayName = "Date of Birth", DateTimeFormat = "mm/dd/yyyy")]
		public DateTime? Dob { get; set; }
	}

	public class Request
	{
		public string? Rule {  get; set; }
		public SourceObject Source { get; set; } = new SourceObject();
	}

	public class Response
	{
		public bool IsRuleEmpty { get; set; } = false;
		public bool IsRuleValid { get; set; } = true;

		public string? Output { get; set; }
		public string? ClientInvalidData { get; set; }
		public SourceObject Source {  set; get; } = new SourceObject();
	}
}
  • To complete the editor's implementation, add a div element to your markup where you want the editor to appear. The demo project also adds the test form with 3 inputs that represent SourceObject's properties to test the current rule agains the input values in that form:
<div id="divEditor">
	<!-- Container for the editor. Its ID is used in the Api.GetControl() method -->
</div>

<!-- Test form -->
<div><input type="number" id="txtId" maxlength="8"  placeholder="ID" /></div>
<div><input type="text" id="txName" maxlength="50" placeholder="Name" /></div>
<div><input type="date" id="txDob" maxlength="10" placeholder="Date of Birth" /></div>

<div><input type="button" id="btnEvaluate" value="Evaluate" /></div>
  • This example assumes a new plain JS script, but you can also import the editor into a bundler (React, Vue, etc.). See demo projects for working examples:
var editor = null;


// ..... most of the code of the demo script is omitted here for clarity


// This method evaluates the rule currently displayed in the
// rule area against the input values in the Data Test Form
async function evaluate()
{
	// Create a new instanse of the SourceObject class and fill it with data
	// from the Test form to be evaluated against the currently displayed rule
	const data =
	{
		id: Number(nullOrValue("txtId")),
		name: nullOrValue("txName"),
		dob: nullOrValue("txDob")
	};

	const ev = await fetch('/api/evaluate', {
		method: 'POST',
		headers: { 'Content-Type': 'application/json' },
		body: JSON.stringify({ rule: editor.extract(), source: data })
	});

	const res = await ev.json();

	if (res.isRuleEmpty)
	{
		alert("The rule is empty");
	}
	else if (!res.isRuleValid)
	{
		editor.loadInvalids(res.clientInvalidData);
	}
	else
	{
		// Refresh the test form (values could be updated by the rule)
		document.getElementById("txtId").value = res.source.id ?? "";
		document.getElementById("txName").value = res.source.name ?? "";

		// Remove the time portion of the date (demo expects the US date format)
		document.getElementById("txDob").value =
			res.source.dob ? (res.source.dob.slice(0, 10)) : "";

		// Display the evaluation output
		alert(res.output);
	}
};

function nullOrValue(id)
{
	var v = document.getElementById(id).value.trim();
	return v.length > 0 ? v : null;
};

// Evaluate the rule currently displayed in the rule area
// when the user clicks the Evaluate button
document.getElementById("btnEvaluate").onclick = function (e) { evaluate(); };

Resources

p101

l097 --

l102

p101

×