Saturday, October 24, 2009

How to Use the jQuery dirtyFields Plugin with FCKEditor

A commenter named Dean recently asked me if there was a way to use an FCKEditor field with my dirtyFields jQuery plugin.

When applied to a form or any container that contains HTML form elements, the dirtyFields plugin records the intial values or states of each form element, and then anytime the change event is triggered on any of those fields, the plugin functions compares the current value or state of the field to the recorded value/state and then applies certain CSS styles to certain page elements (as defined by the user) if that particular field has been changed.

There are two problems with trying to use an FCKEditor element with dirtyFields. The first is that the FCKEditor Javascript generates the page elements it uses to represent the editor window after the page has been loaded, after the jQuery document.ready event typically used when binding plugin functions and events to page elements. The second problem is that the editor content window (where the user does their typing) is contained in an <iframe> element, and as such the text within the editor window and any events triggered within that window aren't accessible to the dirtyFields plugin.

However, it is possible to get a handle on what's going on within the editor window by utilizing the FCKEditor API and then use some of the public functions within the dirtyFields plugin to make it work.

First the code, then the explanation:

<script type="text/javascript">
    function FCKeditor_OnComplete( editorInstance )
        {
            editorInstance.Events.AttachEvent( 'OnBlur' , checkForChanges ) ;
            $.fn.dirtyFields.setStartingTextValue($("#editorField"),$("#myForm"));
        }
			
    function checkForChanges( editorInstance )
        {
            $("#editorField").val(editorInstance.GetXHTML(true));
            $.fn.dirtyFields.updateTextState($("#editorField"),$("#myForm"));
        }
	
    $(document).ready(function() { 
	$("#myForm").dirtyFields();
       ...whatever other functions you want to run...
    });
</script>

<form name="myForm" id="myForm" method="post" action="something.cfm">
    <label id="lbl_editorField" for="editorField">FCKEditor Window:</label>
    <cfmodule
        template="/fckeditor/fckeditor.cfm"
        basePath="/fckeditor"
        instanceName="editorField"
        value=''
        width="600px"
        height="200px"
    >
    ...
</form>

A note about how I'm using FCKEditor in this example: the example is based off of a ColdFusion page I wrote in order to try this out, so I used the <cfmodule> tag to create the FCKEditor field. Programmers using version 8 or higher of ColdFusion can instantiate an FCKEditor field using the <cftextarea> tag, but I suspect the FCKEditor API calls will work the same in either case. 

As I said earlier, one of the problems was that the HTML elements created by FCKEditor were created after the page loaded. The FCKeditor_OnComplete() function solves this problem: it executes as soon as FCKEditor has finished creating the HTML elements needed to support the editor, and provides the instance of the editor as an object ("editorInstance" is simply the name assigned to the object in the FCKEditor API examples, so I followed their lead). One of the elements it creates is a hidden text field with an "id" attribute equal to the name of the FCKEditor instance. My example presupposes that the FCKEditor field in the form is named "editorField", in which case a hidden text field with an id of "editorField" now exists in the form.

The first line in the FCKEditor_OnComplete function binds the checkForChanges() function to the editor's "OnBlur" event, which means that any time the user navigates away from the editor window (either by pressing the Tab key or clicking on another page element with the cursor), the checkForChanges() event will fire (Note: for a while, I was convinced the API didn't provide a way of detecting a loss of focus because "OnBlur" isn't documented on the API page). The second line uses the dirtyFields setStartingTextValue() public function to record and save the value currently contained with the "editorField" hidden text field, so that dirtyFields has an initial value to compare any changes in the editor window against.

The checkForChanges() function also receives an instance of the editor as an object. The first line in the function uses the getXHTML() method of the editor object to retrieve the current contents of the editor window and copy it into the value of the "editorField" hidden text field. The second line calls the updateTextState() function of dirtyFields, which tells dirtyFields to compare the current value of the "editorField" hidden text field with the previous value recorded by the setStartingTextValue() function. If the values are different, the label associated with the "editorField" hidden text field will be styled to indicate that the content of the FCKEditor window is "dirty."

And that's all there is to it: even though the dirtyFields plugin can't interact with FCKEditor fields by default, the public functions provided by the plugin can be used in conjunction with the FCKEditor API to make it work.

Sunday, October 18, 2009

Creating a Reload Event-Handler for Model-Glue

The Model-Glue framework has a number of configuration settings that modify its behavior. Perhaps the most important of these settings is the "reload" setting. When the reload setting is set to "true", the framework itself will be reload on each request, and any changes you made to the Model-Glue, ColdSpring, or ORM settings of your application since the last reload will be implemented. When reload is set to "false," those framework files will not be reloaded, and any changes you might have made to them are ignored.

So the idea is that you would have the reload setting set to "true" during development so that any changes you make are processed in the next request. The drawback to being in this constant-reload mode is that it takes time to reload the framework, an amount of time that increases as you add event handlers, message broadcasts, and such to the ModelGlue.xml file and add more bean definitions to your ColdSpring file. It's for this very reason that the Model-Glue team state that it is "imperative" that you set reload to "false" when you put your application into production.

As the development of your application progresses, you'll reach a point where you don't want to reload the framework for every single request because you're spending too much time waiting between requests. At that point, you would set reload to "false", and manually force a reload of the framework when necessary by adding the reloadKey and reloadPassword to the URL of one of your page requests, like so:

http://www.myModelGlueApp.org/index.cfm?init=true

...where "init" is the reloadKey and "true" is the reloadPassword.

Many Model-Glue developers open a separate tab in their web browsers pointing a URL for their application that contains the reloadKey and reloadPassword and simply reload that page whenever they need the framework reloaded, rather than navigating away from the current page they're working on. I decided to go a slightly different route and create a reload event-handler that I could trigger from any page in my Model-Glue application and execute the reload event in a new browser tab or window.

The page layouts of my applications always include some sort of header area at the top of the page. In the header area, I added a hyperlink labeled "Reload MG" with a URL pointing at my reload event-handler:

<cfif viewState.getValue("appEnvironment") EQ "local">
    <cfset reloadURL= viewState.getValue("myself") & "performReload&init=true">
    <p align="right"><a href="#reloadURL#" target="_blank">Reload MG</a></p>
</cfif>

In my last blog post, I explained how my applications determine whether they're running in my local development environment or my remote production environment. The <cfif> statement ensures that the reload link only appears in the header when I'm running the application on my local box. Having the "target" of the hyperlink set to "_blank" ensures that the reloader page opens in a new tab or window.

The performReload event-handler is defined in the ModelGlue.xml file like so:

<event-handler name="performReload">
    <broadcasts />
    <results />
    <views>
        <include name="body" template="util/dspReload.cfm" />
    </views>
</event-handler>

The reload action is initiated by the presence of the reloadKey and reloadPassword in the URL, so there isn't a need to execute any additional controller or model code. The event-handler simply loads the dspReload.cfm page in my "util" directory. This is what the dspReload.cfm page looks like:

<h2>Page Should Close Automatically</h2>

<script type="text/javascript">
    window.close();
</script>

The single Javascript statement is pretty self-explanatory: close the current window.

So when I click on the "Reload MG" link at the top of any of my Model-Glue application pages, a new browser tab opens and the focus of the browser is on that new tab. I can return to my previous tab (where I'm working) and simply wait for the reload event to complete over in the new tab. Once it does complete, the Javascript executes and the new tab is closed: I don't have to close it myself.

So instead of keeping an open browser tab that I'd have click on, click the reload button for, and the click back to my previous tab, I can just click a link in my current page to spawn a reload tab and simply navigate back to my original tab. While it's not a huge improvement, I prefer it over maintaining a reload tab.

Saturday, October 17, 2009

Managing Environment-Specific Application Settings Via A ColdSpring Bean

When I develop my ColdFusion applications, I work in two different environments. I develop the applications on my local machine, then deploy them to the server when I reach particular milestones or when it's ready for limited user testing. Even though I mimic the server environment as best as I can on my local machine, there are still certain aspects of the application that are different when it executes on the server. For example, my local machine isn't configured to send and receive e-mail, but I want my application (when it's running in production) to send out an e-mail when certain functions are executed.

What I used to do to solve this problem was to run a series of functions via the onApplicationStart() function in Application.cfc that would determine where the application was running (development or production), record that fact in an application-scoped variable called "locale", and then set a series of additional application-scoped variables based on what the current environment was. I would then code certain aspects of my applications, such as those responsible for sending e-mail, to examine the value of the application.locale variable to determine if certain function should execute and then use any other application-level settings necessary for the actual execution (like the e-mail address associated with the application).

I recently started using Model-Glue as my application framework, and since Model-Glue uses ColdSpring to manage some of its internal settings, I decided to revise my technique to take advantage of ColdSpring as well.

Here is the code of the appConfig.cfc I created for managing the application settings:

The init() constructor function takes three structs as arguments: one that contains the universal application settings that are the same regardless of environment, one that contains the application configuration settings for my local/development environment, and one that contains the configuration settings specific to my remote/production environment. The universal settings are copied into the "config" struct in the variables scope, and then a call is made out to the determineEnvironment() function to determine the current application environment. The determineEnvironment() function uses the cgi.cf_template_path to determine if the current ColdFusion template is running on a Macintosh system (my work laptop is a Mac) and returns the name of the constructor argument that should be used to populate the rest of the "config" struct. The getConfig() function provides a means of retrieving the value of a configuration setting, while the setConfig() function allows you to change a configuration setting.

So populating this appConfig object with the correct application configuration settings is simply a matter of providing the three different configuration structs into the init() constructor argument. That's where ColdSpring comes into play: ColdSpring is ideal for managing and documenting the data and objects that need to be injected into singleton objects. Here is an example ColdSpring bean definition (within a ColdSpring.xml file) for the appConfig object:

As you can see, the ColdSpring bean definition contains three <constructor-arg> XML blocks, one for each argument to be passed into the appConfig.cfc during instantiation, and each constructor argument contains a <map> XML block that defines the struct variable for that argument.

What's the advantage of defining these settings in ColdSpring? Once the appConfig object is defined as a ColdSpring bean, I can inject an appConfig bean into any other singleton bean object I define in ColdSpring or retrieve appConfig from the ColdSpring beanFactory object (which is usually instantiated during application startup). Plus, Model-Glue makes it very easy to inject ColdSpring beans into any of your controller objects simply by passing a list of ColdSpring bean names into a "beans" attribute in either the controller's <cfcontroller> tag or the starting <controller> tag of the controller's XML block in the ModelGlue.xml file of the application (see the "How to Use Bean Injection" page on Model-Glue documentation site for more details on that).  And it makes sense to put my application configuration settings alongside the Model-Glue configuration settings in ColdSpring so there's only one file I need to refer to for my configuration data.

The end result? I end up with an appConfig object containing the correct application settings for the current environment that my code can retrieve variable settings from whenever a function's behavior is dependent upon the application environment.

This technique I've just outlined works for me because I'm a solo developer with only two application environments, a simple way of determining which environment an application is running in, and usually only a small number of environment-specific app settings to deal with. If you're working in a team or with multiple application environments, you made need a more robust approach.  One option you might want to consider is the Environment Config project on RIAForge.org. The project also lets you leverage ColdSpring to define environment-specific variables, but it provides more options for distinguishing between different application environments and more flexibility in how you structure and retrieve your application configuration information. It's still in beta at the moment, but it looks promising.

Thursday, October 1, 2009

Revision, Re-Release of My dirtyFields jQuery Plugin

I was pretty happy with the functionality of my original dirtyFields jQuery plugin, but as comments and questions about the plugin started coming in, I soon realized that there was a lot of room for improvement.  So I spent the last few weeks reworking the code to create a more flexible, more powerful version, one that completely replaces the original version.

The main purpose of the plugin remains the same:  it helps you show a user exactly which HTML form fields have been changed (made "dirty") since the page was last loaded or saved (text inputs, drop-downs, checkboxes, etc.).  But the new version of the plugin gives you more control over what page elements are affected when a particular form field is changed, provides callbacks that let you execute your own functions before and after a form field is processed by the plugin, and includes over a dozen public functions for manipulating individual form fields or the entire form (including functions that let you add the plugin event handler to any new form fields you add to the form dynamically after the page is loaded).

I created a website to go along with the new plugin that provides full documentation on all of the plugin settings, callbacks, and public functions and three working examples that demonstrate the plugin in action.  You can go there to learn all of the details:

https://bcswartz.github.io/dirtyFields-jQuery-Plugin/

Monday, September 28, 2009

Quick Tip: Accessing the Contents of a FCKEditor Box

Today I found myself needing a way to access the current contents of a FCKEditor instance on a web page via Javascript (I needed to create a "preview" dialog box that would show the user what the content would look like when published).  After some searching on Google, I learned that by instantiating FCKEditor on the page, I had access to the FCKEditor API and could access the content with the following statement:

var editorContent = FCKeditorAPI.GetInstance("postText").GetXHTML(true);

...where "postText" is the id value ("instanceName") of the editor.

Wednesday, September 9, 2009

jQuery Tip: Storing and Retrieving Object Properties with the Data() Function

The jQuery data() function allows you to store data "within" a DOM object by associating a property name with a value.  The first line of the code below assigns the value "bar" to the property name "foo" associated with the <form> element on the page, while the second line retrieves the stored value:

$("form").data("foo","bar");
...
var fooValue= $("form").data("foo");

It's a useful technique for adding metadata to a DOM element.

The jQuery plugin I'm currently working on associates several pieces of metadata with certain DOM elements on the page.  It occurred to me yesterday that it might be cleaner to store all of that metadata under one property (one namespace) in the DOM element, reducing the possibility of my plugin interfering with any other code might also store data using the data() function.  But I wasn't quite sure how I would retrieve the individual metadata values.

Turns out the solution is quite simple:  you can retrieve the object sorted in the namespace property, then reference the individual properties within the namespace using dot notation:

  var settings= {
     denoteDirtyForm: true,
     denoteDirtyOptions:false,
     trimText:true,
     dirtyFieldClass:"changedField"
  }
  ...
  $("form").data("myNamespace",settings);
  ...
  var showDirtyForm= $("form").data("myNamespace").denoteDirtyForm;
  var dirtyClass= $("form").data("myNamespance").dirtyFieldClass;

So it looks like there's no real limit to the amount or complexity of data you can associate with a DOM element using data().

Sunday, August 23, 2009

New jQuery Plugin dirtyFields: Bring Attention to Changed/Unsaved Fields

I just finished a project that included an administrative page where users could make changes to a parent record and numerous child records via multiple forms. All of the form submissions were done via AJAX, so I needed a way to illustrate which of the forms on the page contained changed or "dirty" data, and which forms had no unsaved changes.

This dirtyFields plugin is derived from the functions I wrote to handle that task in that project.

It applies a CSS class to a contextual DOM element associated with any form field that has been changed from its original value. For text inputs, <textarea>, and <select> elements, the <label> element associated with the field (via the "for" attribute) will be styled with the class. For <checkbox> and <radio> elements, you can specify the text associated with each element using "checkboxRadioTextTarget" plugin option (the default setting is "next span").

It works by storing the current value or state of each form field within the data store of the field using the jQuery data() function.  As the value or state of the form field is changed, its current value/state is compared to the one within its data store, and if they aren't equal the field is considered "dirty".  The plugin includes a function called "resetForm" that can be used after the form has been submitted to update the data store of each field with the new field value and mark all the form fields as clean.  There is also an option to apply a CSS class to the form itself to indicate whether or not it contains dirty fields.

(UPDATE: I've now added support for a "formIsDirty" callback function that can be used in conjunction with the "denoteFormDirty" option to run your own functions once a form has been determined to be clean or dirty.)

Links to a demo and documentation can be found below the following screenshot, which shows the style changes to the labels and text of form elements that have been changed:

Project homepage: https://bcswartz.github.io/dirtyFields-jQuery-Plugin/