Monday, July 1, 2024
HomeJavaScriptConstructing-Up A Advanced Objects Utilizing A Multi-Step Kind Workflow In ColdFusion

Constructing-Up A Advanced Objects Utilizing A Multi-Step Kind Workflow In ColdFusion


Earlier this week, I checked out utilizing type POST-backs to construct up complicated objects in ColdFusion. That approach allowed for deeply-nested knowledge to be seamlessly up to date utilizing dot-delimited “object paths”. My earlier demo used a single web page to render the shape. As a fast-follow, I needed to interrupt the demo up right into a multi-step type workflow during which every step manages solely a portion of the complicated object.

As a fast recap of my earlier put up, listed here are some key-points to recollect:

  • The complete pending knowledge construction was serialized as JSON (JavaScript Object Notation) and was included as a kind="hidden" type discipline. This allowed the “state” of the pending complicated object to be submitted again to the server together with every type POST.

  • Every type discipline had a reputation that was composed of a dot-delimited object path, instance: .contacts.2.kind. The worth of every type discipline was then seamlessly merged into the pending complicated object utilizing these dot-delimited paths with every type POST.

  • The shape used a number of submit buttons, all with title="motion". The worth of those submit buttons decided if-and-how the pending complicated object was manipulated with every type POST.

Accessing and mutating arbitrarily deep struct keys and array indices was facilitated by a ColdFusion part referred to as PendingFormData.cfc. This part merged the shape submission into the complicated object utilizing the dot-delimited paths:

part
	output = false
	trace = "I present strategies for manipulating the pending knowledge in a type."
	{

	/**
	* I initialize the pending type knowledge with the given, serialized worth.
	*/
	public void operate init(
		required struct formScope,
		required string dataKey,
		required struct defaultValue
		) {

		this.knowledge = ( len( formScope[ dataKey ] ?: "" ) )
			? deserializeJson( formScope[ dataKey ] )
			: defaultValue
		;

		// Loop over the shape scope and search for OBJECT PATHS. Any key that's an object
		// path ought to have its worth saved into the pending knowledge construction.
		for ( var key in formScope ) {

			if ( key.left( 1 ) == "." ) {

				setValue( key, formScope[ key ].trim() );

			}

		}

	}

	// ---
	// PUBLIC METHODS.
	// ---

	/**
	* I return the serialized worth for the present, pending knowledge construction.
	*/
	public string operate getJson() {

		return( serializeJson( this.knowledge ) );

	}


	/**
	* I take a dot-delimited object path, like ".contacts.3.quantity", and return the worth
	* saved deep inside the "knowledge" construction.
	*/
	public any operate getValue( required string objectPath ) {

		// Right here, we're utilizing the .cut back() methodology to stroll the dot-delimited segments
		// inside the important thing path and traverse down into the info object. Every dot-delimited
		// section represents a step down right into a nested construction (or Array).
		var worth = objectPath.listToArray( "." ).cut back(
			( discount, section ) => {

				return( discount[ segment ] );

			},
			this.knowledge
		);

		return( worth );

	}


	/**
	* I take a dot-delimited object path, like ".contacts.3.quantity", and retailer a brand new worth
	* deep inside the "knowledge" construction.
	*/
	public void operate setValue(
		required string objectPath,
		required any worth
		) {

		// Once more, we're utilizing the .cut back() methodology to stroll the dot-delimited segments
		// inside the important thing path and traverse down into the info object. Solely this time,
		// as soon as we attain the LAST SEGMENT, we will deal with it as a WRITE slightly than
		// a READ.
		objectPath.listToArray( "." ).cut back(
			( discount, section, segmentIndex, segments ) => {

				// LAST SEGMENT turns into a write operation, not a learn.
				if ( segmentIndex == segments.len() ) {

					discount[ segment ] = worth;

				}

				return( discount[ segment ] );

			},
			this.knowledge
		);

	}

}

In my earlier demo, I stored a bunch of the logic within the “controller” (ie, the top-level CFML web page). However, now that we’re breaking this type up right into a multi-step course of, the “controller” is getting a bit busy. As such, I’ve determined to sub-class the PendingFormData.cfc part, creating a brand new ColdFusion part particular to this multi-step type.

Along with extending (and initializing) the “tremendous” part, this ColdFusion part gives high-level strategies for manipulating the complicated knowledge construction. For instance, it has an addContact() and a deleteContact() methodology. The “controller” remains to be chargeable for invoking these strategies; however, the logic for what they do is now collocated with the complicated object state.

A part of why I did it is because I needed so as to add some light-weight validation to the multi-step workflow because the consumer progresses from step to step. That is completed by way of the subsequent() methodology, which appears on the present step and the present state of the complicated object and both updates the state or returns an error message (to be rendered to the consumer within the present step).

Within the following ColdFusion part, word that I now have some “step” info along with the core state knowledge:

part
	extends = "PendingFormData"
	output = false
	trace = "I present processing strategies round a selected multi-step type."
	{

	/**
	* I initialize the multi-step type helper with the given type scope.
	*/
	public void operate init( required struct formScope ) {

		tremendous.init(
			formScope,
			// Which type discipline holds our serialized knowledge.
			"knowledge",
			// The default construction if the "knowledge" key's empty.
			[
				// Where we are (and what we've completed) in the multi-step process.
				steps: [
					current: 1,
					completed: 0
				],
				// The precise type knowledge that we care about (ie, not associated to the steps).
				title: "",
				e mail: "",
				contacts: [],
				isFavorite: false
			]

		);

	}

	// ---
	// PUBLIC METHODS.
	// ---

	/**
	* I add a brand new, empty contact.
	*/
	public void operate addContact() {

		this.knowledge.contacts.append([
			type: "home",
			phone: ""
		]);

	}


	/**
	* I delete the given contact.
	*/
	public void operate deleteContact( required numeric index ) {

		this.knowledge.contacts.deleteAt( index );

		// If the consumer has eliminated all the contacts AFTER having already accomplished step
		// 2, we are able to now not think about step past to 2 to be accomplished. This fashion, the
		// consumer should "subsequent" by step 2, which is able to kick within the validation for
		// step 2 as soon as once more.
		if ( ! this.knowledge.contacts.len() && ( this.knowledge.steps.accomplished > 2 ) ) {

			this.knowledge.steps.accomplished = 2;

		}

	}


	/**
	* I proceed the multi-step type to the given step.
	*/
	public void operate gotoStep( required numeric step ) {

		// The consumer cannot proceed previous the best step that they've already accomplished.
		if ( step > this.knowledge.steps.accomplished ) {

			return;

		}

		this.knowledge.steps.present = step;

	}


	/**
	* I course of the CURRENT step after which proceed to the NEXT step. This includes
	* validation of the present step's knowledge and the return of an error message if the
	* knowledge isn't legitimate.
	*/
	public string operate subsequent() {

		swap ( this.knowledge.steps.present ) {
			case 1:

				if ( ! this.knowledge.title.len() ) {

					return( "Please enter your title." );

				}

				if ( ! this.knowledge.e mail.len() ) {

					return( "Please enter your e mail." );

				}

				// Present step is legitimate, proceed to the following step.
				this.knowledge.steps.accomplished = max( this.knowledge.steps.present, this.knowledge.steps.accomplished );
				this.knowledge.steps.present = 2;

			break;
			case 2:

				if ( ! this.knowledge.contacts.len() ) {

					return( "Please enter at the least one contact quantity." );

				}

				// Present step is legitimate, proceed to the following step.
				this.knowledge.steps.accomplished = max( this.knowledge.steps.present, this.knowledge.steps.accomplished );
				this.knowledge.steps.present = 3;

			break;
			case 3:

				// !! Woot woot, you probably did it !!

			break;
		}

		// Fall-through return, no error detected.
		return( "" );

	}


	/**
	* I toggle the favourite standing.
	*/
	public void operate setFavorite( required boolean isFavorite ) {

		this.knowledge.isFavorite = isFavorite;

	}

}

To this point, we’ve not actually modified something on this model of the demo – we have solely moved a number of the logic out of the “controller” layer and into this ColdFusion part. Now, let us take a look at how we are able to break the data-entry workflow up into a number of steps.

Bear in mind, nevertheless, that that is being damaged up into a number of steps for the sake of the consumer expertise (UX); however, I am nonetheless contemplating this all to be “one huge type”. As such, I am nonetheless processing all of the actions on the prime of the primary “controller” file – the steps solely server to render a subset of the shape knowledge.

The person steps are rendered as separate CFInclude templates that every one render inside the context for a father or mother <type> tag. The father or mother <type> takes care of submitting the pending state JSON as a hidden type discipline, leaving the step-wise templates to do little greater than render just a few of the shape fields. And, because the complete, serialized state of the pending type is being submitted together with every POST, we do not have to fret about our particular person steps submitting any extra knowledge than is related for that specific step.

This is my top-level “controller” CFML file:

<cfscript>

	// With each FORM POST, the present, serialized model of our pending knowledge goes
	// to be posted again to the server. The ACTION values will then describe how we would like
	// to mutate this post-back knowledge on every rendering.
	param title="type.knowledge" kind="string" default="";

	// The PendingFormData() gives helper features that make it simple to entry and
	// change properties deep inside the pending knowledge construction. And, to encapsulate some
	// of our controller processing logic, I am EXTENDING the ColdFusion part with one
	// that exposes motion methodology for manipulation of the info.
	formProxy = new MyMultiStepForm( type );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Every SUBMIT BUTTON can present an "motion" worth that's within the type of:
	// --
	// {motion} : {objectPath} : {index}
	// --
	// This worth shall be break up into separate values for simpler consumption, and gives
	// us with a way to find and mutate values deep inside the pending knowledge construction.
	param title="type.motion" kind="string" default="";
	param title="type.actionPath" kind="string" default="";
	param title="type.actionIndex" kind="numeric" default="0";

	if ( type.motion.discover( ":" ) ) {

		elements = type.motion.listToArray( ": " );

		type.motion = elements[ 1 ];
		type.actionPath = elements[ 2 ];
		type.actionIndex = val( elements[ 3 ] ?: 0 );

	}

	// NOTE: All steps on this multi-step type course of SUBMIT BACK TO THE SAME PAGE. The
	// steps are right here to make it simpler for the consumer (UX); however, because the developer, I am
	// nonetheless contemplating this a "single course of". As such, I am managing all of the actions
	// and type knowledge mutations on this top-level web page (versus breaking them out
	// into the person steps).

	errorMessage = "";

	// NOTE: Not all actions have to make use of the "object path" strategy. If we all know the keys
	// within the knowledge that we're mutating, we are able to entry them immediately. There is no profit to
	// including abstraction for the sake of abstraction.
	swap ( type.motion ) {
		case "addContact":

			formProxy.addContact();

		break;
		case "deleteContact":

			formProxy.deleteContact( val( type.actionIndex ) );

		break;
		case "gotoStep":

			formProxy.gotoStep( val( type.actionIndex ) );

		break;
		case "subsequent":

			// Because the consumer proceeds from step to step, we've got to validate the shape knowledge
			// within the present step. Calling subsequent() on our type proxy will return an error
			// message if there's a drawback.
			errorMessage = formProxy.subsequent();

		break;
		case "setFavorite":

			formProxy.setFavorite( true );

		break;
		case "clearFavorite":

			formProxy.setFavorite( false );

		break;
	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	typeOptions = [
		{ value: "home", label: "Home" },
		{ value: "mobile", label: "Mobile" },
		{ value: "work", label: "Work" }
	];

	// To make it simpler to render the shape inputs and handle the multi-step development,
	// let's get a direct reference to the pending knowledge construction. There is no profit to
	// going by the PendingFormData() part if we're not going to be manipulating
	// summary object paths.
	knowledge = formProxy.knowledge;

</cfscript>
<cfoutput>

	<!doctype html>
	<html lang="en">
	<head>
		<meta charset="utf-8" />
		<hyperlink rel="stylesheet" kind="textual content/css" href="https://www.bennadel.com/weblog/./kinds.css" />
	</head>
	<physique>
	
		<h1>
			Constructing-Up A Advanced Objects Utilizing A Multi-Step Kind Workflow In ColdFusion
		</h1>

		<type methodology="put up">

			<h2>
				Multi-Step Kind Workflow
			</h2>

			<!---
				The complete pending type object (JSON model), is posted again to the
				server as a hidden discipline with each motion. We'll then use the "motion"
				values to use totally different transformations to the pending type object with
				every type submission.
			--->
			<enter
				kind="hidden"
				title="knowledge"
				worth="#encodeForHtmlAttribute( formProxy.getJson() )#"
			/>

			<!---
				When a type is submitted with the ENTER KEY, the browser will implicitly
				use the primary submit button within the DOM TREE order because the button that
				triggered the submit. Since we're utilizing MULTIPLE SUBMIT buttons to drive
				object manipulation, let's add a NON-VISIBLE submit as the primary factor
				within the type in order that the browser makes use of this one because the default submit.
			--->
			<button
				kind="submit"
				title="motion"
				worth="subsequent"
				fashion="place: mounted ; prime: -1000px ; left: -1000px ;">
				Subsequent
			</button>

			<!---
				BREADCRUMBS NAVIGATION. Since our complete type state is being saved as
				JSON in a hidden type discipline, navigation between steps needs to be carried out
				as a POST BACK to the server. As such, our breadcrumb hyperlinks are literally
				unstyled SUBMIT BUTTONs with "goto" actions.
			--->
			<p>
				Steps:

				<cfloop index="i" from="1" to="3">
					<cfif ( i lte knowledge.steps.accomplished )>
						<!--- Actionable breadcrumb. --->
						<button kind="submit" title="motion" worth="gotoStep : _ : #i#" class="text-button">
							[ Step #i# ]
						</button>
					<cfelse>
						<!--- Informational breadcrumb. --->
						[ Step #i# ]
					</cfif>
				</cfloop>
			</p>

			<cfif errorMessage.len()>
				<p class="error">
					#encodeForHtml( errorMessage )#
				</p>
			</cfif>

			<cfswitch expression="#knowledge.steps.present#">
				<cfcase worth="1">
					<cfinclude template="./multi-step-1.cfm" />
				</cfcase>
				<cfcase worth="2">
					<cfinclude template="./multi-step-2.cfm" />
				</cfcase>
				<cfcase worth="3">
					<cfinclude template="./multi-step-3.cfm" />
				</cfcase>
			</cfswitch>

		</type>

		<h2>
			Pending Kind Knowledge
		</h2>

		<cfdump var="#knowledge#" />

	</physique>
	</html>

</cfoutput>

As you’ll be able to see, we’re utilizing CFInclude to incorporate the rendering for the present step. This is Step 1 – discover that it does not care in regards to the pending type state or any type fields exterior of the present step. However, it does have a number of motion buttons – these are what drive the mutation of the pending type knowledge on every type POST:

<cfoutput>

	<h2>
		Step 1
	</h2>

	<!---
		NOTE that the title of every type discipline begins with a "." as in ".title". These are
		OBJECT PATHS and can robotically be saved into the pending knowledge construction when
		the MyMultiStepForm( PendingFormData() ) elements are initialized.
	--->

	<div class="entry">
		<label class="entry__label">
			Title:
		</label>
		<div class="entry__body">
			<enter
				kind="textual content"
				title=".title"
				worth="#encodeForHtmlAttribute( knowledge.title )#"
				measurement="32"
			/>
		</div>
	</div>
	<div class="entry">
		<label class="entry__label">
			E-mail:
		</label>
		<div class="entry__body">
			<enter
				kind="e mail"
				title=".e mail"
				worth="#encodeForHtmlAttribute( knowledge.e mail )#"
				measurement="32"
			/>
		</div>
	</div>
	<div class="entry">
		<div class="entry__label">
			Favourite:
		</div>
		<div class="entry__body">
			<cfif knowledge.isFavorite>
				Is favourite contact
				<button kind="submit" title="motion" worth="clearFavorite">
					Clear favourite
				</button>
			<cfelse>
				Is <em>not</em> favourite contact
				<button kind="submit" title="motion" worth="setFavorite">
					Set as favourite
				</button>
			</cfif>
		</div>
	</div>

	<div class="buttons">
		<button kind="submit" title="motion" worth="subsequent" class="major">
			Subsequent &raquo;
		</button>
	</div>

</cfoutput>

I am not going to trouble displaying the opposite steps as a result of I mainly simply took the code from the earlier demo and broke it up into separate information. The person steps did not change – it is the managing of the steps as a multi-step workflow that differentiates this model of the demo.

With that mentioned, if I open the ColdFusion type and begin submitting knowledge, we get the next output:

As you’ll be able to see, as I proceed by every step, our pending, complicated knowledge construction is being populated iteratively. Every particular person step solely cares about its personal type fields because the complete pending knowledge construction is being re-submitted as a hidden type discipline with every motion. As such, every type submission is barely about updating a subset of the item graph.

The important thing to all of this, actually, is the truth that the whole state of the shape is being submitted as JSON in every POST. That is what makes it attainable to handle state throughout a number of pages with out having to persist the info to a data-store of some kind.

Need to use code from this put up?
Try the license.



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments