July 1, 2011

FarCry - grouping formtool category items

A new project we’re working has the need to categorise each new content object, for this we use the built in FarCry category/keyword formtool.

By default the render type is a multi-select tree, I wanted a single select dropdown. Luckily formtools makes this oh so simple.

<cfproperty name="catHTML" type="nstring" required="true" default="" ftSeq="2" ftwizardStep="Content"  ftValidation="required" ftFieldset="General Details" ftLabel="Categories" ftType="Category" ftAlias="sections" ftRenderType="dropdown" ftSelectSize="1" ftSelectMultiple="false" ftAliasAsOptGroup="true">

Note that I’ve set the dropdown size to 1 and multiple select to false. Basically I have a group of “sections” that each content object needs to belong to, section 1 through 11. I’ve setup the categories already under a parent category called “sections” (note the ftAlias in the formtool definition). Unfortunately the default rendering was showing “sections” as a selectable category, however I didn’t want this. I only wanted the users to be able to choose the children of this category, not the category itself.

Luckily FarCry makes this kind of thing simple to override. I added my own category.cfc inside my projects packages/formtools directory and made sure to extend category.cfc from farcry.core.packages.formtools.category. In this I only used the edit() function to render an optgroup from a new custom property called ftAliasAsOptGroup (see above snippet). This gave me the following:

FarCry optgroup

The full category.cfc is below if you’re interested.

<cfcomponent extends="farcry.core.packages.formtools.category">


	<cfproperty name="ftAliasAsOptGroup" default="" hint="If true, renders ftAlias as an optgroup">


	<cffunction name="edit" access="public" output="false" returntype="string" hint="his will return a string of formatted HTML text to enable the user to edit the data">
		<cfargument name="typename" required="true" type="string" hint="The name of the type that this field is part of.">
		<cfargument name="stObject" required="true" type="struct" hint="The object of the record that this field is part of.">
		<cfargument name="stMetadata" required="true" type="struct" hint="This is the metadata that is either setup as part of the type.cfc or overridden when calling ft:object by using the stMetadata argument.">
		<cfargument name="fieldname" required="true" type="string" hint="This is the name that will be used for the form field. It includes the prefix that will be used by ft:processform.">

		<cfset var html = "" />
		<cfset var navid = "" />
		<cfset var oCategory = createObject("component",'farcry.core.packages.farcry.category')>
		<cfset var lSelectedCategoryID = "" >
		<cfset var lCategoryBranch = "" />
		<cfset var CategoryName = "" />
		<cfset var i = "" />
		<cfset var rootNodeText = "" />
		<cfset var rootID = "" />

		<cfif structKeyExists(application.catid, arguments.stMetadata.ftAlias)>
			<cfset rootID = application.catid[arguments.stMetadata.ftAlias] >
		<cfelse>
			<cfset rootID = application.catid['root'] >
		</cfif>

		<cfset lSelectedCategoryID = oCategory.getCategories(objectid=arguments.stObject.ObjectID,bReturnCategoryIDs=true,alias=arguments.stMetadata.ftAlias) />

		<cfset rootNodeText = oCategory.getCategoryNamebyID(categoryid=rootID) />

		<cfswitch expression="#arguments.stMetadata.ftRenderType#">

			<cfcase value="dropdown">
				<cfset lCategoryBranch = oCategory.getCategoryBranchAsList(lCategoryIDs=rootID) />

				<cfsavecontent variable="html">
					<cfoutput><select id="#arguments.fieldname#" name="#arguments.fieldname#"  <cfif arguments.stMetadata.ftSelectMultiple>size="#arguments.stMetadata.ftSelectSize#" multiple="true"</cfif> class="selectInput #arguments.stMetadata.ftSelectSize# #arguments.stMetadata.ftClass#"></cfoutput>
					<cfloop list="#lCategoryBranch#" index="i">
						<!--- If the item is the actual alias requested then it is not selectable. --->
						<cfif i EQ rootID>
							<cfif len(arguments.stMetadata.ftDropdownFirstItem)>
								<cfoutput><option value="">#arguments.stMetadata.ftDropdownFirstItem#</option></cfoutput>
							<cfelse>
								<cfset CategoryName = oCategory.getCategoryNamebyID(categoryid=i,typename='dmCategory') />
								<cfif arguments.stMetadata.ftAliasAsOptGroup EQ true>
									<cfoutput><optgroup label="#CategoryName#"></cfoutput>
								<cfelse>
									<cfoutput><option value="">#CategoryName#</option></cfoutput>
								</cfif>
							</cfif>
						<cfelse>
							<cfset CategoryName = oCategory.getCategoryNamebyID(categoryid=i,typename='dmCategory') />
							<cfoutput><option value="#i#"<cfif listContainsNoCase(lSelectedCategoryID, i)> selected="selected"</cfif>>#CategoryName#</option></cfoutput>
						</cfif>
					</cfloop>
					<cfif arguments.stMetadata.ftAliasAsOptGroup EQ true>
						<cfoutput></optgroup></cfoutput>
					</cfif>
					<cfoutput></select></cfoutput>
				</cfsavecontent>
			</cfcase>

			<cfcase value="prototype">
				<cfsavecontent variable="html">

					<cfoutput><fieldset style="width: 300px;">
						<cfif len(arguments.stMetadata.ftLegend)><legend>#arguments.stMetadata.ftLegend#</legend></cfif>

						<div class="fieldsection optional full">

							<div class="fieldwrap">
							</cfoutput>

								<ft:NTMPrototypeTree id="#arguments.fieldname#" navid="#rootID#" depth="99" bIncludeHome=1 lSelectedItems="#lSelectedCategoryID#" bSelectMultiple="#arguments.stMetadata.ftSelectMultiple#">

							<cfoutput>
							</div>

							<br class="fieldsectionbreak" />
						</div>
						<input type="hidden" id="#arguments.fieldname#" name="#arguments.fieldname#" value="" />
					</fieldset></cfoutput>

				</cfsavecontent>
			</cfcase>
			<cfcase value="extjs">
				<!--- <skin:htmlHead library="extjs" />
				<skin:htmlHead library="farcryForm" /> --->

				<cfsavecontent variable="html">

					<cfoutput><fieldset style="width: 300px;">
						<cfif len(arguments.stMetadata.ftLegend)><legend>#arguments.stMetadata.ftLegend#</legend></cfif>

						<!--- <div id="tree-div" style="border:1px solid #c3daf9;"></div> --->
						<div class="fieldsection optional full">

							<div class="fieldwrap">

								<div id="#arguments.fieldname#-tree-div"></div>

							</div>

							<br class="fieldsectionbreak" />
						</div>
						<input type="hidden" id="#arguments.fieldname#" name="#arguments.fieldname#" value="#lSelectedCategoryID#" />
						<input type="hidden" name="#arguments.fieldname#" value="" />
					</fieldset>
					</cfoutput>

				</cfsavecontent>

				<skin:onReady>
				<cfoutput>
				    createFormtoolTree('#arguments.fieldname#','#rootID#', '#application.url.webtop#/facade/getCategoryNodes.cfm', '#rootNodeText#','#lSelectedCategoryID#', 'categoryIconCls');
				</cfoutput>
				</skin:onReady>
			</cfcase>
			<cfcase value="jquery">

				<skin:onReady>
				<cfoutput>
					$j("###arguments.fieldname#_list").treeview({
						url: "#application.url.webtop#/facade/getCategoryNodes.cfm?node=#rootID#&fieldname=#arguments.fieldname#&multiple=#arguments.stMetadata.ftSelectMultiple#&lSelectedItems=#lSelectedCategoryID#"
					})
				</cfoutput>
				</skin:onReady>

				<cfsavecontent variable="html">
					<cfoutput>
					<div class="multiField">
						<ul id="#arguments.fieldname#_list" class="treeview"></ul>
					</div>
					<input type="hidden" name="#arguments.fieldname#" value="" />
					</cfoutput>
				</cfsavecontent>
			</cfcase>
			<cfdefaultcase>

				<skin:loadJS id="jquery" />
				<skin:loadJS id="jquery-checkboxtree" basehref="#application.url.webtop#/thirdparty/checkboxtree/js" lFiles="jquery.checkboxtree.js" />
				<skin:loadCSS id="jquery-checkboxtree" basehref="#application.url.webtop#/thirdparty/checkboxtree/css" lFiles="checkboxtree.css" />

				<skin:onReady>
				<cfoutput>
					$j.ajax({
					   type: "POST",
					   url: '#application.fapi.getLink(type="dmCategory", objectid="#rootID#", view="displayCheckboxTree", urlParameters="ajaxmode=1")#',
					   data: {
					   	fieldname: '#arguments.fieldname#',
					   	rootNodeID:'#rootID#',
					   	selectedObjectIDs: '#lSelectedCategoryID#'
						},
					   cache: false,
					   success: function(msg){
					   		$j("###arguments.fieldname#-checkboxDiv").html(msg);
							$j("###arguments.fieldname#-checkboxTree").checkboxTree({
									collapsedarrow: "#application.url.webtop#/thirdparty/checkboxtree/images/checkboxtree/img-arrow-collapsed.gif",
									expandedarrow: "#application.url.webtop#/thirdparty/checkboxtree/images/checkboxtree/img-arrow-expanded.gif",
									blankarrow: "#application.url.webtop#/thirdparty/checkboxtree/images/checkboxtree/img-arrow-blank.gif",
									checkchildren: false,
									checkparents: false
							});
							$j("###arguments.fieldname#-checkboxDiv input:checked").addClass('mjb');
							$j("###arguments.fieldname#-checkboxDiv input:checked").parent().addClass('mjb');
					   }
					 });

				</cfoutput>
				</skin:onReady>

				<cfsavecontent variable="html">

					<cfoutput>
					<div class="multiField">
						<div id="#arguments.fieldname#-checkboxDiv">loading...</div>
						<input type="hidden" name="#arguments.fieldname#" value="" />
					</div>
					</cfoutput>

				</cfsavecontent>
			</cfdefaultcase>

		</cfswitch>

		<cfreturn html>
	</cffunction>

</cfcomponent>

© Michael Sharman 2017