Just a quick note to say that I'm releasing a new version of my Scribe plugin for Mango Blog. This version adds the ability to add a blurb of text to the pod content, so it's not just a title, text box and submit button. You can edit the text blurb on the Scribe settings page, and it's blank by default.
You can see it in use in my sidebar right now.
Enjoy!
- Plugin:
- Scribe
- Version:
- 1.2
- Requires:
- Mango Blog 1.3+
- Auto-install URL:
- http://fusiongrokker.com/get/Scribe
Posted in
ColdFusion |
Mango |
My projects
| No Responses Yet
November 23 2009
Pollyanna -- or Secret Santa, as I understand it's often referred to outside of PA -- is a great way to do a gift exchange in a large group while keeping costs down for those involved. Everyone is randomly assigned another member of the group to buy a gift for, and nobody knows who anyone else has been assigned. My wife and I do this with our friends, and to complicate things, most of us are married or at least dating, so we stipulate that you can't accept a drawing of your significant other. So when the hat gets passed to me, if I draw my wife's name, I just put it back and draw again. If we get stuck in a stalemate, we just start over.
We usually do our drawing during our Thanksgiving get-together, but this year we forgot. A quick Google search only showed one site that let you setup a Pollyanna, and it's got several requirements that don't meet my needs (#1 being that I don't want to make my friends sign up for a site just to do Pollyanna drawings); so I decided to write a quick ColdFusion script to do a random drawing that meets our extra rules and then automatically email everyone their gift recipient.
This is fairly basic, but it got the job done for us. If I were going to do anything else with it, I might add list support to the "noPick" option to allow listing multiple participants that can't be picked for the current person.
First, let's create a list of people who are participating, and their email addresses. In addition, put in each person's significant other in a field we'll call "noPick", so that we can stop them from picking that person.
<cfset variables.people = {
baz = {
email = "******@gmail.com",
noPick = ""
},
dave = {
email = "******@gmail.com",
noPick = ""
},
beth = {
email = "******@gmail.com",
noPick = "steve"
},
steve = {
email = "******@gmail.com",
noPick = "beth"
},
zach = {
email = "******@gmail.com",
noPick = "erin"
},
erin = {
email = "******@gmail.com",
noPick = "zach"
},
megan = {
email = "******@gmail.com",
noPick = "adam"
},
adam = {
email = "******@gmail.com",
noPick = "megan"
}
} />
Now, I know I'm going to want to randomly select from a list, so let's pull in listGetRandom from CFLib.
<cfscript>
/**
* Returns a random selection from a comma delimited list.
* Modified by Raymond Camden
*
* @param List The list to grab a random element from. (Required)
* @param Delimiter The list delimiter. Defaults to a comma. (Optional)
* @return Returns a random element from the list.
* @author Brad Breaux (bbreaux@blipz.com)
* @version 2, March 12, 2004
* http://cflib.org/udf/ListGetRandom
*/
function ListGetRandom(instring) {
var delim = ",";
var rnum = 0;
var r = '';
if(ArrayLen(Arguments) GTE 2) delim = Arguments[2];
if(listlen(instring) gt 0) {
rnum = randrange(1,listlen(instring,delim));
r = listgetat(instring,rnum,delim);
}
return r;
}
</cfscript>
Now, I know from first-hand experience that this can sometimes result in two or three complete restarts due to deadlock. When you're dealing with true randomness, there's no telling how many restarts you might need, so I know I want to write a recursive function so that it can restart itself as many times as needed if we detect a deadlock; and I want it to accept my original structure of people as input.
Inside that function, let's immediately create 2 lists of all of the participants: 1 to loop over for the person drawing from the hat, and 1 to represent the names in the hat. We'll also need a structure to hold all of the successful drawings, a variable to hold the current pick from the hat, and a variable to act as our loop index.
<cffunction name="getRandomPicks">
<cfargument name="people" type="Struct" />
<cfset var allPeople = lcase(structKeyList(arguments.people)) />
<cfset var remainingPeople = "#allPeople#" />
<cfset var picks = {} />
<cfset var thisPick = '' />
<cfset var thisPerson = '' />
Loop over all of our entrants - once each - and let them pick a name randomly from the pool of remaining names in the hat.
<cfloop list="#allPeople#" index="thisPerson">
<!--- pick a random person --->
<cfset thisPick = listGetRandom(remainingPeople) />
This is where we start to apply our constraints. Deadlock is when there's only one name left in the hat, and it's either the person themself, or their significant other. Check for deadlock, and if it's found, recurse.
<!--- detect deadlocks and start over when necessary --->
<cfif listLen(remainingPeople) eq 1>
<cfif remainingPeople eq arguments.people[thisPerson].nopick>
<cfreturn getRandomPicks(arguments.people) />
<cfelseif thisPerson eq thisPick>
<cfreturn getRandomPicks(arguments.people) />
</cfif>
</cfif>
There's actually one more deadlock scenario that I didn't run into in my testing or pick up on until I started writing this all out. Do you see it? (Answer is in the next paragraph, so don't jump ahead if you want to figure it out.)
It's when there are 2 names left in the hat and they are myself and my significant other. So, to account for that, let's rewrite the above code as follows. We'll loop over the remaining names in the hat, and the first time we find a name that doesn't violate the rules, we break out of the loop. If we make it all the way through the loop, we're in a deadlock, and need to restart.
<!--- if only person left to pick is the no-pick for this person, and/or themself, start over --->
<cfif listLen(remainingPeople) lte 2>
<cfset deadlock = true />
<cfloop list="#remainingPeople#" index="dlTest">
<cfif dlTest neq thisPerson and dlTest neq arguments.people[thisPerson].noPick>
<cfset deadlock = false />
<cfbreak />
</cfif>
</cfloop>
<cfif deadlock>
<cfreturn getRandomPicks(arguments.people) />
</cfif>
</cfif>
Since we're adding two new variables for the deadlock check, don't forget to go back and var scope them.
So now we know we're not in a deadlock, the only remaining rule is that as long as I've picked myself or my significant other, I need to put that name back and draw another one. Once I've drawn a suitable name, escape this loop.
<!--- we know there's more than 1 person left to pick, so pick randomly until we get a good one --->
<cfloop condition="true eq true">
<cfif thisPick eq arguments.people[thisPerson].nopick>
<cfset thisPick = listGetRandom(remainingPeople) />
<cfelseif thisPick eq thisPerson>
<cfset thisPick = listGetRandom(remainingPeople) />
<cfelse>
<cfbreak />
</cfif>
</cfloop>
Remove the selection from the pool of remaining people, save the selection so that it can be used later to send an email, and pass the hat.
<!--- pick is good, so remove pick from remaining people --->
<cfset remainingPeople = listDeleteAt(remainingPeople,listFindNoCase(remainingPeople,thisPick)) />
<!--- save the pick --->
<cfset picks[thisPerson] = thisPick />
</cfloop>
<cfreturn picks />
</cffunction>
That's the meat and potatoes of the selection. All that's left is to send the emails, and I'm sure you can figure out how to do that.
Posted in
ColdFusion |
Misc
| 1 Response
November 16 2009
Earlier today a friend asked me if I knew of a slick plugin for jQuery that makes it easy to show and hide form fields based on the selected value in a <select> box. Unfortunately, I don't -- I've always just done stuff like that manually. And that definitely starts to get hairy with large complex forms where you want to show and hide different combinations of fields.
A light bulb went off in my head though, and I quickly put together this example for him. Using a combination of jQuery and some cleverly placed CSS class names, you can very easily find the elements you want to show, and from there it's cake.
So what I've put together here is a form with a select box, and each option has a value from 0 to 4. The form also contains 5 paragraphs (could just as easily be table rows, divs, fieldsets, or a plethora of other things), with class names like "show0" and "show3" -- indicating which status of the drop-down they should be displayed for. Luckily for us, you can assign multiple classes to an object which makes it insanely simple to setup different combinations of visible elements.
In the example below, only a single paragraph is displayed for options 0, 1, and 4 -- but for options 2 and 3, two different paragraphs are shown. Selecting option 3 shows paragraphs 2 and 3 because they both have the class "show3", while selecting option 2 only shows paragraph 2 because while it does have the class "show2" none of the other paragraphs do.
Update: Thanks to Charlie Griefer for reminding me of some of the root functionality of jQuery that I mistakenly left out of this example. I'm updating my example according to his comments.
Here's a working demo for you.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#mySelect').change(function() {
$("#formContainer p:not('.alwaysShow')").hide();
$("#formContainer .show" + $('#mySelect').val()).show();
});
//optional:
$('#mySelect').change(); // simulate a change event to trigger the above
});
</script>
<div id="formContainer">
<form>
<p class="alwaysShow">
<select id="mySelect">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
</p>
<p class="show0">I am displayed for option 0</p>
<p class="show1">I am displayed for option 1</p>
<p class="show2 show3">I am displayed for option 2 and option 3</p>
<p class="show3 show4">I am displayed for option 3 and option 4</p>
<p class="show4">I am displayed for option 4</p>
</form>
</div>
Posted in
JavaScript |
jQuery
| 5 Responses
November 06 2009
Sometimes I find myself sending a query object from ColdFusion to Flex, for example, to bind to a DataGrid, and then I want to edit that query object and return it to ColdFusion to be saved in the database. Unfortunately, Queries are only a data type in ColdFusion. When sent to Flex, they get mapped to an ArrayCollection, and when you send that ArrayCollection back to ColdFusion, it isn't automatically converted back to a Query object (though lord knows that would be nice!).
It's certainly possible to just treat that ArrayCollection as an array of structures -- because that's what it is -- and loop over it to extract the data you want. Sure. Be my guest. But ColdFusion has so many great functions designed to make working with Queries easier. Wouldn't it be great if you could just convert that ArrayCollection back to a Query?
That's why I created this UDF, and I intend to submit it to CFLib once I know it's solid. I wanted to put this out to the community though to make sure it covers all of the necessary data types and doesn't have any errors. Can you see anything I've missed?
Below is the code for the function as well as a unit test. To see how it handles bad/unexpected data, you can do something wacky like change one of the values inside the structs to be a struct instead of the simple value it is now.
<cffunction name="arrayCollectionToQuery">
<cfargument name="arrayColl" type="Array" required="true" />
<cfset var qResult = '' />
<cfset var columnList = structKeyList(arrayColl[1]) />
<cfset var typeList = ''/>
<cfset var numericType = ''/>
<cfset var k = '' />
<cfset var i = 0 />
<cfloop collection="#arrayColl[1]#" item="k">
<cfif isNumeric(arrayColl[1][k])>
<!--- decimal or integer? --->
<cfset numericType = "integer">
<cfloop from="1" to="#arrayLen(arrayColl)#" index="i">
<cfif arrayColl[i][k] - fix(arrayColl[i][k]) gt 0>
<cfset numericType = "decimal" />
<cfbreak />
</cfif>
</cfloop>
<cfset typeList = listAppend(typeList, numericType) />
<cfelseif isSimpleValue(arrayColl[1][k])>
<cfset typeList = listAppend(typeList, 'varchar') />
<cfelseif isBoolean(arrayColl[1][k])>
<cfset typeList = listAppend(typeList, 'bit') />
<cfelseif isDate(arrayColl[1][k])>
<cfset typeList = listAppend(typeList, 'date') />
<cfelse>
<cfthrow message="Invalid ArrayCollection"
detail="All keys in your array collection must be of one of the following types: Numeric (Int or Float), String, Boolean, Date. The following key contains data that is not one of these types: `#k#`" />
</cfif>
</cfloop>
<cfset qResult = queryNew(columnList, typeList) />
<cfloop from="1" to="#arrayLen(arrayColl)#" index="i">
<cfset queryAddRow(qResult) />
<cfloop collection="#arrayColl[i]#" item="k">
<cfif not isNumeric(arrayColl[i][k]) and not isSimpleValue(arrayColl[i][k]) and not isBoolean(arrayColl[i][k]) and not isDate(arrayColl[i][k])>
<cfthrow message="Invalid ArrayCollection"
detail="All keys in your array collection must be of one of the following types: Numeric (Int or Float), String, Boolean, Date. The following key contains data that is not one of these types: `#k#`" />
</cfif>
<cfset querySetCell(qResult,k,arrayColl[i][k]) />
</cfloop>
</cfloop>
<cfreturn qResult />
</cffunction>
<cfset testData = [
{Num=2, String='fubar!', Bool=true, Date=CreateDate(2009,05,2)},
{Num=4.8, String='bufar!', Bool=false, Date=CreateDate(2009,06,1)},
{Num=6, String='futbol!', Bool=true, Date=CreateDate(2009,07,12)},
{Num=8, String='string!', Bool=false, Date=CreateDate(2009,08,9)},
{Num=10, String='data type!', Bool=true, Date=CreateDate(2009,09,18)},
{Num=12, String='yes', Bool=false, Date=CreateDate(2009,10,6)}
] />
<cfdump var="#testData#" label="data in">
<cfdump var="#arrayCollectionToQuery(testdata)#" label="data out">
Posted in
ColdFusion |
Flex
| No Responses Yet
November 05 2009