My Shortie: Ray Camden's Beginner ColdFusion Contest (Monster Maker)

Posted May 16, 2007 at 9:19 AM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML, HTML / CSS, AJAX

Now that Ray Camden's Beginner ColdFusion Content has ended, I can discuss my unofficial entry. Seeing as I am not a beginner, I could not technically enter the contest, but I thought it would be a cool little project to have a go at, especially since I am not tremendously proficient with CFCs and object oriented programming (OOP).

Before I get into the details, I figured you might want to try the application. It is called My Shortie and can be launched here:

Launch My Shortie

Here is a screen shot of the application (don't worry, there is nothing Saucy about this application):

 
 
 
 
 
 
Ray Camden's Beginner ColdFusion Contest Entry - My Shorti 
 
 
 

The labotomize button resets the Shortie's data. Also I am sure that is misspelling that word (labotomize) but I was too deep into the project to care by the time I found out :)

This little project proved much harder than I thought it was going to be. My first real hurdle was, How the heck do I interact with this entity (the Shortie) and how do I know how to affect the environmental properties in a way that is open to change? My first break through was the idea of an "Interaction".

An Interaction was a base component that defined what an interaction event with the Shortie would do; how does it affect the Shortie's energy? How long does it take to complete this interaction? How does it affect feelings of anger or love? Here is the base ColdFusion component that defines what an Interaction must define:

  • <cfcomponent
  • output="false"
  • hint="This component defines interactions with your Shortie. This is the base interaction that has zeroed out values. This interaction's INIT method should be overridden by specific interactions.">

  • <!---
  • Run pseudo constructor. Here is where we can set
  • up default data structures and data values.
  • --->

  • <!---
  • Create a private data structure to hold all instance
  • related data.
  • --->
  • <cfset VARIABLES.Instance = StructNew() />

  • <!--- The name of the interaction. --->
  • <cfset VARIABLES.Instance.Name = "" />

  • <!---
  • This is the unique key for this interaction. No TWO
  • interactions should share the same key. This will help
  • the user keep track of repetative interactions.
  • --->
  • <cfset VARIABLES.Instance.Key = "base_interaction" />

  • <!---
  • This is the amount of time that the interaction
  • requires. All interactions require a minimum of 1 hour
  • (shortie hour, not real world hour). Some interactions
  • will take longer.
  • --->
  • <cfset VARIABLES.Instance.TimeRequired = CreateTimeSpan(
  • 0, <!--- Days. --->
  • 1, <!--- Hours. --->
  • 0, <!--- Minutes. --->
  • 0 <!--- Seconds. --->
  • ) />

  • <!---
  • The following are suggested values as to how this
  • interaction will leave the user feeling. How the user
  • actually interpruts these values is out of our hands.
  • Positive values means that this interaction should
  • leave the end user feeling more of that dimension.
  • Negative numbers should leave the end user feeling
  • less of that dimension.
  • --->

  • <!---
  • This is how much love (feelings of being loved) a
  • user should feel after this interaction.
  • --->
  • <cfset VARIABLES.Instance.Love = 0 />

  • <!---
  • This is how much anger a user should feel after
  • this interaction.
  • --->
  • <cfset VARIABLES.Instance.Anger = 0 />

  • <!---
  • This is how much hunger a user should feel after
  • this interaction (negative values, for example,
  • means less hungry).
  • --->
  • <cfset VARIABLES.Instance.Hunger = 0 />

  • <!---
  • This is how much happiness a user should feel
  • after this interaction.
  • --->
  • <cfset VARIABLES.Instance.Happiness = 0 />

  • <!---
  • This is how much energy consumption is used after
  • this interaction (a negative value here means that
  • the user had to PUT OUT energy to perform this
  • interaction. A positive value, such as that defined
  • by sleep, would result in a higher final eneregy).
  • --->
  • <cfset VARIABLES.Instance.Energy = 0 />

  • <!---
  • This is a subjective flag as to whether the
  • interaction in general is thought of as a positive
  • or negative one. This flag will not always determine
  • how the end user views this interaction.
  • --->
  • <cfset VARIABLES.Instance.IsPositive = true />



  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Returns an initialized Interaction component. This method is supposed to be overriden by interactions that extend this base interaction.">

  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>


  • <cffunction
  • name="GetAnger"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Gets the anger rating.">

  • <cfreturn VARIABLES.Instance.Anger />
  • </cffunction>


  • <cffunction
  • name="GetEnergy"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Gets the energy rating.">

  • <cfreturn VARIABLES.Instance.Energy />
  • </cffunction>


  • <cffunction
  • name="GetHappiness"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Gets the happiness rating.">

  • <cfreturn VARIABLES.Instance.Happiness />
  • </cffunction>


  • <cffunction
  • name="GetHunger"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Gets the hunger rating.">

  • <cfreturn VARIABLES.Instance.Hunger />
  • </cffunction>


  • <cffunction
  • name="GetIsPositive"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="Gets the positive vs. negative falg.">

  • <cfreturn VARIABLES.Instance.IsPositive />
  • </cffunction>


  • <cffunction
  • name="GetKey"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="Gets the interaction key.">

  • <cfreturn VARIABLES.Instance.Key />
  • </cffunction>


  • <cffunction
  • name="GetLove"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Gets the love rating.">

  • <cfreturn VARIABLES.Instance.Love />
  • </cffunction>


  • <cffunction
  • name="GetTimeRequired"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Gets the time required to perform action.">

  • <cfreturn VARIABLES.Instance.TimeRequired />
  • </cffunction>

  • </cfcomponent>

As you can see, each interaction MUST have the following properties that are Get()able:

  • Anger
  • Energy
  • Happiness
  • Hunger
  • IsPositive
  • Love
  • TimeRequired

I decided that every interaction with the Shortie could be defined as some combination of these properties. Most of the properties (anger, energy, happiness, hunger, love) are simply numeric values that either add or subtract from the Shortie's properties. For instance, telling the Shortie, "I love you," should have a positive "Love" property since you tend to feel more loved when someone says they love you.

All interactions defined for the application must extend this base interaction. So for example, here is concrete ColdFusion component that defines the interaction for taking the Shortie to dinner (TakeToDinner.cfc):

  • <cfcomponent
  • extends="Interaction"
  • output="false"
  • hint="Take someone out to dinner.">


  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Returns an initialized interaction.">

  • <!--- Run super constructor. --->
  • <cfset SUPER.Init() />

  • <!---
  • Set interaction specific values. We are
  • NOT adding any new values here; these are
  • the values that will override the base
  • values set by the base interaction. NOT
  • all of the previously defined values need
  • to be overridden.
  • --->
  • <cfset VARIABLES.Instance.Name = "Take To Dinner" />
  • <cfset VARIABLES.Instance.Key = "take_to_dinner" />

  • <!--- This interaction will take 3.5 hours. --->
  • <cfset VARIABLES.Instance.TimeRequired = CreateTimeSpan(
  • 0,
  • 3,
  • 30,
  • 0
  • ) />

  • <!---
  • This interaction can make the user feel a bit more
  • loved, or it can just be part of the routine.
  • --->
  • <cfset VARIABLES.Instance.Love = 1 />

  • <!---
  • This interaction can make the user feel a bit less
  • angry, or it can just be part of the routine.
  • --->
  • <cfset VARIABLES.Instance.Anger = -1 />

  • <!---
  • This interaction can give the user more energy or
  • leave them with some serious food coma.
  • --->
  • <cfset VARIABLES.Instance.Energy = 3 />

  • <!---
  • This interaction can increase happiness or it can
  • just be part of the routine.
  • --->
  • <cfset VARIABLES.Instance.Happiness = 1 />

  • <!---
  • This interaction will most definitely leave the user
  • feeling less hungry than they were before.
  • --->
  • <cfset VARIABLES.Instance.Hunger = -10 />

  • <!---
  • This interaction is a positive overall
  • experience.
  • --->
  • <cfset VARIABLES.Instance.IsPositive = true />

  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>

  • </cfcomponent>

As you can see, these concrete implementations of the Interaction base class just override the constructor (Init) method. All the Getter methods are defined by the base ColdFusion component. The only thing that is special about the concrete Interactions is that they have a special combination of the properties listed above. So, looking at the TakeToDinner.cfc, you can see that eating results in a -10 change in the Shortie's hunger (potentially) and requires 3.5 hours of time.

The idea behind the TimeRequired is that you can't simply fire interaction after interaction. Interactions with anyone take time. Sure, some interactions don't take very long, but for our simple application, we are going to assume that no interaction takes any less than one hour.

Once I started to flesh out more of the concrete interaction ColdFusion components, I felt really good about where this path was leading. This combination of properties, while simple, does feel quite natural. The next big hurdle was data persistence. The application has to be really simple and portable, so that means no database. But, since we cannot depend on purely APPLICATION-scoped values as this application must survive over time, the next best solution is data persistence via XML files.

But what are we persisting? Not just any old data - we are persisting how the Shortie feels at a given time. To me, this sounds like a Brain. So, I created a Brain.cfc ColdFusion component that has generic Get() and Set() methods. These Get() and Set() methods can take any kind of serializeable data (strings, numbers, dates, arrays, structs). These data points then get serialized and stored to an XML file via ColdFusion's WDDX functionality:

  • <cfcomponent
  • output="false"
  • hint="This the brain of the shortie and is used to persist data between application instances.">

  • <!---
  • Run pseudo constructor. Here is where we can set
  • up default data structures and data values.
  • --->

  • <!---
  • Create a private data structure to hold all instance
  • related data.
  • --->
  • <cfset VARIABLES.Instance = StructNew() />

  • <!---
  • We are going to be persisting this data via an XML
  • file that we write to disk. This is the file path
  • of the XML file. By default, we are going to store
  • the XML file in the same directory (but this can be
  • overridden in the INIT method).
  • --->
  • <cfset VARIABLES.Instance.DataFilePath = (
  • GetDirectoryFromPath( GetCurrentTemplatePath() ) &
  • "brain.xml"
  • ) />

  • <!---
  • This is the struct in which we are going to hold the
  • data. This is going to be the only form of data we
  • store. I am going to assume that this struct will
  • only hold simple value. I am not going to enforce this.
  • --->
  • <cfset VARIABLES.Instance.Data = StructNew() />



  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Returns an initialized brain instance.">

  • <!--- Define arguments. --->
  • <cfargument
  • name="DataFilePath"
  • type="string"
  • required="false"
  • default=""
  • />


  • <!---
  • Check to see if we were passed a new file path.
  • If so, then store this one as our XML file path.
  • --->
  • <cfif Len( ARGUMENTS.DataFilePath )>

  • <cfset VARIABLES.Instance.DataFilePath = ARGUMENTS.DataFilePath />

  • </cfif>


  • <!---
  • Now that we have a data file stored, we can read
  • in what ever data might already exists. Load the
  • persisted data.
  • --->
  • <cfset VARIABLES.LoadData() />

  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>


  • <cffunction
  • name="ClearData"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Clears the stored data.">

  • <!--- Clear the instance data. --->
  • <cfset StructClear( VARIABLES.Instance.Data ) />

  • <!--- Commit the cleared data to file. --->
  • <cfset THIS.CommitData() />

  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>


  • <cffunction
  • name="CommitData"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Commits the data to persited storage. This is a public access function so that the invoker can delay auto-committing during other methods.">

  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />

  • <!---
  • Convert the internal data structure to a WDDX
  • xml data structure. Again, not crazy about WDDX,
  • but it is easy for our purposes.
  • --->
  • <cfwddx
  • action="CFML2WDDX"
  • input="#VARIABLES.Instance.Data#"
  • output="LOCAL.FileData"
  • />

  • <!---
  • Write the data to the data file. We don't care
  • if we overwrite an existing data file or just
  • create a new one.
  • --->
  • <cffile
  • action="WRITE"
  • file="#VARIABLES.Instance.DataFilePath#"
  • output="#LOCAL.FileData#"
  • />

  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>


  • <cffunction
  • name="Get"
  • acces="public"
  • returntype="any"
  • output="false"
  • hint="Gets a stored value at the given key. If no value is found, returns an empty string.">

  • <!--- Define arguments. --->
  • <cfargument
  • name="Property"
  • type="string"
  • required="true"
  • />

  • <!---
  • Check to see if we have a value stored at the
  • given property key. If we cannot find the given key,
  • then just return the empty string. Think about a
  • brain - if you can't remember something, your head
  • doesn't explode - ok maybe not, but that's how
  • this is going to work.
  • --->
  • <cfif StructKeyExists(
  • VARIABLES.Instance.Data,
  • ARGUMENTS.Property
  • )>

  • <!--- Return the matching property value. --->
  • <cfreturn VARIABLES.Instance.Data[ ARGUMENTS.Property ] />

  • <cfelse>

  • <!---
  • No value was found. Return our default
  • value which is the empty string.
  • --->
  • <cfreturn "" />

  • </cfif>
  • </cffunction>


  • <cffunction
  • name="LoadData"
  • access="private"
  • returntype="void"
  • output="false"
  • hint="Tries to load the XML file into the data struct. If the data file does not exist, no action is taken.">

  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />

  • <!---
  • Check to see if our data file eixsts. If it
  • does not, then we might have not done anything
  • yet (ignore action).
  • --->
  • <cfif FileExists( VARIABLES.Instance.DataFilePath )>

  • <!--- Read in the XML data file. --->
  • <cffile
  • action="READ"
  • file="#VARIABLES.Instance.DataFilePath#"
  • variable="LOCAL.FileData"
  • />

  • <!---
  • Convert the XML data to our internal struct
  • data using a WDDX conversion. I am not crazy
  • about this WDDX conversion, but it's easy.
  • --->
  • <cfwddx
  • action="WDDX2CFML"
  • input="#LOCAL.FileData#"
  • output="VARIABLES.Instance.Data"
  • />

  • </cfif>

  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>


  • <cffunction
  • name="Set"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Sets the given property value at the given key.">

  • <!--- Define arguments. --->
  • <cfargument
  • name="Property"
  • type="string"
  • required="true"
  • hint="The key used to access the property value."
  • />

  • <cfargument
  • name="Value"
  • type="any"
  • required="true"
  • hint="The property value. This should be a simple value, but I am not enforcing that."
  • />

  • <cfargument
  • name="CommitData"
  • type="boolean"
  • required="false"
  • default="true"
  • hint="If the invoker is planning on calling serveral sets in a row, they might choose to delay commiting and then commit manually after the last Set method call."
  • />

  • <!--- Store the given value. --->
  • <cfset VARIABLES.Instance.Data[ ARGUMENTS.Property ] = ARGUMENTS.Value />


  • <!---
  • Check to see if we are committing the data. If we
  • are not, then skip the commit and leave it up
  • to the invoker.
  • --->
  • <cfif ARGUMENTS.CommitData>

  • <!--- Invoke the data commit. --->
  • <cfset THIS.CommitData() />

  • </cfif>


  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>

  • </cfcomponent>

I decided to build the Brain.cfc as a separate component from the Shortie.cfc (up next) because I really wanted to stress the separation of concerns. This Shortie itself should not really have to know how to store data over time. And think about it in the real world; do we know how data gets stored? Sort of? Not really? We just send nerve impulses to our brain and our brain just kind of stores and retrieves it via some sort of crazy awesome black magic.

By building the Brain.cfc separately, it allowed me to unit test the brain functionality before I even started working on the Shortie.cfc. Also, by breaking the system down into smaller parts, it made it easier for me to think about.

Now that we have our interactions defined and our model for data persistence, it's time to actually build something to interact with. The Shortie.cfc ColdFusion component is our representation of the "Monster" (the original contest idea was Monster Maker). The Shortie.cfc was, by far, the most complicated of the involved components. Not so much because the code was crazy - more so because people are irrational and crazy and random and how the hell to you model that EVEN if you have good Interaction definitions?

The Shortie's Interact() method takes an instance of the Interaction.cfc ColdFusion component. It then grabs the properties out of that instance and updates its own internal state. To try to model the real world, this method if chock full of "randomness". Every now and then, it will totally flip the properties so that something that should have given you energy make you tired and something that should have made you feel loved will fill you with anger.

The other difficult thing about this component was that I didn't want it to be in real time. Meaning, like dog years, I wanted the Shortie's time to be smaller but proportional to real world time. For this application, one day of real world time is the equivalent of 120 days of Shortie time (one Shortie hour = 30 seconds real world time).

Aside from the time scale itself, incrementing time was hard to figure out. When you increment time, you are doing more than just changing a single variable; time wears on people - it makes them hungry, it sucks energy. And, if interactions themselves take time, then incrementing the Shortie time is going to be coupled with possibility of interactions.

Here is the Shortie.cfc code:

  • <cfcomponent
  • output="false"
  • hint="This is your shortie; treat her well.">

  • <!---
  • Run pseudo constructor. Here is where we can set
  • up default data structures and data values.
  • --->

  • <!---
  • Create a private data structure to hold all instance
  • related data.
  • --->
  • <cfset VARIABLES.Instance = StructNew() />

  • <!---
  • This is the brain that the shortie will use to persist
  • data. This is a place holder. The actual data fill be
  • passed in during construction.
  • --->
  • <cfset VARIABLES.Instance.Brain = "" />


  • <!---
  • These are the base line values on the shortie
  • properties. These are the values that will be updated
  • by the interactions. These will be used to calculate
  • the overall mood and health of your shortie. A positive
  • number indicates "more" of that property where as a
  • negative value indicates "less" of that property.

  • For a more in-depth explanation of what these
  • properties mean, check out the Interaction.cfc code.
  • --->
  • <cfset VARIABLES.Instance.Anger = 0 />
  • <cfset VARIABLES.Instance.Energy = 0 />
  • <cfset VARIABLES.Instance.Hunger = 0 />
  • <cfset VARIABLES.Instance.Happiness = 0 />
  • <cfset VARIABLES.Instance.Love = 0 />


  • <!---
  • This is the current time in which the Shortie exists.
  • This is not necessarily reflective of the real world
  • time and it might be overriden by the Brain.
  • --->
  • <cfset VARIABLES.Instance.Time = Now() />

  • <!---
  • This is the real world time. We need to keep track of
  • this so we can later on figure out how much time has
  • passed since we last calculated the time.
  • --->
  • <cfset VARIABLES.Instance.RealTime = Now() />

  • <!---
  • This is the time scale to days of real world time
  • to shortie world time. Hour shortie hours are going
  • to equal 30 real world seconds. Since our time
  • difference are going to be calculated in fractions
  • of a day, we need to figure out how to multiple our
  • day fractions to get shortie hours.

  • H = Hour
  • D = Day
  • SH = Shortie Hour
  • SD = Shortie Day

  • 1 H == 60 * 2 SH
  • 24 H == 60 * 2 * 24 SH
  • 1 D == 60 * 2 * 24 / 24 SD
  • 1 D == 60 * 2 SD
  • 1 D == 120 SD
  • --->
  • <cfset VARIABLES.Instance.DayScale = 120 />

  • <!---
  • This is the time at which the next interaction will
  • be available. This is to prevent you from abusing your
  • shortie and to take into account that each interaction
  • does require some time to enact. For now, we will just
  • set it to now(), but that can be overridden.
  • --->
  • <cfset VARIABLES.Instance.NextInteractionTime = Now() />


  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Returns an initialized shortie instance.">

  • <!--- Define arguments. --->
  • <cfargument
  • name="Brain"
  • type="any"
  • required="true"
  • />

  • <!---
  • Store the passed in brain object. The brain object
  • should already have the persisted data loaded into
  • it, so no need to alter the Brain in any way.
  • --->
  • <cfset VARIABLES.Instance.Brain = ARGUMENTS.Brain />

  • <!--- Load the brain data. --->
  • <cfset VARIABLES.LoadData() />

  • <!---
  • Adjust time to get the Shortie's time scale the
  • current time (if time has elapsed since that last
  • time the Application was fired up).
  • --->
  • <cfset THIS.AdjustTime() />


  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>


  • <cffunction
  • name="AdjustTime"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="This adjusts the shortie time to make sure it scales to regular world time increments.">

  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />

  • <!--- Get the current time. --->
  • <cfset LOCAL.Now = Now() />

  • <!---
  • Get the difference in real world time that has
  • elapsed since our last time adjustment. This will
  • give us the fraction of days that have passed.
  • Then, we need to multiple that by are day factor to
  • get how many shortie hours have passed.
  • --->
  • <cfset LOCAL.DayDiff = (
  • (LOCAL.Now - VARIABLES.Instance.RealTime) *
  • VARIABLES.Instance.DayScale
  • ) />

  • <!---
  • This is the actual date that we need to adjust
  • our internal time value to.
  • --->
  • <cfset LOCAL.TargetTime = (
  • VARIABLES.Instance.Time +
  • LOCAL.DayDiff
  • ) />

  • <!---
  • Increment the time. This will take care of
  • actually updating the internal time value. We want
  • in increase the time only that is required.
  • --->
  • <cfset VARIABLES.IncrementTime(
  • (LOCAL.TargetTime - VARIABLES.Instance.Time)
  • ) />

  • <!---
  • Update the real time so that our date-diffs don't
  • lose track of our scaled time.
  • --->
  • <cfset VARIABLES.Instance.RealTime = LOCAL.Now />

  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>


  • <cffunction
  • name="CommitData"
  • access="private"
  • returntype="void"
  • output="false"
  • hint="This commits short term memory to the brain.">

  • <!--- Commit data to brain. --->
  • <cfset VARIABLES.Instance.Brain.Set(
  • Property = "Anger",
  • Value = VARIABLES.Instance.Anger,
  • CommitData = false
  • ) />

  • <cfset VARIABLES.Instance.Brain.Set(
  • Property = "Energy",
  • Value = VARIABLES.Instance.Energy,
  • CommitData = false
  • ) />

  • <cfset VARIABLES.Instance.Brain.Set(
  • Property = "Hunger",
  • Value = VARIABLES.Instance.Hunger,
  • CommitData = false
  • ) />

  • <cfset VARIABLES.Instance.Brain.Set(
  • Property = "Happiness",
  • Value = VARIABLES.Instance.Happiness,
  • CommitData = false
  • ) />

  • <cfset VARIABLES.Instance.Brain.Set(
  • Property = "Love",
  • Value = VARIABLES.Instance.Love
  • ) />

  • <cfset VARIABLES.Instance.Brain.Set(
  • Property = "Time",
  • Value = VARIABLES.Instance.Time
  • ) />

  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>


  • <cffunction
  • name="GetNextInteractionTime"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Gets the time at which the Shortie can next be interacted with.">

  • <cfreturn VARIABLES.Instance.NextInteractionTime />
  • </cffunction>


  • <cffunction
  • name="GetProperties"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="Returns the 'mental' properties of the shortie.">

  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />

  • <!--- Store properties locally. --->
  • <cfset LOCAL.Anger = VARIABLES.Instance.Anger />
  • <cfset LOCAL.Energy = VARIABLES.Instance.Energy />
  • <cfset LOCAL.Hunger = VARIABLES.Instance.Hunger />
  • <cfset LOCAL.Happiness = VARIABLES.Instance.Happiness />
  • <cfset LOCAL.Love = VARIABLES.Instance.Love />

  • <!--- Return local property copy. --->
  • <cfreturn LOCAL />
  • </cffunction>


  • <cffunction
  • name="GetTime"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="Returns the shortie time (internal time model).">

  • <!---
  • Return the time as a string with full date / time
  • stamp formatting.
  • --->
  • <cfreturn (
  • DateFormat(
  • VARIABLES.Instance.Time,
  • "mm/dd/yyyy "
  • ) &
  • TimeFormat(
  • VARIABLES.Instance.Time,
  • "hh:mm TT"
  • )
  • ) />
  • </cffunction>


  • <cffunction
  • name="IncrementTime"
  • access="private"
  • returntype="void"
  • output="false"
  • hint="This increments time.">

  • <!--- Define arguments. --->
  • <cfargument
  • name="TimeSpan"
  • type="numeric"
  • required="false"
  • default="#CreateTimeSpan( 0, 1, 0, 0 )#"
  • hint="The time to increase for this iteration (defaults to an hour)."
  • />


  • <!--- Increment the internal time. --->
  • <cfset VARIABLES.Instance.Time = (
  • VARIABLES.Instance.Time +
  • ARGUMENTS.TimeSpan
  • ) />

  • <!---
  • Every time we increment the time, it takes a
  • toll on the shortie. Time makes you tired, it
  • makes you hungry. This is on top of any
  • interactions that have just taken place. For every
  • four hours that pass, the shortie loses 1 energy
  • units (scaled to our time span).
  • --->
  • <cfset VARIABLES.Instance.Energy = (
  • VARIABLES.Instance.Energy -
  • (
  • 1 *
  • ARGUMENTS.TimeSpan / CreateTimeSpan( 0, 4, 0, 0 )
  • )
  • ) />

  • <cfset VARIABLES.Instance.Hunger = (
  • VARIABLES.Instance.Hunger +
  • (
  • 1 *
  • ARGUMENTS.TimeSpan / CreateTimeSpan( 0, 4, 0, 0 )
  • )
  • ) />


  • <!--- Commit the data changes to the brain. --->
  • <cfset VARIABLES.CommitData() />

  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>


  • <cffunction
  • name="Interact"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="This is how you can pass in an interaction. This will update the internal state.">

  • <!--- Define arguments. --->
  • <cfargument
  • name="Interaction"
  • type="any"
  • required="true"
  • hint="This is an interaction that must implement the interaction interface."
  • />

  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />

  • <!---
  • Before we do any interaction, we have to adjust
  • the time. We might not even be able to perform an
  • interaction and we will need to adjust time in
  • order to figure that out.
  • --->
  • <cfset THIS.AdjustTime() />


  • <!---
  • Now that we have the proper time, check to see if
  • we can perform an interaction.
  • --->
  • <cfif (VARIABLES.Instance.NextInteractionTime GT VARIABLES.Instance.Time)>

  • <!---
  • We have not yet reached our time of next
  • possible interaction. Just return out of the
  • interaction method call.
  • --->
  • <cfreturn />

  • </cfif>


  • <!---
  • ASSERT: At this point, we know that we have waited
  • long enough to perform another interaction.
  • --->


  • <!--- Get the time required for this interaction. --->
  • <cfset LOCAL.TimeRequired = ARGUMENTS.Interaction.GetTimeRequired() />

  • <!---
  • This interactoin will "lock" the shortie's ability
  • to interact for a given amount of time. Keep track
  • of that time by adding the time required to the
  • current shortie time.
  • --->
  • <cfset VARIABLES.Instance.NextInteractionTime = (
  • VARIABLES.Instance.Time +
  • LOCAL.TimeRequired
  • ) />


  • <!---
  • With all interactions, there is some degree of
  • error and randomness. We are going to produce
  • this randomness and we are going to do it on
  • several levels.

  • Create a general multiplier. We are going to
  • default this to one, but one time out of 30
  • (statistically) this is going to be totally
  • haywire and flip.
  • --->
  • <cfif (RandRange( 1, 30 ) EQ 15)>

  • <!---
  • This interaction is NOT going to make any
  • sense. But that's ok - that is life sometimes.
  • --->
  • <cfset LOCAL.Multiplier = -1 />

  • <cfelse>

  • <!--- This is a standard interaction. --->
  • <cfset LOCAL.Multiplier = 1 />

  • </cfif>


  • <!---
  • ASSERT: At this time, we have determines if this
  • going to be a completely non-sensical interaction
  • or a normal interaction. Now, we can determine the
  • minor randomness.
  • --->


  • <!---
  • Create an interum struct that will hold the deltas
  • for our internal properties.
  • --->
  • <cfset LOCAL.Delta = StructNew() />

  • <!---
  • Get the delta values from the interaction. When
  • getting the base data, add some minor noise to
  • each value and multiple the random multiplier.
  • --->
  • <cfset LOCAL.Delta.Anger = (
  • (
  • ARGUMENTS.Interaction.GetAnger() *
  • LOCAL.Multiplier
  • ) +
  • RandRange( -1, 1 )
  • ) />

  • <cfset LOCAL.Delta.Energy = (
  • (
  • ARGUMENTS.Interaction.GetEnergy() *
  • LOCAL.Multiplier
  • ) +
  • RandRange( -1, 1 )
  • ) />

  • <cfset LOCAL.Delta.Hunger = (
  • (
  • ARGUMENTS.Interaction.GetHunger() *
  • LOCAL.Multiplier
  • ) +
  • RandRange( -1, 1 )
  • ) />

  • <cfset LOCAL.Delta.Happiness = (
  • (
  • ARGUMENTS.Interaction.GetHappiness() *
  • LOCAL.Multiplier
  • ) +
  • RandRange( -1, 1 )
  • ) />

  • <cfset LOCAL.Delta.Love = (
  • (
  • ARGUMENTS.Interaction.GetLove() *
  • LOCAL.Multiplier
  • ) +
  • RandRange( -1, 1 )
  • ) />

  • <cfset LOCAL.Delta.IsPositive = ARGUMENTS.Interaction.GetIsPositive() />


  • <!---
  • Now that we have the delta values with random
  • noise, update the internal data model.
  • --->
  • <cfset VARIABLES.Instance.Anger = (
  • VARIABLES.Instance.Anger +
  • LOCAL.Delta.Anger
  • ) />

  • <cfset VARIABLES.Instance.Energy = (
  • VARIABLES.Instance.Energy +
  • LOCAL.Delta.Energy
  • ) />

  • <cfset VARIABLES.Instance.Hunger = (
  • VARIABLES.Instance.Hunger +
  • LOCAL.Delta.Hunger
  • ) />

  • <cfset VARIABLES.Instance.Happiness = (
  • VARIABLES.Instance.Happiness +
  • LOCAL.Delta.Happiness
  • ) />

  • <cfset VARIABLES.Instance.Love = (
  • VARIABLES.Instance.Love +
  • LOCAL.Delta.Love
  • ) />


  • <!--- Commit the data changes to the brain. --->
  • <cfset VARIABLES.CommitData() />

  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>


  • <cffunction
  • name="LoadData"
  • access="private"
  • returntype="void"
  • output="false"
  • hint="Loads the property data from the brain (persisted data).">

  • <!---
  • From the brain, set the initial values for our
  • internal state properties. Since we might not have
  • all of these value set, be sure to Val() each
  • value that gets returned.
  • --->
  • <cfset VARIABLES.Instance.Anger = Val(
  • VARIABLES.Instance.Brain.Get( "Anger" )
  • ) />

  • <cfset VARIABLES.Instance.Energy = Val(
  • VARIABLES.Instance.Brain.Get( "Energy" )
  • ) />

  • <cfset VARIABLES.Instance.Hunger = Val(
  • VARIABLES.Instance.Brain.Get( "Hunger" )
  • ) />

  • <cfset VARIABLES.Instance.Happiness = Val(
  • VARIABLES.Instance.Brain.Get( "Happiness" )
  • ) />

  • <cfset VARIABLES.Instance.Love = Val(
  • VARIABLES.Instance.Brain.Get( "Love" )
  • ) />


  • <!--- Get the shortie's scaled time. --->
  • <cfset VARIABLES.Instance.Time = Val(
  • VARIABLES.Instance.Brain.Get( "Time" )
  • ) />

  • <!---
  • Make sure our time is valid. If for some reason
  • we are not starting out with a valid time, then
  • set it equal to the real world time.
  • --->
  • <cfif NOT VARIABLES.Instance.Time>
  • <cfset VARIABLES.Instance.Time = VARIABLES.Instance.RealTime />
  • </cfif>


  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>


  • <cffunction
  • name="Labotomize"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Clears the brain data, thereby, resenting the properties.">

  • <!--- Clear the brain data. --->
  • <cfset VARIABLES.Instance.Brain.ClearData() />

  • <!--- Load the blank data into the shorti. --->
  • <cfset VARIABLES.LoadData() />

  • <!--- Clear next interaction time. --->
  • <cfset VARIABLES.Instance.NextInteractionTime = Now() />

  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>

  • </cfcomponent>

Notice that the Shortie takes an instance of the Brain.cfc in its Init method. I think this is a good application of composing one object inside another. But again, I am learning this stuff as I go.

The one final hurdle to conquer was not letting interaction after interaction being fired. We know that interactions take time (one of their properties). Using this, the Shortie keeps track of a "Next Interaction Time". Once an interaction gets sent to the Shortie, the time required for the interaction is added to the Shortie's current time. Then, if any more interactions get passed in (Interact() method) before the Shortie's time is greater than the next interaction time, they are simply ignored.

The one final piece of the back end is the Application.cfc which ties it all together. The Application.cfc ColdFusion component here is very simple. All it does really is create an instance of the Brain.cfc and injects it into the instance of the Shortie.cfc and then caches it in the APPLICATION scope.

  • <cfcomponent
  • output="true"
  • hint="Handles application definition and application level events.">

  • <!---
  • Run pseudo constructor. Set up the Application
  • properties and session management.
  • --->
  • <cfset THIS.ApplicationName = "KinkySolutions - My Shortie" />
  • <cfset THIS.ApplicationTimeOut = CreateTimeSpan(
  • 2, <!--- Days. --->
  • 0, <!--- Hours. --->
  • 0, <!--- Minutes. --->
  • 0 <!--- Seconds. --->
  • ) />

  • <!--- Turn of session management. --->
  • <cfset THIS.SessionManagement = false />
  • <cfset THIS.SetClientCookies = false />


  • <!--- Set page request settings. --->
  • <cfsetting
  • requesttimeout="20"
  • showdebugoutput="false"
  • />


  • <cffunction
  • name="OnApplicationStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="Runs when application starts or is manually reset.">

  • <!---
  • Even though the OnApplicationStart() method is
  • meant to be single threaded, since we might be
  • calling it manually, we have to be sure to
  • force the single thread - the act of calling
  • this method alone will not accomplish that.
  • --->
  • <cflock
  • scope="APPLICATION"
  • type="EXCLUSIVE"
  • timeout="10">

  • <!--- Clear the application data. --->
  • <cfset StructClear( APPLICATION ) />

  • <!---
  • Create an instance of the user defined
  • functions library, cached in the application.
  • --->
  • <cfset APPLICATION.UDFLib = CreateObject(
  • "component",
  • "UDFLib"
  • ).Init()
  • />

  • <!---
  • Create a shortie and cache her in the
  • APPLICATION scope. We only need one per
  • application and we need her to persist
  • across page requests. When creating the
  • shortie, we need to initialize her with
  • a brain that knows where to store its
  • data (the data file path).
  • --->
  • <cfset APPLICATION.Shortie = CreateObject(
  • "component", "Shortie"
  • ).Init(
  • CreateObject(
  • "component", "Brain"
  • ).Init(
  • ExpandPath( "./brain_data.xml" )
  • )
  • ) />

  • </cflock>

  • <!--- Return out. --->
  • <cfreturn true />
  • </cffunction>


  • <cffunction
  • name="OnRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="Fires when prior to page processing.">

  • <!--- Define arguments. --->
  • <cfargument
  • name="TargetPage"
  • type="string"
  • required="true"
  • />


  • <!---
  • Check to see if we have the manual appplication
  • reset flag in the URL (we don't care about the
  • actual value, just the key existance).
  • --->
  • <cfif StructKeyExists( URL, "reset" )>

  • <!---
  • Reset the application via the OnApplicationStart()
  • event method. This method will take care of it
  • own thread-safety concerns.
  • --->
  • <cfset THIS.OnApplicationStart() />

  • </cfif>

  • <!--- Return out. --->
  • <cfreturn true />
  • </cffunction>

  • </cfcomponent>

At this point, the major back end of the My Shortie application is complete. We have interactions, we have data persistence, we have a "Monster" entity that can update and report its internal state (anger, happiness, etc.). Really, the only thing left to do is build an interface that can hook into it and let the end user trigger interactions and view details.

I will very quickly just show you some of the front end code, but I feel that this is not really the primary objective of the application (CFC usage), so I will not go into in depth.

The application has one main public page, index.cfm:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>My Shortie - Ray Camden's Beginner Contest</title>
  •  
  • <link rel="stylesheet" type="text/css" href="styles.css"></link>
  • <script type="text/javascript" src="jquery-latest.pack.js"></script>
  • <script type="text/javascript" src="script.js"></script>
  • </head>
  • <body>
  •  
  • <div id="console">
  •  
  • <div id="shortie">
  • <br />
  • </div>
  •  
  • <p id="time">
  • Time:
  • <span id="timevalue"></span>
  • <br />
  •  
  • <span id="delay" class="delay">
  • Delay:
  • <span id="delayvalue"></span>
  • </span>
  • </p>
  •  
  • <p rel="anger" class="property p1"></p>
  • <p rel="energy" class="property p2"></p>
  • <p rel="happiness" class="property p3"></p>
  • <p rel="hunger" class="property p4"></p>
  • <p rel="love" class="property p5"></p>
  •  
  • <a
  • href="javascript:void(0);"
  • rel="ILoveYou"
  • class="interaction i1"
  • >I Love You</a>
  •  
  • <a
  • href="javascript:void(0);"
  • rel="SlapAround"
  • class="interaction i2"
  • >Slap Around</a>
  •  
  • <a
  • href="javascript:void(0);"
  • rel="TakeToDinner"
  • class="interaction i3"
  • >Take To Dinner</a>
  •  
  • <a
  • href="javascript:void(0);"
  • rel="TurnTrick"
  • class="interaction i4"
  • >Turn Trick</a>
  •  
  • <a
  • href="javascript:void(0);"
  • rel="Sleep"
  • class="interaction i5"
  • >Sleep</a>
  •  
  • <a
  • href="javascript:void(0);"
  • rel="Labotomize"
  • class="interaction i8"
  • >Labotomize</a>
  •  
  • </div>
  •  
  • </body>
  • </html>

Notice that the code is VERY simple. I use jQuery to update the DOM after it has loaded to hook up the actual interaction events and timed events (getting properties via AJAX, updating the shortie's picture).

The Javascript for this application - thanks to some basic AJAX and the power of jQuery - was quite easy. In addition to the jQuery core Javascript file, here was my scripts.js file:

  • // This is an AJAX request. It takes a structure of URL parameters as
  • // well as an optional call back method.
  • function APIRequest( objParams, fnCallBack ){
  • var objConnection = null;
  • var strUrl = "api.cfm?";
  • var strQueryString = "";
  • var strKey = "";
  •  
  • // The the various forms of creating the HTTP request. IE
  • // and Mozilla do this differently. Use the try / catch
  • // to figure out which one's won't break.
  • try {
  •  
  • objConnection = new ActiveXObject("Microsoft.XMLHTTP");
  •  
  • } catch( objMSError ) {
  •  
  • try {
  •  
  • objConnection = new XMLHttpRequest();
  •  
  • } catch (objMozError) {
  •  
  • alert("Your browser cannot handle this XMLHttpRequest technology.");
  •  
  • }
  •  
  • }
  •  
  •  
  • // Check to make sure we have a connection object.
  • if (objConnection){
  •  
  • // Loop over the URL params object and add the
  • // key-value pairs to the query string.
  • for (strKey in objParams){
  •  
  • // Be sure to escape the value for the URL.
  • strUrl += (
  • "&" +
  • strKey +
  • "=" +
  • encodeURIComponent( objParams[ strKey ] )
  • );
  •  
  • }
  •  
  • // prompt( "url", strUrl );
  • // return;
  •  
  • // Open the connection. We are going to use a "Get"
  • // approach. That will put all of our values into the
  • // URL, not the FORM scope.
  • objConnection.open(
  • "get",
  • strUrl
  • );
  •  
  •  
  • // Check to see if we have a call back method. If we do,
  • // then set up the state change method handler. This is
  • // designed to send the evaluated JSON result back to
  • // the call back method.
  • if (fnCallBack){
  •  
  • // Define the ready state change method.
  • objConnection.onreadystatechange = function(){
  • var objResponse = objConnection;
  •  
  • // Check to see if we have a response.
  • if (objResponse.readyState == 4){
  •  
  • // Evaluate the JSON response and send it to the
  • // given call back method.
  • fnCallBack(
  • eval( "(" + objResponse.responseText + ")" )
  • );
  •  
  • }
  •  
  • };
  •  
  • }
  •  
  •  
  • // Send the AJAX request now that we have our connection
  • // set up and the handlers defined.
  • objConnection.send(
  • null
  • );
  •  
  • }
  •  
  • }
  •  
  •  
  •  
  • function CallAPI( strInteraction ){
  •  
  • // Check to see if we have an APIInterval
  • // value that we should clear.
  • if (window.APIInterval){
  •  
  • // Delete this interval since we are about to
  • // launch another API request.
  • clearTimeout(
  • window.APIInterval
  • );
  •  
  • }
  •  
  • // Launch a new API request to update the Shortie and
  • // get the updated property data returned.
  • APIRequest(
  • {
  • interaction: (strInteraction ? strInteraction : "")
  • },
  •  
  • function( objData ){
  •  
  • // Update the property values.
  • $( "p.property" ).each(
  • function( intI ){
  • var objProp = $( this );
  •  
  • objProp.text(
  • objData[ objProp.attr( "rel" ) ]
  • );
  • }
  • );
  •  
  • // Update the time values.
  • $( "#timevalue" ).text( objData.time );
  •  
  • // Update the delay value.
  • $( "#delayvalue" ).text(
  • (
  • (parseInt( objData.delay ) > 0) ?
  • (objData.delay + " minutes") :
  • "No Delay"
  • )
  • );
  •  
  • // Set a timeout to call another API request.
  • window.APIInterval = setTimeout(
  • CallAPI,
  • (5 * 1000)
  • );
  •  
  • }
  • );
  •  
  • }
  •  
  •  
  • function UpdateShortieImage(){
  • var jShortie = $( "#shortie" );
  • var intValue = ((parseInt( Math.random() * 100 ) % 17) + 1);
  • var imgShortie = new Image();
  •  
  • // Format the value to make sure it is a two digit
  • // value (for our file naming convention).
  • intValue = (
  • (intValue.toString().length == 1) ?
  • ("0" + intValue) :
  • intValue
  • );
  •  
  • // Set the image onload handler.
  • $( imgShortie ).load(
  • function( obj ){
  •  
  • // Fade out the current shortie.
  • jShortie.fadeOut(
  • function(){
  •  
  • // Set the background image of the
  • // shortie div to the newly loaded
  • // image.
  • jShortie.css(
  • "background-image",
  • "url( '" + imgShortie.src + "' )"
  • );
  •  
  • // Fade the shorti back in.
  • jShortie.fadeIn(
  • function(){
  •  
  • // Set the timeout to update the
  • // shortie image.
  • setTimeout(
  • UpdateShortieImage,
  • (8 * 1000)
  • );
  •  
  • }
  • );
  • }
  • );
  •  
  • }
  • );
  •  
  • // Set the image source. This will trigger the
  • // onLoad handler set above.
  • imgShortie.src = ("images/" + intValue + ".jpg");
  • }
  •  
  •  
  • // Run this code when the document loads.
  • $(
  • function(){
  •  
  • // Set up all the interaction links.
  • $( "a.interaction" ).click(
  • function( objEvent ){
  • CallAPI( this.rel );
  • }
  • );
  •  
  • // Update the form with an API call.
  • CallAPI();
  •  
  • // Update the shortie image.
  • UpdateShortieImage();
  • }
  • );

The final pieces was the tying of the interface to the back end. This takes place through my api.cfm ColdFusion template. This takes possible interaction values, updates the Shortie instance that is cached in the APPLICATION scope and then returns the Shortie's properties using JSON.

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!--- Param the url variables. --->
  • <cfparam
  • name="URL.interaction"
  • type="string"
  • default=""
  • />
  •  
  •  
  • <!---
  • Check to see if there was a valid interation
  • sent. If there was one, then store the name
  • of the target CFC back into the URL.
  • --->
  • <cfswitch expression="#URL.interaction#">
  • <cfcase value="ILoveYou">
  • <cfset URL.interaction = "ILoveYou" />
  • </cfcase>
  • <cfcase value="SlapAround">
  • <cfset URL.interaction = "SlapAround" />
  • </cfcase>
  • <cfcase value="Sleep">


    Reader Comments

    May 16, 2007 at 10:53 AM // reply »
    1 Comments

    Wow. I wish I would have seen this before I wrote mine! :) A whole new way of thinking about it. Mine seems so.... over-thought now. Like a complicated system of pulleys and levers. But I guess, that's why I am still a newb. Thanks for sharing!

    And, the "Shortie" is brilliant. Nice concept there. Completely great and too funny. Turn Trick is classic.

    - Nick


    May 16, 2007 at 11:00 AM // reply »
    11,247 Comments

    @Nick,

    We are all learning as we go. Most components that I make are one level deep - meaning, it is rare that I have components that have references to other components. This was really fun to think about.

    But even more than the components, it was just getting an understanding of how the heck to even model this kind of interaction. I walked around for like 3 days (not continuously) going over interactions and "properties" in my mind. It was driving me mad.

    To help solve it, I just kept trying to break it down into smaller problems. So like, look at the Interaction.cfc. I made that structure before I had ANY idea how it was going to be used. In fact, if look, I have a property for the name of the interaction... never gets used anywhere. All I knew was that it resulted in properties and that those were going to be used some how at some time.

    Once that was done, I could forget about it and move on to the next sub problem. All in all, what I thought would be a relatively easy task became much more complicated that I thought. I have to respect the Tomagachi people! There must be some pretty cool algorithms in those tiny cubes.


    May 16, 2007 at 6:45 PM // reply »
    76 Comments

    Nice work mate - one question though: I have never used SUPER.Init()
    How exactly does it work?


    May 17, 2007 at 7:22 AM // reply »
    11,247 Comments

    @Shuns,

    SUPER is a pointer (not sure what else to call it) the base class that is be extended by the current class. So for instance, for all the concrete interaction classes, the SUPER class is the Interaction.cfc that they are extending.

    So, when I say SUPER.Init(), it is calling the Init() method as it is defined by Interaction.cfc... then the code continues to run and runs the Init() method for the current class.


    May 17, 2007 at 8:25 PM // reply »
    76 Comments

    So the pointer the parent that is being extended - SUPER always exists or did you create it?


    May 18, 2007 at 7:36 AM // reply »
    11,247 Comments

    I think SUPER only exists in components that are extending other components (meaning it won't be there for stand-along CFCs). But, for those that do extend, it is always there.

    Also, it is not scoped, as far as I can remember. Meaning, you can't do THIS.SUPER or VARIABLES.SUPER; it stands alone.... although, now that I say that, I am not sure if I ever tested "VARIABLES.SUPER".


    May 20, 2007 at 6:12 PM // reply »
    76 Comments

    OK thanks.


    May 31, 2007 at 6:28 PM // reply »
    5 Comments

    Just a note...

    EVERY CFC that has the <cfcomponent> extends another

    ....you can find the one they extend by default here: "WEB-INF\cftags\component.cfc"

    try this:

    create a CFC called "bob" with this in it:

    <cfcomponent></cfcomponent>

    Put this code in a template and check what it extends:

    <cfset oBob = createObject("component","bob")>

    <cfdump var="#getMetaData(oBob)#">

    ...to not have a "super" remove the <cfcomponent> tags from your bob.cfc...


    May 31, 2007 at 7:25 PM // reply »
    11,247 Comments

    Dave is correct. Technically every CFC extends the CFC in the ColdFusion installation. But, I would highly suggest that NO ONE puts any code in that base CFC as that would make debugging for people not in "the know" an absolute nightmare.


    May 31, 2007 at 7:28 PM // reply »
    5 Comments

    I couldn't agree more!
    keeping away from messing with the "under the hood" workings of CF is something I'll always advocate!


    Jun 13, 2007 at 1:01 AM // reply »
    14 Comments

    wow, I'm super impressed. My brain actually broke part way through but what an impressive project.


    Post A Comment

    Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

    Please review the following issues:

    Author Name:


    Author Email:

    Author Website:

    Comment:

    Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools