Tuesday, June 2, 2009

SMF, Simple Portal, and Cross-Domain Home Pages

SMF (Simple Machines Forum) is great software. One of the mods called Simple Portal is equally cool (if less complex probably). Another mod called Custom Pages (even more simple but robust) is as well. The 'custom pages' and 'portal blocks' can be html, php, or a variety of pre-set forum-based options. Between this forum software and these two mods, a person can do just about anything needed for a website as complex as you want to make it. This also means that you can pretty much click a button and make a logo and change the entire visual theme, because custom themes are also available for SMF. Redesign in 20 minutes.

For me, the problem is I use Coldfusion, so my forum is on a Linux php server (.info) while all my dynamic software is over on an NT cfml server (.com). I would really like to have more integration between these two, but it requires more coding-learning-curve than I have time for to get the forum and/or dojo's registration "integrated" in the two websites with different languages. I already spend hours I should be sleeping, working free for the cfml site. If anybody should know of something existing like this, I would trade money, sex or volunteer work for it. Maybe.

So I finally got the SMF+SP+CP combination set up, and the dilemma is:

How can I get my various cool-stuff 'display-only' from my coldfusion comserver, where all the action happens in my projects, to function or display "inside" my developing website in php on the infoserver?

Better yet, could it be possible to make it so people could browse my normally login-only coldfusion website from INSIDE the portal on the other server? Because I can control the permissions on that side, after all. They might get a nice "you need to register over here too" message if they want to add something but that would still make it way easier than having two completely separate worlds, which is the case right now.

These three php apps/mods (SMF+SP+CP) actually make this possible! Wow, the possibilities seem endless now!

For my first super-simple experiment, I made the index.cfm page on my coldfusion comserver work in both places. It sets a parameter called portal to default to 0 and uses IF. If someone arrives at the home domain there, they see some titles at the top, and a link to the forum at top right. Also the 'home' links are a bit different.

If they arrive at the portal home which is my PhP driven infoserver, they see that same included page but without the titles, icon, and different 'home' links, because the forum/portal already has a big title/nav. In my Simple Portal I created a block called 'welcome' which is Custom HTML, ignore permissions, show block on portal:

So what this proves to me is that I can make pages in coldfusion on one server, set them for certain parameters that will pull them directly into the framework in php on another server, and then control the access of those pages in the portal via permissions over there, so even if I normally would not want something to display outside the login, in this case if they're logged in over-there-instead that'll still work.

I think this will go a long way toward integration as well as to making more groovy stuff available to people who frequent the forum.


Monday, April 20, 2009

Multiple Records Insert (cfloop)

I was asked about this as a comment on a previous post on sortable lists. It used to be a question for me too so I thought I'd post it up top here.

Sometimes you need to take data in a form (or keep it in a table that is part or all form) that spans multiple records, so there are multiple 'groups' of values.

For example let us say we have these form fields: FirstName, LastName, Email. Normally, submitting these on a page is obvious. But what if we want to allow 5 of those? In fact what if the number is even adjustable by the user themselves (such as a javascript that lets the user add new form fields)?

Personally I use counter-loops and evaluate. I think there are more fluent ways to do this to be honest I just haven't had time to look them up and haven't had to do this for awhile. See end of the post for a note on doing the CFSET more properly on the post page.

Note: I'm ignoring most formatting, form validation, etc. in this example.

For our form, let's say we want to make 5 entries possible at a time. We'll do this:

<cfset recordQty = 5>

<form name="myForm" method="post" action="myPostScript.cfm">
<cfoutput><input type="hidden" name="recordQty" value="#variables.recordQty#"></cfoutput>

<cfloop from="1" to="#variables.recordQty#" index="ii">
<p>First <input type="text" name="FirstName#variables.ii#" size="30" maxlength="40" value="">
Last <input type="text" name="LastName#variables.ii#" size="30" maxlength="40" value="">
Email <input type="text" name="Email#variables.ii#" size="30" maxlength="45" value=""></p>

<input type="submit" name="addNames" value=" Add ">

Notice we appended the loop counter number to each field. So on the posting page we can just loop through with a counter again. (See the posts on ROTA as I think some version of this was in there too.)

Posting page, I make sure my recordQty field exists and is numeric and set it as a variable. You may want to do some if-exists logic on the numbered fields. I don't always.

<cfloop from="1" to="#variables.recordQty#" index="n">
<!--- first we set up field names to match what the form should be passing. --->
<cfset newfirst = "FirstName" & variables.n>
<cfset newlast = "LastName" & variables.n>
<cfset newemail = "Email" & variables.n>
<!--- then we get whatever values are in those field names and set them as vars --->
<cfset finalfirst = "#Evaluate(variables.newfirst)#">
<cfset finallast = "#Evaluate(variables.newlast)#">
<cfset finalemail = "#Evaluate(variables.newemail)#">
<!--- then we do something with this, maybe say IF a key field isn't blank before we get into SQL --->
[SQL insert or update here, or create list or array for one or more of those fields, etc.]

Quick and dirty that's it. Here's a note on the sets: Apparently it's better to use array notation when doing this, such as:

<cfset newfirst = variables["FirstName" & variables.counter]>

Haven't done it again since the ROTA to try it but that's a tip.


Sunday, February 22, 2009

Coldfusion RSS Atom Feed: CFML for Making, Creating, Generating, Valid RSS

One of the things about 'helpful blogs' that show you code is that sometimes, they show you little snippets of code. You have to basically already know everything about the subject in question, already know how to put everything together, already know how to call it, and THEN yes, that little snippet of code in the middle will be helpful. What that mostly means is that a lot of people searching will find page after page of how-to and not be able to use any of them.

I was feeling less oblivious than usual when I found Pete Freitag's old Sep 2005 blog post on 'How to Create an RSS Feed', but reading the comments made me feel better. First he left a ton of people in the dark and then he didn't respond to much of any of them. This is the honor of being a code guru I guess. Anyway, since I managed (thanks to him, and to one of his comments, and to further tweaking using the validator and trying-again) to get it working, I thought I would post the full code for my solution for others.

Possibly helpful points for new folks:
  • Generating an RSS feed (information about your website) is ONE thing; actually reading that RSS into a page is ANOTHER different thing.Two separate scripts/efforts.
  • Getting the RSS to take a user to choose their reader etc. to put that RSS into, is merely a matter of calling the file -- I mean providing the link to your file with that RSS XML in it.
  • If you have to use CFML to generate your code for the XML, then obviously, it has to be sitting in a .cfm file. It turns out it doesn't matter what the file extension is--as long as the XML inside it is recognized/validated as an RSS feed.
So we have the first part, akin to technical META headers:
  1. XML version and encoding tag
  2. RSS version and content type tag; I'm using an IF statement from Pete F there from a different one of his blog posts
  3. CHANNEL tag which opens the feed
  4. Atom link with application type tag
The next section is the RSS equiv of non-tech, content-based META headers:
  1. TITLE the actual title of the feed itself, maybe same as website
  2. LINK the link to the website the feed is representing
  3. DESCRIPTION basic info about what this site/feed is
  4. LASTBUILDDATE this is "lastupdate" info about the overall feed itself. Since mine is building from the datatable when called I put Now() on it.
  5. LANGUAGE that's pretty obvious I think
The next section is the actual content of the feed -- in my case, looping records from a CFQUERY call.

Note that any CFML code I needed to put in here, I put to the right of a basic tag -- so it would not mess up the display of the XML itself when generated to the final file, except the query cfoutput which I let take up lines so the output would display better in code. I have no idea if this is necessary, it just seemed like the thing to do... now I'm thinking, probably doesn't matter. I dunno. You can try it without that.
  1. ITEM open tag
  2. TITLE the title of this individual record, post, entry, etc.
  3. LINK this is the link to the individual record, post, entry, etc. so if someone clicked it, it would take them right to the page in question. (In my case this is behind a login but oh well. If logged in, the link works fine.)
  4. GUID with the isPermalink parameter--for me this is the same as the item above.
  5. PUBDATE the date of the individual post/entry/etc. in question.
  6. DESCRIPTION whatever description, listing, etc. you want for the item in question
  7. ITEM close tag
and when the loop is finished then
  1. CHANNEL close tag
  2. RSS close tag
and you're done.

You will want to post your code file to your remote server and then go to a validator and enter the link to it, so it can check it for you. The validator I used is: http://feedvalidator.org/

Some notes on the details of this code that might be useful:

  • It wants date formats in this format: #DateFormat(Now(), 'Ddd, dd mmm yyyy')# #TimeFormat(Now(), 'HH:MM:SS')# EST although it would prefer GMC timezone.
  • The XML must be perfect and there's a good chance that something, at least your 'description' area, might violate that once inawhile, with a character it doesn't accept. There are two different ways to deal with this, and I used them both, nested:
  • The CDATA tag, and XMLFORMAT function, which looks like this in practice:
  • <![CDATA["#XmlFormat(myquery.mydescription)#"]]>
  • although you'll note in the code below I had other info in there and a few separate calls to the function. Note that Pete's article had an error in this part, but blessedly some commenter corrected it or I would have been there all night I imagine.
The full code is below, albeit with query details changed.
The link to this code in practice is: http://www.dojopsi.com/tkr/rss1.cfm

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<cfif cgi.user_agent contains "Mozilla"><cfheader name="Content-Type" value="text/xml"><cfelse><cfheader name="Content-Type" value="application/rss+xml"></cfif>
<atom:link href="http://www.dojopsi.com/tkr/rss1.cfm" rel="self" type="application/rss+xml" />
<title>Remote Viewing at Ten Thousand Roads: TKR at the Dojo Psi</title>
<description>Double-blind Remote Viewing Sessions Posted at TKR</description>
<cfset nowdt = "#DateFormat(Now(), 'Ddd, dd mmm yyyy')# #TimeFormat(Now(), 'HH:MM:SS')# EST">
<language>en-us</language><cfquery ... name="dslist" maxrows="50">select id, entrydt, tasktype, target, method from mytable where ... order by entrydt desc </cfquery>
<cfoutput query="dslist"><cfset target = "#trim(dslist.target)#">
<guid isPermaLink="true">http://www.dojopsi.com/tkr/rv/galleries/peanutview.cfm?dsid=#dslist.id#</guid><cfset dst = "#DateFormat(dslist.entrydt, 'Ddd, dd mmm yyyy')# #TimeFormat(dslist.entrydt, 'HH:MM:SS')# EST">
<description><![CDATA["RV #XmlFormat(dslist.id)# > Type: <cfif dslist.tasktype is 2>Practice<cfelseif dslist.tasktype is 6>Mission<cfelse>Solo</cfif> > Method: #XmlFormat(dslist.method)#"]]></description>

[Valid RSS]
Hope this helps someone else someday.

I'll have to be doing the script to compile and display an RSS feed at some point, and I'll post it when I do.


Friday, February 20, 2009

Date Loops for a ROTA

As part of the ROTA application I had to build a very simple way for a user to say, "Starting on this date, assign something weekly for X weeks." This meant I had to get the start date, the quantity of weeks, write the dates and then loop them in as rows in a table. Later I'd use the row IDs for the outside of a nested loop to assign people to these date-events.

Not much time to explain laboriously here but am posting my code just in case it ever helps someone searching.

I get from a form the qty of weeks and the start date.

<cfset rotaweeks = "#form.rotaweeks#">
<cfset rotadt = "#dateformat(form.rotadt,'mm/dd/yyyy')#">

I actually go back a week so the script can then regularly go forward a week in the loop.

<cfset newday = #DateAdd("d", -7, "#variables.rotadt#")#>
<cfset newdaylist = "">
<cfloop from="1" to="#variables.rotaweeks#" step="1" index="i">
<cfset newdayA = #DateAdd("d", 7, "#variables.newday#")#>
<cfset newday = "#DateFormat(variables.newdayA,'mm/dd/yyyy')#">
<cfset newdaylist = "#ListAppend(variables.newdaylist, '#variables.newday#')#">

That leaves me with a list of dates. I set and format some vars for the edges of that date range.

<cfset firstdt = "#ListFirst(variables.newdaylist)#">
<cfset afterA = '#DateAdd("d", -1, "#variables.firstdt#")#'>
<cfset after = '#DateFormat(variables.afterA,"mm/dd/yyyy")#'>
<cfset lastdt = "#ListLast(variables.newdaylist)#">
<cfset beforeA = '#DateAdd("d", 1, "#variables.lastdt#")#'>
<cfset before = '#DateFormat(variables.beforeA,"mm/dd/yyyy")#'>

<cfset listqty = '#ListLen(variables.newday)#'>

I give users the option to remove all previous entries from that date range.

<!--- if checked, remove previous entries for this team between those dates (if they are replacing/modifying the rota) --->
delete from myrotatable WHERE
teamid = #variables.teamid#
and rotaid = #variables.rotaid#
and rotadt > '#variables.after#'
and rotadt < '#variables.before#'

Then the dates get written into rows in a table

<!--- then insert the dates in table --->
<cfloop list="#variables.newdaylist#" index="ii">
insert into myrotatable
(rotadt, rotaid, rotateamid, ...)
('#ii#',#variables.rotaid#,#variables.teamid#, ...)

I can grab the record IDs of those dates and loop through them, sub-looping my list of userIDs, to create the rota. See the post Rota, Dynamic Variables and Nested Links for the rest of it.

ROTA, Dynamic Variables and Nested Loops

Fresh from the land of nobody-can-help-me-I'm-doomed, I discovered (as usual) that I was trying to make something a lot harder than it needed to be. Hate it when I do that. Then the relief of solving it is tempered by knowing I was an idiot and it should have been obvious in the first place. In any case, onward:

I never knew why I might 'need' something like 'dynamic variables' but here is where I did. I've been working on a ROTA. This is a list of user assigned slots/dates. A baseball team lineup is a good example of a rota. A list of three kids and the chores they take turns doing, or a few employees and the tasks they take turns doing, would be a rota.

In my application, there are "teams" (groups) of people who change off weekly setting up a fun assignment for the rest of the team. This means in my case, there are generally far more dates (weekly assignments) than people (team members), so there is the big loop through dates and then the smaller sub-loop through the members, which repeats until all the dates are filled.


John (userID 93), Jane (userID 156), and Alex (userID 418) are the team;
The dates are weekly, every Monday for 7 weeks.

So when we end this we should have something like:

week 1: john (93)
week 2: jane (156)
week 3: alex (418)
week 4: john (93)
week 5: jane (156)
week 6: alex (418)
week 7: john (93)

This was easy to code except I screwed up the dynamic variables part. Just overcomplicated it.

In general the building process worked like this:

* build rota info including settings, rota name and rota ID, start date and qty of weeks
* get team info including list of user IDs, Aliases and team ID
* show team members for a drag&drop sequence (see Drag & Drop Sortable Lists)
* that list is going to post as a list of the userIDs: form.userid = 93,156,418

Then on the next page the assignments happen.

These team members are being looped in sequentially, and this is going to be controlled with a simple counter variable. So up top we set a list of our user IDs in dynamic variables using the counter. That way, LATER when we are doing the loop through dates, we can use the counter to rebuild what the variable should be named and then evaluate the name to get the user ID.

<!--- set up a numbering list for the user IDs. eg 'theuser2' = 156. --->
<cfset counter = 1>
<cfset qtyusers = "#ListLen(variables.userlist)#">
<cfloop list="#variables.userlist#" index="i">
<cfset "theuser#variables.counter#" = "#i#">
<cfset counter = #variables.counter# + 1>
* query for the record IDs of the list of dates, now we have a list to big-loop through

To see how I created the weekly dates, see Date Loops for Rota

* our list of user IDs is our small list to sub-loop through
<cfset counter = 1>
<cfloop query="myquery">
<!--- we have fewer users than events. user numbering starts over. --->
<cfif variables.counter GT variables.qtyusers><cfset counter = 1></cfif>
<cfset thisuserA = 'theuser' & '#variables.counter#'>

{another query here gets user info}
then the query that updates the record says:

update table SET
rota_userid = #evaluate(variables.thisuserA)#

<cfset counter = #variables.counter# + 1>
And that's it. I really can't believe that I've started and stopped on this several times over 3 years -- previously it was insanely complicated. A couple months ago the solution fell into my head and I thought "wow, so simple!" and I wrote the code superquick, and it worked perfectly right up till the end -- when it crashed and burned on the dynamic variable.

It was telling me the dynamic variable I had set was not found. Even though I could display both its name and its value right above the line where it insisted it didn't exist. It turned out to be something funky about the way I'd been coding it that the evaluate function was crashing against. The above code works fine.