v1.01 RoomBooking system released

So I admit that’s a pretty quick release cycle, but hey, I was inspired (or something). There’s some massive changes, most noticeably the addition of user authentication, roles, permissions, logging & password resets. All documentation has been moved to https://github.com/neokoenig/RoomBooking/wiki, where you can find a breakdown of all the settings, permissions, roles and upgrading instructions.

Things learned from creating the RoomBooking System

Since launching my little Room Booking system as an open source project, I thought it might be useful to highlight a few techniques used which others might find useful. Obviously, this isn’t to say that this is the ‘only way to do x’, but merely to flag some ways to approach common problems. All the below can be seen in context at the gitHub repo

events/functions.cfm

One issue a had a while back was determining the IP address of a user. Now, historically, this wasn’t an issue, as cgi.remote_host worked fine. However, since Railo’s installer changed the way it forwards requests from Apache (mod_jk-> mod_proxy), it always saw the remote IP as 127.0.0.1. This function checks the incoming request header and tries to grab the IP there, rather than in the CGI scope.

<cffunction name="getIPAddress" hint="Gets IP address even from Railo" returntype="String">
<cfscript>
   var result="127.0.0.1";
   var myHeaders = GetHttpRequestData();
    if(structKeyExists(myHeaders, "headers") AND structKeyExists(myHeaders.headers, "x-forwarded-for")){
      result=myHeaders.headers["x-forwarded-for"];
    }
    return result;
</cfscript>
</cffunction>

events/onrequeststart.cfm

This simply checks every incoming request for the URL or FORM scope, loops over each key in the struct, and trims the whitespace.

<cfif StructCount(form)>
	<cfloop collection="#form#" item="key">
		<cfset form[key] = Trim(form[key])>
	</cfloop>
</cfif>
<cfif StructCount(url)>
	<cfloop collection="#url#" item="key">
		<cfset url[key] = Trim(url[key])>
	</cfloop>
</cfif>

Personally, I think cfWheels should do this by default, but then, it would probably break a load of people’s applications. Simply put, I can’t imagine a case where I’d want whitespace at the beginning or end of any key in my url or form scope.

view/helpers.cfm

This one was an interesting one – Javascript often passes it’s values in Epoch time, i.e number of seconds since 1970; I needed to convert this to localTime in a few places. Surprisingly, I’ve never had to do this before!

<cffunction name="eToLocal" hint="convert epoc to localtime">
	<cfargument name="e">
	<cfreturn DateAdd("s", arguments.e ,DateConvert("utc2Local", "January 1 1970 00:00"))>
</cffunction>

So Bootstrap3 has panels. I love panels. But I’m lazy, I don’t want to type out the markup for a panel all the time. So these two helpers do it for me:

<cffunction name="panel" hint="Renders a bootstrap Panel">
    <cfargument name="title" required="true">
    <cfargument name="class" default="panel-default">
    <cfargument name="ignorebody" default="false">
    <cfset var r ="">
    <cfsavecontent variable="r"><Cfoutput>
    <!--- Start Panel --->
    <div class="panel #arguments.class#">
        <div class="panel-heading">
            <h3 class="panel-title">#arguments.title#</h3>
        </div>
        <cfif !arguments.ignorebody>
        <div class="panel-body">
        </cfif>
    </Cfoutput>
    </cfsavecontent>
    <cfreturn r />
</cffunction>

<cffunction name="panelend" hint="Close Panel">
    <cfargument name="ignorebody" default="false">
    <cfif !arguments.ignorebody>
        <cfreturn "</div></div>" />
    <cfelse>
        <cfreturn "</div>" />
    </cfif>
</cffunction>

Usage:

#panel(title="foo")#
.. things here
#panelEnd()#

Sometimes Bootstrap needs it’s content to be flush with the panel, so you occasionally need to ignore the panel-body – flush list-group-items – I’m looking at YOU.

#panel(title="foo", ignorebody=true)#
<ul class="list-group">
<li class="list-group-item">FOO</li>
</ul>
#panelEnd(ignorebody=true)#

Previously, I’d have done this using custom tags, i.e , but with the added import needed on every page, I find this a *lot* quicker/easier.

views/layout.cfm

One of things I like to do is have all the CSS in the header, and all the JS in the footer. So what happens when you want to do your JS stuff in your view folders? Here’s one solution..

In your view file, simply wrap your JS bits in cfsavecontent, i.e

<cfsavecontent variable="request.js.mystuff">
<script>// JS goes here</script>
</cfsavecontent>

Then in your layout file, after all your calls to jQuery etc:

<cfif structkeyexists(request, "js")>
<cfloop list="#structKeyList(request.js)#" index="key"><cfoutput>#request.js[key]#</cfoutput></cfloop>
</cfif>

The only thing this doesn’t really deal with is calling JS in a specific order, as structs are by their nature, unordered. I guess you could turn this principle into an array based system easily enough though: generally speaking, I haven’t found needing to write JS over many view files which needs to be executed in a specific order. YMMV.

bookings/index.cfm

This bit of JS is very useful:

 $('body').on('hidden.bs.modal', '.modal', function () {
        $(this).removeData('bs.modal');
});

Bootstrap caches modals which have had dynamic data inserted in, so initially, when I was building it, every time I clicked on an event, the modal would display the first event I’d click on’s data. This destroys the modal data after you close it. Handy.

And another thing…

The last thing to note is that this has an example of using Ajax in cfWheels, examples of which are fairly rare. If you’re interested in how that’s done, look at the javascript call in bookings/index.cfm under event sources: it’s just a jQuery ajax request. Now look at controllers/Eventdata.cfc = you’ll see the renderWith call, and if you look at the function ‘prepdata’, you’ll see me converting a query into a array of data (in the format I wanted it), which is then passed back by wheels using renderWith(events);

So there you go. If, of course, you guys have better ways of handling the above, I’d love to hear them.

New Open Source Project – Room Booking System

I’m pleased to announce a new open source project for a room booking system – it’s basically a backend to the excellent fullcalendar.js jQuery plugin, using cfWheels as the back end.

Features include:

  • All (well most) configuration done via database settings
  • Uses the DBMigrate plugin for cfWheels for easy future updates
  • Uses the FullCalendar.js month/day/week views
  • Allows for multiple locations, which can be filtered from the main view
  • Each location can be colour coded, and have room layout options
  • Allows you to bulk create events, so easy to add placeholders for repeating series
  • Completely configurable via web interface, all you need to do is setup a datasource called ‘roombooking’
  • Has a theme switcher so you can use/try any theme on http://bootswatch.com/
  • Can send confirmation emails on a new booking

You can download the code from gitHub – https://github.com/neokoenig/RoomBooking or try a demo at http://roombooking.oxalto.co.uk. Some screenshots below. Full documentation at http://roombooking.readme.io/

 

Feel free to contribute or log a bug at https://github.com/neokoenig/RoomBooking – hopefully it’ll be useful for someone!