dans.blog


The miscellaneous ramblings and thoughts of Dan G. Switzer, II

Update to the Linkselect jQuery Plugin (v1.5.11)

I just pushed out an update to the Linkselect jQuery Plugin.

Revisions

v1.5.11 (2013-07-09)

  • Linkselect plugin now annouces an "update" event whenever the value changes--which allows you to set up listeners for when the value has changed.


Update to the mcDropdown jQuery Plugin (v1.3.3)

I just pushed out an update to the mcDropdown jQuery Plugin.

Revisions

v1.3.3 (2013-07-09)

  • Fixed issue where menu option underneath a selected sub-menu option would sometimes cause the option to disappear from the menu
  • Fixed issue where sub-menus would sometimes still be open after re-opening a menu


Merging changes after a successful pull request back into your fork using EGit

Yesterday I finally had the incentive to download Git so I could fork a Github project (Mustache.cfc) and contribute some changes I'd made. Being that Eclipse is my main IDE, EGit seemed to be a logical client to install. The install process was painless and I was able to figure out how to clone a local copy, commit and push changes back to Github. Even the Github pull request process was painless. Much to my chagrin, my pull request was accepted almost immediately and merged back into the main branch.

Naturally the next thing I wanted to do was to sync my fork with the original repository. This is where I got stuck. Whether due to my lack of understanding of Git terminology or just a lack of my Google Search skills, I was not able to easily find instructions for how to sync the original repository back to my fork. After much searching, I was finally able to find some help.

However, I figured I'd clean up the instructions a bit and provide a little more detail on the steps for merging the main repository back into your fork.

The first thing you need to do, is set up a new remote repository linked to your fork:

  1. Open the "Git Repositories" view (Window > Show View > Other > Git > Git Repositories)
  2. Locate your local copy of the fork
  3. Expand the "Remotes" branch (you should see a "origin" entry)
  4. Right-click on "Remotes" and select "Create Remote"
  5. Enter a name representing the master repository, for this example I'll use "mainrepo"
  6. Select the "Configure fetch" option
  7. Click "OK"
  8. Go to the main repository in your browser
  9. Copy the Git URI into your clipboard
  10. Go back to Eclipse
  11. Click on the "Change" button
  12. Paste the URI into the "URI:" field
  13. Click "Finish"
  14. Click the "Add…" button
  15. In the "Source" field, type in the name of the branch you want to import from.

    NOTE: In most cases if you start typing "m", it should show you an auto-complete entry with the "master" branch—just select that option to sync to the master branch.
  16. Click "Finish" (or click "Next" if you want more options)
  17. Click "Save and Fetch"

Now that you have a linked the original repository to your fork, you can merge changes from the original repository to your fork:

  1. Right-click on your project and go to Team > Merge
  2. Under "Remote Tracking", find the "mainrepo/master" (or the appropriate repository/branch based on the remote repository you just configured.)
  3. Click "Merge"
  4. If your repository is up-to-date, there's nothing else to do.
  5. Finally, just commit your changes locally and push back to the upstream

These steps worked for me, so hopefully they'll help guide someone else!


Upgrading Trac with integrated SVN to new version of Python

We recently upgraded our version of Subversion from a very old version of Subversion to v1.7.1. We really wanted the newer merging capabilities that arrived in Subversion 1.5 and figured it was worth the energy to upgrade Subversion to the latest version. Fortunately upgrading our Subversion repositories went very smoothly. I even decided to just do a full dump/load to make sure the repositories were fresh 1.7.x repositories. While this process took a little extra time, the upgrade itself went very smoothly.

However, where things really fell apart for us was with our Trac/SVN integration. The problem is we were still running Python 2.4, which include old SVN bindings, so any attempt to do anything with our SVN ended up with an error that looked like:

TracError: Couldn't open Subversion repository c:/path/to/repository: SubversionException: ("Expected FS format '2'; found format '4'", 160043)

In order to get the SVN commit hooks working with Trac, I was going to need SVN bindings that worked with SVN v1.7.1, but also were compiled for my version of Python. While one might think this wouldn't be difficult, I couldn't find any SVN bindings for Python 2.4. This left my options at either trying to compile my own versions or upgrade Python. Since I don't have easy access to tools for compiling my own version of the Windows binaries, I thought the best option was just to upgrade Python—especially since Trac is soon dropping support for Python 2.4.

The problem is there's no easy way to just migrate a Trac install to a new version of Python, because you're going to need to recompile all your Python *.egg files for the version of Python you're using. After spending way too much time getting all this working, I thought it was wise that I compile a list of all the steps you may need to take in order to get Trac migrated to a new version of Python.

Here is the general install instructions. I don't break this down as a true step-by-step instructions, because so much varies based on your environment. However I do break down all the major steps you'll need to take and try to point you to step-by-step instructions for each bullet when possible.

  1. Install the version of Python you intend to run
  2. Make sure to install Python into a new folder (do not install over your old version of Python)
  3. Compile a list of all the Trac plug-ins you're currently running (check both your Trac/path/to/env/eggs folder and the Python/Lib/site-packages folders for EGG packages you currently have installed)
  4. Back up your current Trac environment folder (where all your Trac repository files are)
  5. Follow the instructions for installing Trac (but ignore the steps for creating new repositories—you can use your existing ones)
  6. Delete any old EGG packages from the Trac/path/to/env/eggs folder
  7. Reinstall all your old Trac Plugins
  8. Shut down your Trac server
  9. Follow the Trac guide for upgrading Trac, with the following notes:
    • You don't need to update the Trac Code (you just installed the latest version)
    • Skip the "Resynchronize the Trac Environment Against the Source Code Repository" step—you'll do that after updating SVN
  10. For Trac/SVN Integration, the Trac and Subversion page outlines the basic steps needed, but this is the step that cost me the most pain, so I'm going to break down some extra notes.
    1. Download the SVN binaries from http://alagazam.net. You'll want to download the Windows binaries and Python bindings for the version of Subversion you plan on using.

      NOTE: This site has the largest collection of Python SVN bindings that I could find and also offers the command line SVN binaries you'll also need. While you should be able to use another SVN command line binaries, using the Setup-Subversion-*.msi installer is probably going to be the most forward way to get started.
    2. Install the Subversion binaries.

      NOTE: You may be required to reboot your server. You will also want to confirm that the Subversion/bin path is in your Window PATH environment variable.
    3. If running Apache, you'll want to make sure to replace the mod_authz_svn.so and mod_dav_svn.so in your /Apache/modules with the versions in  the /Subversion/bin folder.
    4. Copy the files in the Python bindings file (e.g. svn-win32-1.7.1_py27.zip) to the Python/Lib/site-packages folder. You'll want to overwrite the existing files or delete the existing directories.
    5. You'll now want to test your Python SVN installation:
      1. Go to your Python folder and type "python"
      2. Verify that you can load the bindings using the Python interactive interpreter by typing:

        from svn import core
      3. If all is successful you should just see a new Python prompt. The only time you should see output is if there is an error.

        NOTE: This is the part where I spent a lot of time troubleshooting. I kept getting a "ImportError: DLL load failed: The specified module could not be found." After much digging and research, this turns out to be related to Trac Issue #665. The fix for me was to copy the following files into the Windows\system32 folder:
        • libdb42.dll
        • libeay32.dll
        • ssleay32.dll
        I'd recommend backing up existing files before replacing them.
      4. If you're still getting errors, see the Trac/Subversion checklist for more tips.
    6. Resynchronize the Trac Environment Against the Source Code Repository, using the following syntax for each repository:

      trac-admin /path/to/projenv repository resync '*'
  11. Check all your SVN hooks and update any paths pointing to the old version of Python to make sure they point to the new install. For example, your trac-svn-post-commit-hook.cmd may point to your old Python path and these need to be updated in order to work.
  12. Check your Windows PATH environment variable and make sure the new version of Python is in the PATH statement. If it's not, update/add it and reboot your server.
  13. Restart your Trac server.

If all goes well, your environment should now be running under your new Python installation.

This process ended up costing me way more time than expected, so hopefully this guide will prevent you from struggling the way I did.


jQuery Field, mcDropdown and iButton Plugins Updated!

I'm working on migrating our application to use the latest version of jQuery. In the process of migrating, I've been working on fixing compatibility issues with several of the plug-ins I've worked on to make sure they work with the latest version of jQuery. So, here's a list of updated plug-ins and their release notes:

Field Plug-in v0.9.4

  • Fixed issues with jQuery v1.6.x
  • Fixed file fields not currently supported by getValue or getType
  • Fixed formHash breaks on fields with apostrophes
  • Fixed moveNext() did not work, it have invisible parent

mcDropdown jQuery Plug-in v1.3.1

  • Fixed support for jQuery v1.6.2

iButton jQuery Plug-in v1.0.03

  • Fixed compatibility issues with jQuery v1.6.2
  • Added className option for adding an additional class name to the main container—which is useful for adding alternative styles to the button
  • Revised image sprite to be better organized (handles are now grouped together)
  • Updated CSS to reflect changes to image sprite
  • Fixed bug where iButton behavior could be attached multiple times if code attempts to re-initialize on the same element


Easy AJAX using ColdFusion, jQuery and CFCs

A recent post on CF-Talk asked whether ColdFusion could use AJAX to do a database lookup. This is actually extremely easy to do in ColdFusion 8+, because it natively supports returning data in the JSON format.

To show how easy this is to do, I decided to throw together a little demo. This took me about 10 minutes to write—most of which was writing the markup. However, the bulk of the work is going to be handled automatically by ColdFusion, which will handle converting your data to JSON and by the jQuery Field Plug-in (which I wrote) which will handle populating your form from the data it receives from ColdFusion.

To show just how easy this all is, here's the jQuery code required to make an AJAX call to a CFC:

$.ajax({
  // the location of the CFC to run
    url: "example.cfc"
  // send a GET HTTP operation
  , type: "get"
  // tell jQuery we're getting JSON back
  , dataType: "json"
  // send the data to the CFC
  , data: {
    // the method in the CFC to run
      method: "getUserById"
    /*
      send other arguments to CFC
    */
    // send the ID entered by the user
    , userId: $("#userId").val()
  }
  // this gets the data returned on success
  , success: function (data){
    // this uses the "jquery.field.min.js" library to easily populate your form with the data from the server
    $("#frmMain").formHash(data);
  }
  // this runs if an error
  , error: function (xhr, textStatus, errorThrown){
    // show error
    alert(errorThrown);
  }
});

Our CFC looks like this:

<cfcomponent output="false">
  <cffunction name="getUserById" access="remote" returnType="struct" returnFormat="json" output="false">
    <cfargument name="userId" type="numeric" required="false" />

    <cfset var user = structNew() />
    <!---//
      The only tricky part here is to use the bracketed notation
      to match the case in your HTML, JS is case sensentive.
      If you use the dot notation (user.name) then the keys will
      be returned in uppercase.
    //--->
    <cfset user["name"] = "User " & arguments.userId />
    <cfset user["email"] = "user_" & arguments.userId & "@example.com" />
    <cfif (arguments.userId mod 2) eq 0>
      <cfset user["gender"] = "f" />
    <cfelse>
      <cfset user["gender"] = "m" />
    </cfif>

    <cfreturn user />
  </cffunction>
</cfcomponent>

I've posted a working example so that you can see how this code looks. For the "User ID" just enter any number. If an error occurs, the error callback will handle displaying the error to the screen. I've also posted the source code as a zip file you can download.

NOTE:
If you're still on ColdFusion MX & 7, there's a little more work because non-string data automatically gets converted to WDDX. While there are JS libraries for converting WDDX to native JS objects, they can be hard to find now that the OpenWDDX site has been shut down. You can find tools at RIAForge though that can convert your data into a JS string--which jQuery will automatically evaluate when it's received.
UPDATE:
I've added some sample code to the download to show how you can use a proxy template in you're using ColdFusion 7 to get the same results. Just look at the example_cf7.cfm. The only difference is we call the example_cf7_proxy.cfm template instead of calling the example.cfc directly. The proxy template uses CFJSON to convert the results to JSON.

[UPDATED: Friday, March 04, 2011 at 9:59:11 AM]


Bug fixes to CFCParser.cfc used by various open source projects (like CFCDocs)

I recently installed a copy of CFCDocs from RIAForge to give our developers better documentation for all of our various components. However, I quickly ran into a few problems related to the CFCParser component that it uses to analyze components.

The CFCParser doesn't create an instance of the object, but instead analyzes the component using a custom parser. The problem is the parser uses some overly simple logic for detecting attribute name/value pairs and for finding starting/ending tag values.

The problem really presents itself when you have a piece of code as follows:

<cfargument name="hightlightStart" type="string" required="false" default="<span style=""background-color:##ffff66;"">" />

In XML, it would be invalid to have an attribute that looked like:  default="<span style=""background-color:##ffff66;"">". However, in ColdFusion this is perfectly legally.

After spending way too much time debugging the issue, I found there were two distinct issues:

  1. The getTagAttributes() function used a regex that could not properly handle attributes that had escaped quotes.
  2. The findTags() function did not analyze if the ending tag token was inside a quoted string. So, if you had a > character inside an attribute in a tag the parser would use first match it found as the ending tag token—instead of only the first match outside of the attribute value.

I was able to fix both of these issues by re-writing portions of both the findTags() and getTagAttributes() functions.

Since I don't know who wrote this code or who manages it, I thought I'd at least post the code here in case anyone else has issues w/this component.

<cfcomponent hint="The CFCParser component provides methods to parse a ColdFusion Component.">

  <cffunction name="init" access="public" returntype="CFCParser" output="false" hint="The init method currently does nothing.">
    <cfreturn this>
  </cffunction>

  <cffunction name="findTags" access="private" returntype="array" output="true" hint="The findTags method searches the document for the given startTag and endTag. It returns an array of structures containing the locations in the document of the start and end position of each tag, and the full contents of the tag itself.">
    <cfargument name="document" type="string" required="yes">
    <cfargument name="startTag" type="string" required="yes">
    <cfargument name="endTag" type="string" required="yes">

    <!--- Find and remove comments --->
    <cfset var tagLocations = arrayNew(1)>
    <cfset var nestingLevel = 1>
    <cfset var searchMode = "start">
    <cfset var position = 1>
    <cfset var i = 0>
    <cfset var j = 0>
    <cfset var tagBegin = 0>
    <cfset var tagEnd = 0>
    <cfset var tagBlock = "">
    <cfset var tmpPosition = 0>
    <cfset var nestCount = 0>
    <cfset var padding = "">
    <cfset var lastReturn = "">
    <cfset var lastSpace = "">
    <cfset var stTag = "">
    <!----// captures quoted strings and the end tag //--->
    <cfset var regexFindEndTag = '(((("")|".*?[^"]"(?!"))|(('''')|''.*?[^'']''(?!''))))|(#arguments.endTag#)' />
    <cfset var findEndTag = 0 />
    <cfset var findEndTagMatch = "" />

    <cfloop from="1" to="#len(document)#" index="i">

      <cfif searchMode is "start">

        <cfset tagBegin = findNoCase(startTag,document,position)>

        <cfif tagBegin>
          <cfset position = tagBegin + len(startTag)>
          <cfset searchMode = "end">
          <!--- <cfoutput>Start Tag found at character #tagBegin#<br></cfoutput> --->
        <cfelse>
          <cfbreak>
        </cfif>

      <cfelse>
        <cfset findEndTagMatch = "" />
        <!---// if finding complex ending tokens, use original simple find //--->
        <cfif arguments.endTag neq ">">
          <cfset tagEnd = find(endTag,document,position)>
        <!---// for other logic, we need to make sure we don't accidentally find the ending tag inside a quoted string //--->
        <cfelse>

          <!---// loop through quotes strings and end tag matches looking for the first end tag match //--->
          <cfloop condition="findEndTagMatch neq arguments.endTag">
            <!---// we're going to loop through quoted strings and matching end tags looking for a match //--->
            <cfset findEndTag = reFindNoCase(regexFindEndTag, document, position, true) />

            <!---// if a match was found, get it //--->
            <cfif findEndTag.pos[1]>
              <!---// get the match--which is either a quoted string or the matching end tag //--->
              <cfset findEndTagMatch = mid(document, findEndTag.pos[1], findEndTag.len[1]) />
              <!---// update the position in the search //--->
              <cfset position = findEndTag.pos[1] + findEndTag.len[1] />

            <!---// if no matches, stop //--->
            <cfelse>
              <cfbreak />
            </cfif>
          </cfloop>

          <!---// return the ending position //--->
          <cfset tagEnd = findEndTag.pos[1] />
        </cfif>

        <cfif tagEnd>
          <cfset tagEnd = tagEnd + len(endTag)>
          <cfset position = tagEnd>
          <!--- <cfoutput>End Tag found at character #tagEnd#<br></cfoutput> --->
        <cfelse>
          <cfbreak>
        </cfif>

        <cfset tagBlock = mid(document,tagBegin,tagEnd-tagBegin)>

        <cfset tmpPosition = 1>
        <cfset nestCount = 0>
        <cfloop from="1" to="#len(tagBlock)#" index="j">
          <cfif findNoCase(startTag,tagBlock,tmpPosition)>
            <cfset tmpPosition = findNoCase(startTag,tagBlock,tmpPosition) + len(startTag)>
            <cfset nestCount = nestCount + 1>
          <cfelse>
            <cfbreak>
          </cfif>
          <!--- <cfoutput>TmpPosition: #tmpPosition#(#htmlEditFormat(mid(tagBlock,tmpPosition,len(tagBlock)))#)<br></cfoutput> --->
        </cfloop>

        <!--- <cfoutput>count - #nestCount# :: Level - #nestingLevel#<br></cfoutput> --->
        <cfif nestCount EQ nestingLevel>

          <cfset lastSpace = reFindNoCase('[#chr(32)##chr(9)#][^#chr(32)##chr(9)#]+$',tagBlock)>
          <cfset lastReturn = reFindNoCase('[#chr(10)##chr(13)#][^#chr(10)##chr(13)#]+$',tagBlock)>

          <cfset padding = "">

          <cfif lastReturn AND lastSpace AND lastReturn LT lastSpace>
            <cfset padding = mid(tagBlock,lastReturn+1,lastSpace-lastReturn)>
          </cfif>

          <cfset stTag = structNew()>
          <cfset stTag.start = tagBegin>
          <cfset stTag.end = tagEnd>
          <cfset stTag.tagBlock = padding & tagBlock>
          <cfset arrayAppend(tagLocations,stTag)>
          <cfset searchMode = "start">
        <cfelse>
          <cfset nestingLevel = nestingLevel + 1>
        </cfif>

      </cfif>

    </cfloop>

    <cfreturn tagLocations>
  </cffunction>

  <cffunction name="removeComments" access="private" output="false" returntype="string" hint="Strips the comments from a document so that code inside comments gets ignored by the findTags method">
    <cfargument name="document" type="string" required="yes">

    <cfset var tagLocations = findTags(arguments.document,"<!---","--->")>

    <cfset var offset = 0>
    <cfset var i = 0>

    <cfset var start = 0>
    <cfset var count = 0>

    <cfloop from="1" to="#arrayLen(tagLocations)#" index="i">
      <cfset start = tagLocations[i].start - offset>
      <cfset count = tagLocations[i].end - tagLocations[i].start>
      <cfset arguments.document = removeChars(arguments.document,start,count)>
      <cfset offset = offset + count>
    </cfloop>

    <cfreturn document>
  </cffunction>

  <cffunction name="getMethods" access="private" returntype="array" output="false" hint="Calls the findTags method to retrieve all cffunction tags in the given document.">
    <cfargument name="document" type="string" required="true">
    <cfreturn findTags(document,"<cf"&"function ","</cf"&"function>") />
  </cffunction>

  <cffunction name="getProperties" access="private" returntype="array" output="false" hint="Calls the findTags method to retrieve all cffunction tags in the given document.">
    <cfargument name="document" type="string" required="true">
    <cfreturn findTags(document,"<cf"&"property ",">")>
  </cffunction>

  <cffunction name="getArguments" access="private" returntype="array" output="false" hint="Calls the findTags method to retrieve all cfarguments tags in the given document. This method should be passed the body of a cffunction tag as the document argument.">
    <cfargument name="document" type="string" required="true">
    <cfreturn findTags(document,"<cf"&"argument ",">")>
  </cffunction>

  <cffunction name="getTagAttributes" access="private" returntype="struct" output="false" hint="Parses the attributes out of the given document for the first occurrence of the tag specified and returns a structure containing name value pairs for the tag attributes.">
    <cfargument name="document" type="string" required="true">
    <cfargument name="tagname" type="string" required="true">

    <cfset var startTag = "">
    <cfset var stAttributes = structNew()>
    <!--- fails on escaped quotes inside the attribute
    <cfset var regex = '[[:space:]][^=]+="[^"]*"' >
    --->
    <!---// get attributes, make sure we get escaped quotes inside quoted strings //--->
    <cfset var regex = '\s[^=]+=((("")|".*?[^"]"(?!"))|(('''')|''.*?[^'']''(?!'')))' />
    <cfset var aTmp = reFindNoCase('<#arguments.tagname#(#regex#)*[^>]*>',document,1,true)>
    <cfset var i = 1>
    <cfset var position = 1>
    <cfset var attribute = "">
    <cfset var attrName = "">
    <cfset var attrValue = "">

    <cfif NOT aTmp.pos[1]>
      <cfreturn stAttributes>
    </cfif>

    <!---// refactored code to use reMatch //--->
    <cfset startTag = mid(document,aTmp.pos[1],aTmp.len[1])>
    <cfset aTmp = reMatchNoCase(regex, startTag) />

    <cfloop index="i" array="#aTmp#">
      <cfset attribute = trim(i) />
      <cfset attrName = listFirst(attribute, "=") />
      <cfset attrValue = listRest(attribute, "=") />
      <cfset stAttributes[attrName] = mid(attrValue, 2, len(attrValue)-2) />
    </cfloop>

    <cfset stAttributes.fullTag = startTag>

    <cfreturn stAttributes>
  </cffunction>

  <cffunction name="parse" access="public" returntype="struct" output="false" hint="Provides the public interface to the CFC parser. This method should be passed the contents of a full ColdFusion component file.">
    <cfargument name="document" type="string" required="true">

    <cfset var cleanDocument = "">
    <cfset var stComponent = "">
    <cfset var aMethods = "">
    <cfset var i = "">
    <cfset var j = "">
    <cfset var stMethod = "">
    <cfset var stArgument = "">
    <cfset var aProperties = "">
    <cfset var stProperty = "">
    <cfset var aArguments = "">
    <cfset var attribStruct = structNew()>

    <cfset cleanDocument = removeComments(document)>

    <cfset stComponent = structNew()>
    <cfset stComponent.isInterface = false>
    <cfset stComponent.attributes = structNew()>
    <cfset stComponent.attributes.hint = "">
    <cfset stComponent.attributes.extends = "cfcomponent">
    <cfset stComponent.attributes.implements = "cfinterface">
    <cfset stComponent.attributes.displayname = "">
    <cfset stComponent.attributes.output = "">

    <!--- check to see if it is a component --->
    <cfset attribStruct = getTagAttributes(cleanDocument,'cfcomponent')>
    <cfif structIsEmpty(attribStruct)>
      <!--- if no attribs found, it might be an interface --->
      <cfset attribStruct = getTagAttributes(cleanDocument,'cfinterface')>
      <cfif NOT structIsEmpty(attribStruct)>
        <cfset stComponent.isInterface = true>
      </cfif>
    </cfif>
    <cfset structAppend(stComponent.attributes,attribStruct,true)>

    <cfset stComponent.properties = structNew()>

    <cfset aProperties = getProperties(cleanDocument)>

    <cfloop from="1" to="#arrayLen(aProperties)#" index="j">
        <cfset stProperty = structNew()>
        <cfset stProperty.name = "">
        <cfset stProperty.type = "any">
        <cfset stProperty.required = "false">
        <cfset stProperty.default = "_an_empty_string_">
        <cfset stProperty.displayName = "">
        <cfset stProperty.hint = "">
        <cfset structAppend(stProperty,getTagAttributes(aProperties[j].tagBlock,'cfproperty'),true)>
        <cfset stComponent.properties[stProperty.name] = stProperty>
      </cfloop>

    <cfset stComponent.methods = structNew()>

    <cfset aMethods = getMethods(cleanDocument)>

    <cfloop from="1" to="#arrayLen(aMethods)#" index="i">
      <cfset stMethod = structNew()>
      <cfset stMethod.name = "">
      <cfset stMethod.access = "public">
      <cfset stMethod.returnType = "any">
      <cfset stMethod.roles = "">
      <cfset stMethod.output = "">
      <cfset stMethod.displayname = "">
      <cfset stMethod.hint = "">
      <cfset structAppend(stMethod,getTagAttributes(aMethods[i].tagblock,'cffunction'),true)>
      <cfset stMethod.fullTag = aMethods[i].tagBlock>
      <cfset stComponent.methods[stMethod.name] = stMethod>

      <cfset stMethod.arguments = arrayNew(1)>
      <cfset aArguments = getArguments(aMethods[i].tagBlock)>

      <cfloop from="1" to="#arrayLen(aArguments)#" index="j">
        <cfset stArgument = structNew()>
        <cfset stArgument.name = "">
        <cfset stArgument.type = "any">
        <cfset stArgument.required = "false">
        <cfset stArgument.displayName = "">
        <cfset stArgument.hint = "">
        <cfset stArgument.default = "_an_empty_string_">
        <cfset structAppend(stArgument,getTagAttributes(aArguments[j].tagBlock,'cfargument'),true)>
        <cfset arrayAppend(stMethod.arguments,stArgument)>
      </cfloop>

    </cfloop>

    <cfreturn stComponent>
  </cffunction>

</cfcomponent>
NOTE:
I also updated all the unscoped variables!

[UPDATED: Thursday, February 24, 2011 at 2:21:00 PM]


Resolving Mylyn "Insufficient permissions" when connecting to Trac/Apache

For the longest time I've been stuck on Mylyn Eclipse Plug-in v3.0.5—which is very old. Every time I attempted to upgrade to a newer version, Mylyn could no longer authenticate against my Trac installation on Apache. It was a frustrating problem, because I really wanted to upgrade to take advantage of a number of enhancements and fixes.

Today I went upgrade another plug-in and figured I'd try the latest version of Mylyn to see if my issue was resolved. Unfortunately, I was still getting the error "Insufficient permissions for selected access type" anytime Mylyn would try to authenticate via the XML-RPC method. What was really strange, is when I tried connecting via the "Web" method, everything worked fine (which was a new behavior.) So, I decided to spend some time in Charles HTTP Proxy to see if I could debug the issue.

I spent some time monitoring the HTTP traffic, but really couldn't find any rhyme or reason why the authentication was failing. So, I decided to investigate my Apache modules to see if there were any updates to the authentication modules I was using.

One aspect of my Apache configuration that is a little different is that I use mod-auth-sspi module because I authenticate against our Active Directory server (see Configuring Windows Authentication with Apache 2.2.x and Subversion.) After much research and playing around with our configuration, I finally discovered the fix for my problem.

There's apparently an issue with the case that Mylyn sends the authentication information in, the trick was to change my Apache configuration and add the line: SSPIUsernameCase lower

Here's what my authentication configuration rules look like in my Apache httpd.conf file:

AuthType SSPI
SSPIAuth On
SSPIAuthoritative On
# set the domain to authorize against
SSPIDomain AD
SSPIOfferBasic On      # let non-IE clients authenticate
SSPIOmitDomain On      # keep domain name in userid string
SSPIBasicPreferred Off # basic authentication should have higher priority
SSPIPerRequestAuth On  # required for IE POST operations
SSPIUsernameCase lower

So, if you're having mystery issues with applications authenticating against your Apache server and your using SSPI, you might try implement the SSPIUsernameCase lower directive.


Pagination in MSSQL 2005

Paginating data is one of those commonly required tasks. In previous version of MS SQL Server it's been tricky to handle, but made easy via the use of stored procedures that handle the pagination logic for you. However, if you're using SQL Server 2005 there's a much easier way to handle pagination and that's to use a Common Expression Table (CTE.)

Microsoft created the CTE syntax to make solving complicated tasks easier (such as returning a query of hierarchical tree data.) When you create a CTE, you're essentially creating a virtual table you can query against. CTEs generally perform very well and can often replace the need for creating temp tables.

Let's look at an example of query out records 11-20 from a Employee database:

-- create the Common Table Expression, which is a table called "pagination" with pagination as ( -- your normal query goes here, but you put your ORDER BY clause in the rowNo declaration select row_number() over (order by department, employee) as rowNo, -- a list of the column you want to retrieve employeeId, employee, department from Employee where disabled = 0 ) -- we now query the CTE table select -- add an additional column which contains the total number of records in the query *, (select count(*) from pagination) as totalResults from pagination where RowNo between 11 and 20 order by rowNo

The first part of the query creates the table expression we'll actually query against when we grab the actual results from the database. In the CTE you're going to write the SQL you'd normally write to grab all the records with one exception—you use the row_number() function to generate a virtual column that orders your result rows for you. The actual "order by" clause you want to use will actually go in the row_number() declaration—this makes SQL Server assign the correct row number for each record.

It's important to remember that a CTE can be as complex as you need it to be. You can do joins, pivots, etc—whatever you need.

The next step is to actually query against the CTE you created. This query will virtually be the same for all pagination queries you write. You are simply grabbing all of the columns and then limiting the returned data to just the rows you want to display (in this case rows 11 - 20.)

In this example, I added an additional column that contains the total results that were returned from the CTE. This is not needed, but it's handy if you need to do something like "Results 11 - 20 of 4,567 records." There are many ways you could generate the total records, this is just one method.

If you've upgraded to SQL Server 2005 and haven't started using CTEs yet, you really need to start getting familiar with them as they really simplify many tasks in SQL Server that previously were very difficult to solve.


Selectively running SVN hook commands in Windows

I was working on another SVN hook today and only wanted to run some code if a particular folder was modified, just to save some processing time from unnecessarily running an SVN update process. Here's the little snippet I came up with to run in my post-commit.bat file:

SET REPOS=%1
SET REV=%2
SET WORKING_COPY=C:\local\working\copy\path
SET FOLDER_PATH=/path/as/seen/in/SVN/log/output

REM Check to see if a file in our specified folder was edited
svn log %WORKING_COPY% -v -r "%REV%" | find /i "%FOLDER_PATH%" >
nul
if errorlevel 1 goto notfound
    svn update %WORKING_COPY%
goto end
:notfound
ECHO No updates to the "%FOLDER_PATH%" folder detected
:end
ECHO Finished

The idea behind this is simple. We check the log for the revision that was just checked in to see if it contains a specific string—which in this case is a folder path. If the log does contain the string, we update our local copy. If it doesn't exist, we skip that step and just output a message that says no updates found.

Make sure to check out my other posts on using SVN hooks in Windows for more tips and tricks.


Getting the URL/web folder path in ColdFusion

Raymond Camden blogged about a question someone had about getting the folder path for the current template. While Raymond addressed how to get the OS path, how would you get the URL path? So, if you had your user was on the URL http://www.example.com/some/folder/and/file.cfm, how would you go about getting the "/some/folder/and/" path?

While there are always many ways to solve a problem, I've tried to come up with a solution that should work for any version of ColdFusion from 6.0 and above. I wanted to avoid using CGI variables (since those vary by webserver,) so I went with using getPageContext() instead.

Here's the solution I wiped up:

<cffunction name="getWebPath" access="public" output="false" returntype="string" hint="Gets the absolute path to the current web folder.">
    <cfargument name="url" required="false" default="#getPageContext().getRequest().getRequestURI()#" hint="Defaults to the current path_info" />
    <cfargument name="ext" required="false" default="\.(cfml?.*|html?.*|[^.]+)" hint="Define the regex to find the extension. The default will work in most cases, unless you have really funky urls like: /folder/file.cfm/extra.path/info" />
    <!---// trim the path to be safe //--->
    <cfset var sPath = trim(arguments.url) />
    <!---// find the where the filename starts (should be the last wherever the last period (".") is) //--->
    <cfset var sEndDir = reFind("/[^/]+#arguments.ext#$", sPath) />
    <cfreturn left(sPath, sEndDir) />
</cffunction>

If you just call getWebPath() it will return the current web folder path for the current base template.

We use a regular expression to strip out additional path info information that can sometimes be present for people using SEO-friendly URLs. For example, the URLs on my site appear like: http://blog.pengoworks.com/index.cfm/2006/9/27/CFMX-UDF-Parsing-a-URI-into-a-struct which returns a path of /index.cfm/2006/9/27/CFMX-UDF-Parsing-a-URI-into-a-struct. We need the regex to find the last period in the string and assume everything else is additional path info. The default regex should work in the vast majority of cases, but you can adjust it for the corner cases.

You can also manually supply a path such as: #getWebPath('/index.cfm/2006/9/27/CFMX-UDF-Parsing-a-URI-into-a-struct')#. This would return "/" as the web path.

Anyway, hopefully some of you will find this little UDF useful.


UDF: Convert ColdFusion Date to JavaScript Date Object

I had the need to convert a ColdFusion date/time stamp to a JS Date Object. I thought serializeJSON() function would handle this, but it turns out it treats CF date/time variables as strings. The toScript() function will convert CF variables to JS Date Objects—provided that the date/time variable is in ODBC format (i.e. {ts '2008-05-02 13:32:16'}.)

However, I wanted something that would work for anything that ColdFusion saw as a Date object, so I just whipped out this little 4 line helper function:

function jsDateFormat(date){
    if( isDate(date))    return 'new Date(#year(date)#, #(month(date)-1)#, #day(date)#, #hour(date)#, #minute(date)#, #second(date)#)';
    else return "null";
}

If ColdFusion doesn't see the date as a date object, then it'll set the date/time to "null". To use this function you just do:

<script type="text/javascript">
var today = <cfoutput>#jsDateFormat(now())#</cfoutput>;
</script>

This would then generate the following:

<script type="text/javascript">
var today = new Date(2008, 4, 2, 13, 32, 16);
</script>

Obviously this is pretty straightforward, but it's saved me a lot of repetitive typing today and simplified the readability of my code.


UDF for converting a PDF page to Images using CF8 & Java

[UPDATED: Monday, November 21, 2011 at 8:33:18 AM]

I'm working on a project where I'm trying to create thumbnails for documents the user uploads. Since CF8 has introduced the <cfpdf /> tag, I thought it would be pretty straightforward to turn page 1 of a PDF into a thumbnail image—turns out I was wrong.

While the <cfpdf /> does work, it was causing me to jump through some various hoops some of which I could easily overcome. The issues I had were:

more…


Accessing privileged methods in a Java Applet via JavaScript

I've been working on a Java applet that can get images from the user's clipboard or take screenshots of the user's desktop and upload them to a server via HTTP. The last piece of the puzzle was to make sure I could access the task methods via JavaScript, as the interface will most likely be driven by HTML.

I've been using a self-signed cert to sign the Java Applet so that I can access the user's clipboard and take the screenshot. All of this was tested and working extremely well from if I used the Applet UI. However, as soon as I would try to invoke one of the sandboxed functions from JavaScript the applet started throwing the error:

AccessControlException is thrown: access denied (java.awt.AWTPermission accessClipboard)

I tried a number of things to get around this problem. I thought it was something I was doing wrong when I was signing the applet (since it was working fine from within the Applet's built-in UI controls.) After playing around with the cert signing process and getting no where, I finally came across the post JavaScripting in Applets - Getting Out of the Sandbox where a comment from Stéphane Bury pointed me to a solution.

public static void doSpecialWork(String param1){ final String fParam1 = param1; java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { public Object run() { // put the code of your method here ... ... return ""; } }); }

My final solution was to write a command() method which I can use to trigger off the internal privileged methods:

/** * The method to invoke from JS to perform the privileged methods--which throw * security errors if you try to access them directly. * * @param command - the command you want to perform (clipboard, screenshot, upload) */ public void command(String command){ final String cmd = command; java.security.AccessController.doPrivileged( new java.security.PrivilegedAction(){ public Object run() { // execute the privileged command executeCommand(cmd); // we must return an object, so we'll return an empty string return ""; } } ); }

As you can see the method is very basic, it just passes the command it received to a private method which actually executes the command. Now I can access any of the privileged methods in my applet with this little helper method.


e / TextMate Command - Open Target Document

[UPDATED: Saturday, March 08, 2008 at 10:26:56 AM]

I've been playing around with e - TextEditor this weekend to see if maybe it'll replace Textpad for me. I've always liked the speed of Textpad and it does a really good job on large files. I use Eclipse as my main IDE, so when it comes to a Notepad replacement my two main requirements are fast load times and the ability to handle large text files (for reading logs.)

However I keep seeing some of the really cool things that TextMate can do, so I've been keeping an eye out on e - TextEditor. It has a couple of features that would make quick editing of HTML files extremely easy. I really like the ability to selected a bunch of words or tags in a document and easily replace them.

One of the key features of e is it's TextMate bundle support—which really allows you to extend the functionality of the program. While surfing the e forums, I came across a Bundle command which allows you to open the file you have selected in the document (or the file where the caret is positioned.) This script makes it really easy to open files being loading from <script />, <style /> or any other tag you may use for loading files.

The original script, posted by tanguyr, I found either needed you to add e.exe to the Windows PATH environment or you needed to hard code the path to e.exe. I fixed the problem by using one of e's environmental variables. The $TM_SUPPORT_PATH variable points to a sub-directory above where the e.exe file exists, so I just use a relative path to get to the e.exe executable.

To add this command to e, go to the Bundles > Edit Bundles > Show Bundle Editor. I added this script to the Source bundle, as it seemed to make the most sense to me. Select the Source folder and click the + button and select the "New Command" option. I decided to call the command "Open Target Document", if you don't like the name choice something you like better.

Paste the following Bash script into the source area.

more…