cfWheels Nested Properties with One to Many and Many to Many Relationships Part 2

Right, following on from part 1…

We should at this point be getting the _emailaddress partial loaded in our form. What I want to do, is be able to add additional email addresses, and get Wheels to update the nested properties appropriately when I submit the form. Oh, I’m assuming you’re using jQuery too.

Firstly, a disclaimer. Javascript isn’t my strong suit, at all, in any way, whatsoever. The following will undoubtedly be able to be condensed down into something much more efficient. Also, this isn’t my javascript – this is unashamedly nicked from Ben Nadel (see http://www.bennadel.com/blog/1375-Ask-Ben-Dynamically-Adding-File-Upload-Fields-To-A-Form-Using-jQuery.htm). Yet again, I find myself standing on the shoulders of giants.

In order to understand what I’m doing, it’s probably best to look at the generated code which wheels makes for our email address partial.

As a reminder, here’s what the cfWheels code is:

<!---_emailaddress.cfm--->
<cfoutput>
#textField(objectName="contact", association="emailaddresses", position=arguments.current, property="email", label="Email Address", size=62, class="email")#
#select(objectName="contact", association="emailaddresses", position=arguments.current, property="type", label="Type", options="Home,Work")#
#checkbox(objectName="contact", association="emailaddresses", position=arguments.current, property="preferred", label="Preferred")#
</cfoutput>

So this creates:

<label for="contact-emailaddresses-1-email">Email Address</label>
<input type="text" value="" size="62" name="contact[emailaddresses][1][email]" maxlength="500" id="contact-emailaddresses-1-email" class="email valid">

<label for="contact-emailaddresses-1-type">Type</label>
<select name="contact[emailaddresses][1][type]" id="contact-emailaddresses-1-type">
<option value="Work" selected="selected">Work</option><option value="Home">Home</option>
</select>

<label for="contact-emailaddresses-1-preferred" class="checkboxLabel">Preferred</label>
<input type="checkbox" value="1" name="contact[emailaddresses][1][preferred]" id="contact-emailaddresses-1-preferred" class="checkbox" checked="checked">
<input type="hidden" value="0" name="contact[emailaddresses][1][preferred]($checkbox)" id="contact-emailaddresses-1-preferred-checkbox">

What we want to do is replicate this output on the fly using Javascript, and increment the counter (i.e, all the 1’s in the above example).

In order to do this, I need to do a few things. Firstly, I need to make sure I can reference the existing set of email(s), which are loaded when the page loads, then I need to be able to clone this set of fields, incrementing the count as I go, and finally, I need to be able to have appropriate add and remove buttons/handlers to deal with manipulating the DOM itself.

Once that’s done, I then can submit the form and do the update: since writing part one, I’ve come accross a small catch with this approach which requires a extra line or two of code (oh noes!) which I’ll get to later.)

Creating theDOM Template

So, in addition to my email address partial, I’m going to create another, called _emailaddressTemplate – Warning, bad code ahead…

<!--- Dynamic Email Field Template--->
<div id="email-templates" class=" clearfix" style="display: none ;">
    <div id="::FIELD1::" class="emailtemplate clearfix">
      <div class="span-11">
        <div class="field">
          <label for="contact-emailaddresses-::FIELD2::-email">Email Address</label>
          <input type="text" value="" size="62" name="contact[emailaddresses][::FIELD12::][email]" maxlength="500" id="contact-emailaddresses-::FIELD3::-email" class="email">
        </div>
      </div>
      <div class="span-3">
        <div class="field">
          <label for="contact-emailaddresses-::FIELD4::-type">Type</label>
          <select name="contact[emailaddresses][::FIELD5::][type]" id="contact-emailaddresses-::FIELD6::-type">
            <option value="Work">Work</option>
            <option value="Home">Home</option>
          </select>
        </div>
      </div>
      <div class="span-3">
        <label for="contact-emailaddresses-::FIELD7::-preferred" class="checkboxLabel">Preferred</label>
        <div class="checkbox">
          <input type="checkbox" value="1" name="contact[emailaddresses][::FIELD8::][preferred]" id="contact-emailaddresses-::FIELD9::-preferred" class="checkbox">
          <input type="hidden" value="0" name="contact[emailaddresses][::FIELD10::][preferred]($checkbox)" id="contact-emailaddresses-::FIELD11::-preferred-checkbox">
        </div>
      </div>
      <div class="span-3 last prepend-top">
        <p><a class="button negative removeemail" href="">Remove</a></p>
      </div>
    </div>
</div>

<script>
// Another bit of JS nicked from Ben Nadel.
// When the DOM has loaded, init the form link.
$(
function addemail(){
var jAddNewRecipient = $( "#addnewemail" );
  jAddNewRecipient
.attr( "href", "javascript:void( 0 )" )
.click(
function( objEvent ){
AddNewUpload();
  objEvent.preventDefault();
return( false );
}
);
}
)

$('.removeemail').live('click',function() {
    $(this).parents("div.emailtemplate:first").remove();
return( false );
});


function AddNewUpload(){
var jFilesContainer = $( "#emails" );
  var jUploadTemplate = $( "#email-templates div.emailtemplate" );
var jUpload = jUploadTemplate.clone();
var strNewHTML = jUpload.html();
var intNewFileCount = (jFilesContainer.find( "div.emailtemplate" ).length + 1);
jUpload.attr( "id", ("emailedit[" + intNewFileCount + "]") );
  strNewHTML = strNewHTML
.replace(
new RegExp( "::FIELD1::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD2::", "i" ),
intNewFileCount
)
  .replace(
new RegExp( "::FIELD3::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD4::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD5::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD6::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD7::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD8::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD9::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD10::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD11::", "i" ),
intNewFileCount
)
.replace(
new RegExp( "::FIELD12::", "i" ),
intNewFileCount
)
 
;
 
jUpload.html( strNewHTML );
  jFilesContainer.append( jUpload );
}
</script>

So what’s going on here? At the top, I’ve got a template, using Ben’s ::field:: references. Underneath I’ve got the JS to replicate the template and insert it in the appropriate place, and increment the counter.

This partial needs to be include *OUTSIDE* the form: this is important: otherwise these oddly named form fields will get into your params and cause problems.

Also note, I’ve got an anchor tag with class of .removeemail – this allows me to remove the parent div element onclick, thus removing it from the the form.

Back in my edit.cfm, I’m going to add these includes, and add another anchor tag to add the additional form fields. So it now looks something like this:

<cfoutput>
<cfif params.action EQ "add">
    <h2>Add a New Contact</h2>
    #startFormTag(class="generic", id="contact-edit", action="create")#
<cfelse>
    <h2>Editing Contact</h2>
    #startFormTag(class="generic", id="contact-edit", action="update", key=params.key)#
</cfif>
    
    #errorMessagesFor("contact")#
        #select(objectName="contact", property="prefix", includeBlank=true, options=application.oii_contacts.prefixes, label="Prefix", title="Optional prefix, such as Dr, Professor etc")#
        #textField(objectName="contact", property="firstname", label="First Name *", class="required", minlength="2", title="First Name, required, needs as least 2 chars")#
        #textField(objectName="contact", property="middlename", label="Middle Name", title="Middle Name, optional")#
        #textField(objectName="contact", property="lastname", label="Last Name *", class="required", minlength="2", title="Last Name, required, needs as least 2 chars")#
<!--- snip... --->

        <div id="emails">
         #includePartial(contact.emailaddresses)#
         </div>
         <a href="" id="addnewemail" class="button">Add Another Email</a>

         <!--- Categories ---->
<cfloop query="categoryTypes">
#hasManyCheckBox(label=name, objectName="contact", association="categories", keys="#contact.key()#,#categoryTypes.id#")#
</cfloop>
        #submitTag(class="edit", value="Update Contact")#
     #endFormTag()#
     
     <!---Hidden DOM Templates --->
     #includePartial("emailaddressTemplate")#
</cfoutput>

So important to note, my DOM template partial is outside the form.

The catch I mentioned earlier comes when updating this: as is stands, I’ve not got a way of telling wheels which email addresses to delete etc. so when I loaded the contact model in my update function (see part 1), it would update and not replace the nested entries. I’ve done the following to simply replace them: More disclaimers – I can bet there’s something I’ve missed, or a better way of doing this: cfWheels gurus please do enlighten me!!

My new update() function in Contacts.cfc controller:

<cffunction name="update">
    <cfloop from=1 to="#arraylen(contact.emailaddresses)#" index="i">
     <cfset contact.emailaddresses[i].delete()>
    </cfloop>
        <cfset contact.update(params.contact)>
        <cfif contact.hasErrors()>
         <cfset renderPage(action="edit")>
        <cfelse>
<cfset flashInsert(success="The contact was updated successfully.")>
<cfset redirectTo(action="view", key=contact.id)>
        </cfif>
    </cffunction>

This works for me, but as you can see, there’s a fair bit of tidying up to be done, especially on the JS end.

 

Building a Webcasting Troubleshooter

At the Oxford Internet Institute (OII), we run a fair few services, one of which is a streaming service based on Wowza Streaming Server. Wowza is a great piece of software (once configured properly) and allows us to stream .h264 encoded mp4s, and also stream live.

Recently, we’ve been trialling the live service as a bit of a recruitment tool; as a department which studies the social effects of the Internet, it seems logical we should utilise social media and streaming video as a way of attracting students, but also to help with the open door effort of not excluding anyone who may wish to apply.

One of the difficulties in providing live webcasting services is the lack of control over the end user’s experience. By using an .h264 encoded stream over RTMP, there are certain conditions which need to be met on the client end. Testing for these conditions has recently proved a bit of a head-scratcher, but we’ve come up with a small testing suite which at least helps push the user in the correct direction.

So what do we need to test?
    • Firstly, Javascript has to be enabled;
    • Secondly, we need to test for Flash, and preferably a version number,
    • And lastly, we need to test that port 1935 is open on the client end, for streaming over RTMP.

The end user should hopefully see three check boxes, for Javascript, Flash and finally, the port 1935 tester. If any of the tests fail, then they should be directed to instructions for enabling Javascript, updating Flash, etc.

I won’t go into the details of the Javascript/Flash detection here, as it’s generally well documented on the web.

The challenge was the port 1935 check.

We didn’t want to go down the Java Applet route (as that would lead to yet another condition to test for), and we didn’t want to give end users instructions on how to ping / tracert etc, as that would simply be too complex for the majority of users. We wanted a solution which would integrate with the rest of the page easily.

As we just need to check 1935 is open from the client end, *any* response on 1935 can be measured as a successful connection. Wowza doesn’t run a HTTP server in the truest sense – it *does* however respond with the Wowza streaming server number on port 80, and also on 1935; It’s a seemingly non standard response though, at least as far as the http headers are concerned.

Via ColdFusion?

So initially, I wasn’t thinking straight, and suggested doing a CFHTTP request to the streaming server on port 1935 to try and gauge a response. As the returned request is slightly odd, CFHTTP threw a few parsing errors, and I ended up trying to get the page as a binary object, which worked. After all, at the time, I was just looking to get anything from the streaming server over 1935.

Obviously, that request would come from our webserver, not the client, so pretty useless for testing whether the  end user could connect on 1935. So that rules out Server side scripting straight away.

Via jQuery?

Next, jQuery was the obvious candidate: it’s a client-side language after all. Couldn’t we just do a get request via jQuery, and gauge the response that way? Unfortunately (well, fortunately for the world wide web) there’s a fair amount of browser restrictions on cross domain requests when it comes to Javascript. You can’t just grab a page hosted on another domain, on a non standard port that way, and access it via jQuery.

Via iFrame?

We can’t use an iFrame, as the browser won’t allow you to access the content from another domain, so passing the result to jQuery wasn’t possible. Also, returning the Wowza generated page wouldn’t be exactly user friendly, and a line such as ‘If you don’t see Wowza X.XXX.XXX below, there’s a connection issue’ isn’t a particularly useful response.

Via JQuery & JSONP?

JSONP (JSON with Padding) is the answer in this case – you could get jQuery to request JSONP – However, we really didn’t want to have to configure Wowza with it’s fairly complex configuration to return JSONP (I’m not even sure if it’s possible, as the Wowza generated page comes from a load of XML config, but it may well be). We also didn’t want to do anything to the rudimentary http server which might affect the video streaming.

Via Proxy?

We couldn’t also do a proxy, as the request would again not be originating from the client – or rather the client request would go over port 80 to the proxy, and then the proxy would continue on 1935. Same issue as Server side scripting.

Ok, Flash Proxy?

The one advantage is that we had control over the server we wanted to test. Enter flash proxy; there’s a good guide here: http://usejquery.com/posts/9/the-jquery-cross-domain-ajax-guide .

By opening up the crossdomain.xml on the streaming server (but restricting it to requests from our domain), we can allow flash to do the check and return the result to jQuery – see flHXR – http://flxhr.flensed.com/ (with the jQuery plugin).

All in all, quite a learning journey!

More links:

Starting out with CfWheels + jQuery + Colorbox

One of the things I’m writing at the moment is a fairly complex bespoke content management system. I’ve done a fair few CMSes in my time, but most of them have had relatively simple back ends – you know the type, where you’ve got a separate ‘admin’ section, which in CF terms is often a completely different application; this is usually for authentication’s sake (let’s face it, it’s fairly easy to secure an entire application.cfc) and to keep it relatively contained in a single folder.

As I’ve been playing around with CfWheels of late, I thought I’d force myself to use it with this project, as for me, that’s usually the best way to learn something – plus I always had the ‘get out’ clause of being able to override internal wheels behaviour if needed. Wheels instantly throws you into a different mindset when it comes to CMS-like apps; as there are various conventions like MVC and ORM already in place, it makes sense to approach everything from that ‘wheels’ point of view.

As building forms is so insanely easy in wheels (see the form helpers), I very quickly got to the point where I started to crave a little more interaction; especially when starting to deal with many to many relationships, and how to update those. But my main objective was to break out of the ‘separate-page-in-a-different-application-to-update’ mindset.

Enter jQuery & colorbox based forms. Having not really played around with jQuery that much (I know, I know) I’ve been pretty shocked about how quickly I managed to pull together some proof of concept forms. Add something like colorbox into the mix, and suddenly you’ve got instant editing ability from any page in your application, without going to have to find the entry in the admin section.

I’m going to try my best to explain where I’ve got to, with the usual caveats – there’s almost always a better way to do this, and I’ve probably just not come across it yet – so comments welcomed..

I’m going to assume you have a basic wheels framework in place, complete with your datasource. For this example, I’m just going to concentrate on one controller and a couple of views.

Starting up – the layout file

The first thing is to include the necessary JS & CSS files, so in my layout.cfm file I’ve got

#styleSheetLinkTag(sources="colorbox", media="screen,projection")#
#javaScriptIncludeTag("jquery-1.3.2,jquery.colorbox")#

The Main View page

We need a page view which will output our page object, and provide our edit link: so in /views/pages/view.cfm you might have something like this:

<cfparam name="page">
<cfoutput><p class="admin-links">
#linkTo(text="Edit This Page", class="colorbox", action="edit", key=page.id)#
</p>
#page.content#
</cfoutput>

Note the class=”colorbox” – this we will use later to tie in the form submission with javascript. So this page is essentially providing a link which points at /pages/edit/[pageid].

Next, we need a form:

In my /views/pages folder, I’ve got a ‘partial’ called _form.cfm, which is basically a view designed to be called without layout.

#startFormTag(action="update")#
#hiddenField(objectName="page", property="id")#
#textArea(objectName="page", property="content")#
#submitTag()#
#endFormTag()#

Nothing too unusual there, move along..

Controller: Pages.cfc

In this file, i’ve got 3 functions: A view, edit and update function. The edit function is fired when the form is loaded, and the update function is fired on the submission of the form.

<cffunction name="view">
     <cfset page = model("page").findAllBySESURL(value=params.title)>
</cffunction>

<cffunction name="edit">
     <cfset page = model("page").findByKey(key=params.key)>
      <cfset renderPartial("pageform")>
</cffunction>
        
<cffunction name="update">
     <cfset page = model("page").findByKey(params.page.id)>
     <cfset page.update(params.page)>      
     <cfif page.hasErrors()>             
             <cfset flashInsert(error="Page Content couldn't update.")>
             <cfset redirectTo(back=true)>
     <cfelse>
            <cfset flashInsert(success="Page Content updated successfully.")>
            <cfset redirectTo(back=true)>
     </cfif>
 </cffunction>

Note the renderPartial(“form”) line. This makes wheels look for a “_form.cfm” file in my /views/pages/ folder. The other bit of note is ‘redirectTo(back=true)’ – this closes the modal window and updates our initial page.

Tie it all together:

The last bit is just the javascript to tie the link into loading the form in a modal window:

 <script type="text/javascript">
 $(document).ready(function(){
$(".colorbox").colorbox({
                         transition: 'fade',
                         speed: 200
  });
 </script>

This tells colorbox to open any link with a class of .colorbox.

So really, it’s three simple things to remember:

  • opening the form via colorbox requires a class/id in the link (at least, that’s one way of doing it, there are many others)
  • Using partials in modal windows means you don’t have to have the layout, and you can reuse that form from anywhere (you could also use renderPage(layout=false))
  • Using redirectTo(back=true) does refresh the page, but the most important thing is, you’ve not really left it due to the overlay.