An introduction to Coldfusion Components
Learn how to model the structure and behaviour of an object in Coldfusion using a CFC, and also how to use the functionality of that CFC in an application.
What is a Coldfusion Component?
A Coldfusion Component (CFC) is a self contained (encapsulated, modular) piece of code that has the capacity to create instances of itself in applications. These instances closely follow the structure and behaviour of objects in the real world. In Object Orientation (OO) terms a CFC is analogous to a Class. In the real world objects have state (shape,colour,capacity). In a CFC we represent that state using instance variables. To describe the state and behaviour of an object derived from a Class there are what is known as instance methods. In a CFC these instance methods are represented as functions, using the cffunction tag with its access attribute set to public.
These functions provide an interface to your CFC, know as an Application Programming Interface (API), that allow other developers to utilise your CFC in their own applications. However, a CFC is just a blueprint of an object, and it is useless unless it can produce objects that match that blueprint. To do that a CFC has what is known as a init() function method. In pure OO languages this init() function would known as a Constructor
Why bother?
When your code is properly encapsulated in a CFC it creates by default an API that gives access to the CFC's functionality; but at the same time hides the complexity of your code. This allows for potentially very complex functionality to be plugged into in any application in a very simple fashion. Your code becomes reusable not only by you - but by all developers. All they need to know to implement your functionality is the API of your CFC.
Encapsulation also makes maintaining your code also much easier. As your CFC is running as a self-contained unit in any application where its deployed. So as long as you don't change the API you can update the code in your CFC without worrying about how it will affect the rest of your application, or other people's applications.
Resuse and maintainability of code are the main reasons for writing OO Code.
I can't tell you how good a feeling it is to have someone from the other side of the world contact you to tell you that they are using your code in their application!
The other 2 main principles of OOP are inheritance and polymorphism. These are very good concepts to employ in your code but are not something you need to have in your CFCs when writing OO. Encapsulation is enough to get you going.
Glossary CFC
I have created a CFC that can provide applications with the functionality to produce glossary pages for a Website. Click here to view is it's API. (A CFC's API can be self documented if you fill in the hint attributes of an
cffunction and cfcomponent).
To view an application using the CFC click here. To view the source code of that application implementing the CFC using the CFC's API click here. To see the full source code for the CFC click here.
About instance variables
First of all we declare our instance variables. These variables hold the data that each object instance created by the glossary component is made up off. By declaring the variables here, outside any function, they are accessible to every function in the component. By putting them in the variables scope we also make them unavailable to any code outside the component which stops them being accessed and breaking encapsulation. These instance variables have what is known in OO as class scope. Where as if we declared them inside a function they would only be of local scope to that particular function and not accessible outside of that function. Which is what we don't want as each method including the constructor must use the same data. That is the data passed into the constructor when the component is called/invoked:
<!---instance varibles --->
<cfset variables.numberOfWordsInGlossary = 0>
<cfset variables.numberOfWordsLookedFor = 0>
<cfset variables.textToRead = "">
<cfset variables.glossaryOfWords = "">
<cfset variables.templateName = 0>
About Constructors
The init() function (constructor) builds objects of type Glossary. It takes the values passed into it from the calling code and instantiates the instance variables declared at the top of the component making that data available throughout the CFC.
<!---create object of type glossary called linuxGloss2 and pass in the values that will make up the glossary object--->
<cfset linuxGloss2 = createObject("component","glossary").init(documentText,glossaryList2,"glossary.cfm")>
The return type for an init() function must be the same as the component name. This ensures an actual object of type Glossary is returned to the calling code.
In here i have also put the code that builds an object of type Glossary. There is some jiggery-pokery going on here with regular expressions to actually bring about the purpose of this component, which is to create a glossary. But I won't go that here (maybe on another tutorial on regEx). For the sake of modularity it is an option to put this code into a private method and then call that method from the init() function but i have included it in the init() just to show that the object is being constructed by the init() function (constructor).
<cffunction name="init" access="public" returntype="Glossary" output="false"
hint="builds a glossary object by initialize members and calling private glossorize method. Object creation will fail if no words in the glossary list appear in the supplied text">
<cfargument name="textToRead" type="String" required="true" hint="This is document / string to search for glossary words in.">
<cfargument name="glossaryOfWords" type="String" required="true" hint="The list of glossary words that are to be used to search textToRead">
<cfargument name="templateName" type="String" required="true" hint="This is the name of the template which contains the HTML glossay page that each found glossary word will link to in order to get its meaning.">
<cfset var holder = "">
<cfset setTextToRead(arguments.textToRead)>
<cfset setGlossaryOfWords(arguments.glossaryOfWords)>
<cfset setTemplateName(arguments.templateName)>
<!---Will store the full text with all glossary words surrounded with HTML --->
<cfloop list="#variables.glossaryOfWords#" index="word">
<!---Find and replace function uses 2 regEx expressions.
By having the first expression, which is just simply a literal word from the glossary list, as a subexpression
i.e enclosed in (). We are able to use backrefernces in the 2nd expression; because backrefernces can span patterns.
The backrefernce to the first expression allows the word found in the doc by the
first expression to be used in the 2nd expression to build a string that
replaces the word found by 1st expression in the document.
Also appended to this string is the HTML code that will point each found word to
its meaning on the 'templatename' page
The 1st RegEx is enclosed in parenthesis to make it a sub expression, and with \b to mark the boundary of
the literal word in the expression.
--->
<cfset holder = #REReplace(variables.textToRead,"(" & '\b' & trim(word) & '\b' & ")",
"<a href='" & variables.templateName & "##" & '\1' & "' title='Click to view definition on our glossary page'>" & '\1' & "</a>")#>
<!---Update the full document text with a new html link for each glossary word processed by loop--->
<cfset variables.textToRead = holder>
<!---Record number words looked for instance variable = number of loop iterations --->
<cfset variables.numberOfWordsLookedFor = variables.numberOfWordsLookedFor + 1>
</cfloop>
<!---call private method which will set the numberOfWordsInGlossary instance variable --->
<cfoutput>
#countWordsFound()#
</cfoutput>
<!---in no glossary words are found in textToRead then dont abort / create object --->
<cfif variables.numberOfWordsInGlossary lt 1>
<cfabort showerror="Failed to create object of type Glossary!
Because no word in the glossary value list exists in the supplied documentText string">
</cfif>
<cfreturn this>
</cffunction>
About Public Methods/Functions
The public functions in the CFC can be accessed from outside the component i.e. from the calling code. This is done by invoking a method call on an instance(object) of type Glossary that has been created in the calling code.
E.G.
<!---create object of type glossary called linuxGloss2 --->
<cfset linuxGloss2 = createObject("component","glossary").init(documentText,glossaryList2,"glossary.cfm")>
<!---call/invoke a PUBLIC method on linuxGloss2 --->
<cfoutput>
#linuxGloss2.numberOfWordsFound()#
</cfoutput>
Public functions perform an operation on each instance of the component, or give info on the current state of the object, e.g...
<cffunction returntype="Numeric" access="public" name="numberOfWordsFound" output="false"
hint="returns the number of glossary words found in text">
<cfreturn variables.numberOfWordsInGlossary>
</cffunction>
The above function simply returns the value of the instance variable that was set and calculated in the init() function. Note the access attribute - this has to be set to public for the function to be accessable outside the CFC.
About Private Methods/Functions
The private methods are routines used only by component itself to perform an internal operation and cannot be accessed from outside the component. E.G. ...
<cffunction access="private" name="countWordsFound" output="false" hint="the text string that will be checked for words that appear in glossary list">
<cfset var holder2 = 0>
<cfloop list="#variables.glossaryOfWords#" index="word">
<!---find the number of words found --->
<cfset holder2 = #find(trim(word),variables.textToRead)#>
<cfif holder2 gt 0>
<cfset variables.numberOfWordsInGlossary = variables.numberOfWordsInGlossary + 1>
</cfif>
</cfloop>
</cffunction>
Summary
I have illustrated how to model and build an object as a CFC. Instance variables contain the state of an object. Init() functions are called/invoked on an instance of a CFC in an application, allowing the functionality of the CFC to used in the application. Publicly accessible instance functions allow info about the state of object to be obtained. Private functions are used to perform functions internal to the CFC and are not accessible to cod outside the CFC. By providing a documented API to a CFC it allows for the reuse of code and ease of maintainability of the code. We showed a real application implementing and using the functionality of a CFC by using it's API.
There are no comments for this entry.
[Add Comment]