Simple Ajax requests with cfWheels and jQuery

Someone asked me via email today – I would like to know how to include a Partial dynamically based on a link/button click;

Well, I thought I’d rustle up the simplest way of doing this I could think of.

This example has three parts to it: an Ajax.cfc controller, an index.cfm file and a _time.cfm partial. I’m assuming you’ve got the index.cfm and _time.cfm files in /views/ajax/ and the controller file will live in /controllers/Ajax.cfc

I’ve included two ways of essentially doing the same thing: one renders the partial (which is what the emailer asked for), but depending on what you want to get back, renderWith() can be really useful too, and saves you having to create a partial just to return something simple like a string.

So what’s going on is:

  1. jQuery looks for the class “callremotedata” and takes over its “click” event
  2. It stops the a tag from firing as a normal request
  3. It takes the href value from the a tag
  4. Then fires an ajax request and replaces the HTML in #result with the html returned from the Ajax.cfc controller

Gridmanager.js 0.3.1 released

Another huge set of changes, mainly the introduction of stackable & sortable editable regions, and  full LESS themeing support.
Give it a go here: http://neokoenig.github.io/jQuery-gridmanager/demo/

Changelog:

New style of editable regions which are stackable/editable/deletable
Added: default editable region button
Added: Theming using LESS
Default, light & dark themes now available
Large visual cleanup
Fixed: remoteURL now posts as proper key/value pair
Added: initMarkup() to autowrap non commented markup
Added: editableRegionEnabled & autoEdit options
Added: additional filterCallback option which runs on init();

Gridmanager.js 0.3.0 released

A pretty big set of changes in this one.

A massive thanks to Percy Brea for his input in moving Gridmanager.js forward, and thanks to all those who have got in touch with bug fixes and ideas!

What’s new?

  • Nested row & column support & new add nested row button
  • Added ability to add custom controls on rows & columns (with your own callbacks)
  • Added Custom column classes in addition to row classes
  • RTE’s are now attached to their own editable regions within columns
  • Responsive class support added
  • Responsive class layout mode added
  • Font Awesome now the icon default
  • Documentation now available at http://neokoenig.github.io/jQuery-gridmanager/docs

 

GridManager.js 0.2.1 released

Yes, yes, I know, rather quick after the last one. But this has some much cooler features which I didn’t want to hold back!
Now with resizeable columns, better sorting, fluid row support, better source code view, better RTE handling and some GUI improvements.

Demo | Docs | GitHub Repo

Introducing gridmanager.js

A way of building rows and grids with built in editable regions; requires jQuery, jQueryUI, Bootstrap 3.x, TinyMCE. 

So over the weekend I was becoming annoyed with the lack of layout tools which you could use with a Rich Text Editor. Specifically, users would get bewildered by divs everywhere with Bootstrap. Let’s face it, if you’re not a coder, adding a row or clearfix class, then specifying divs with col-md-6 etc isn’t exactly straight forward.

This is designed to work in conjuction with TinyMCE (and others to follow hopefully); it uses HTML5’s contentEditable attribute to hook in -inline- instances of TinyMCE. Rows are sortable on the y axis, and you can add or destroy new rows easily.

It identifies all your col-md-* divs – so anything with class=”col-md-6″ (or any other number), changes the contentEditable to true, which in turn loads in TinyMCE. When you’re done manipulating the DOM, previewing strips out and destroys all the TinyMCE instances, removes any additional markup, strips out inline styles on columns and lets you see the markup you’ve just created in the page, as actually will be!

Saving posts the contents of the main div to a URL via ajax as specified by you in the configuration. You can customise the column layout buttons by passing in an array of widths, i.e, [8,2,2] will create col-md-8 and then two col-md-2’s. Handy if your bootstrap grid actually uses 24 columns rather than 12.

So what started as a simple jQuery script seemed to merge into (my first) jQuery plugin. There’s probably a tonne of improvements to be made, i.e, no support for multiple instances or nested columns, but that might be an interesting challenge in the future.

Try it and let me know your thoughts:

Demo | Docs | GitHub Repo

jQuery / jQuery UI / CKeditor4 / Layout Editor

In my search for a visual composer style plugin but *not* for wordpress, I didn’t find much. So I thought I’d try and build one (jQuery isn’t my strong suit) – what do you think?

Edit: now released as a jQuery plugin: gridmanager.js

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!

Creating an Ajax mySQL Full Text Search in cfWheels

I’m quite a fan of mySQL full text search – don’t get me wrong, it’s not exactly Google, and it has some annoying limitations, but for a quick and easy search function, it’s not half bad. It’s particularly useful for intranets, or just listings in admin section when you might have to delve through a few thousand records otherwise.

This example uses cfWheels, and most importantly, the cfWheels plugin Remote Form Helper.

Step One: Set up an index on your Table in MySQL.

Note: you can only do this on myISAM tables, not innodb.
I usually do this is navicat, where it’s very painless to do indeed. (simply right click a table, select Design table, then select indexes: give the index a name, and select the columns in the table you want to search: make sure the index type is set to fulltext).

For the ‘navicat-less’, try something like this:

CREATE TABLE `articles` 
(
  `id` int(11) NOT NULL default '0',
  `title` varchar(125) default NULL,
  `topic` varchar(25) NOT NULL default '',
  `author` varchar(25) NOT NULL default '',
  `createdAt` datetime NOT NULL default '0000-00-00 00:00:00',
  `body` text NOT NULL,
);

Then add the index:

ALTER TABLE articles ADD FULLTEXT(title,body);

The attributes in the Fulltext() function are the column names we wish to search (I believe there’s a limit of 16 columns).

In your mySQL conf file, add this line under the [mysqld] section:
ft_min_word_len=3

This sets mySQL to search for words of 3 characters or more, rather than the annoying default of 4.

Step 2: cfWheels setup
Download and install/activate/configure the Remote Form Helper plugin.
Make sure you remember to add the line:

addFormat(extension="js", mimeType="text/javascript");

To your config/settings.cfm file as mentioned in the plugin documentation, and also include the wheel.jquery.js file distributed with the plugin. (oh and jQuery itself, obviously).

Step 3: Search Views & Controllers

We need to create at least three files.

Firstly, our controller, /controllers/Search.cfc:

<cfcomponent extends="controller">
<cffunction name="init">
<cfscript>
// This provides bit is essential!
 provides("html, json, js"); 
</cfscript>
</cffunction>

<cffunction name="q" hint="The Main Search Router">
<cfscript>
// If search terms is incoming, and is an ajax request
if(structkeyexists(params, 's') AND len(params.s) GTE 3 AND isAjax())
{   
// searchArticles() function returns our HTML directly to the data attribute 
renderWith(data=searchArticles(params.s), template="articles");
}
else
{ renderNothing() };
</cfscript> 
</cffunction>

<cffunction name="searchArticles" access="private" hint="Our FullText Search">
 <cfargument name="s" required="yes" type="string" hint="The Search Term">
 <cfquery name="q" datasource="#application.wheels.datasourcename#" maxrows="50">
 SELECT id, title, topic, author, body, createdAt FROM articles
 WHERE MATCH (title,body) AGAINST (<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.s#">);
 </cfquery>
 <cfloop query="q">
  <cfset q.content[currentrow]=formatResult(string=content, highlightT=arguments.s)>
 </cfloop>
 <cfreturn q />
</cffunction>

<cffunction name="formatResult" access="private" returntype="string" hint="Returns a shortened teaser of the main content, with highlighted search terms">
 <cfargument name="string" type="string" hint="The String to truncate">
 <cfargument name="highlightT" type="string" hint="The term to highlight">
  <cfset var newString="">
  <cfset newString=highlight(text=truncate(stripTags(arguments.string), 400), phrases=arguments.highlightT)>
  <cfreturn newString />
</cffunction>

Next, some view files:

/views/search/index.cfm (where our search form and results output will live)

<cfparam name="params.s" default="">       
<cfoutput> 
<!---Form--->
<!--- NOTE THE remote=true attribute!--->
#startFormTag(controller="search", action="q", id="advancedSearchForm", remote="true")#
#textFieldTag(name="s", placeholder="Search..", label="Search For", value=params.s)#
#submitTag(value="Search")#
#endFormTag()#
<!---Output--->
<div id="results"></div> 
</cfoutput> 

/views/search/articles.js.cfm – This formats our returned dataset for return to the page

<!--- loop over and save the output in a variable--->
<cfsavecontent variable="resultSet">
<cfoutput>
<h2>Articles which match your search:  (#arguments.data.recordcount# results)</h2>
<cfloop query="arguments.data">
<cfoutput>
  <h3>#linkTo(text=title, controller="articles", action="view", key=id)#</h3>
  <h4>#Dateformat(createdAt, 'dd mmm yyyy')#</h4>
  <p>#content#</p>
</cfoutput>
</cfloop>
</cfsavecontent>

<!--- Use the plugin to shove back the results to the page --->
<cfoutput>
#pageReplaceHTML(selector="##results", content=resultSet)#
</cfoutput>

That’s it!
So the form at /views/search/index.cfm posts via ajax to the ‘q’ function in /controllers/Search.cfc.
That q() function returns the html via renderWith(), and the remote form helpers pageReplaceHTML() function posts the results to the results div on the calling page.

The nice part about this approach is that you can use that q() part with a switch/case statement to call any function to return data as you need. You could even go to a different search source, such as a Google Search Appliance, which I’ll cover soon.