With a little side of applesauce...

Tuesday, December 30, 2008

Google Chrome - DOM debugger?

Yes, when I found that my DOM had blown-up on my site, (while the boss was watching... :P ), I opened up the handy-dandy JavaScript Console in Google Chrome, and reloaded the page... Sure enough, the error showed up in the console with a link to the place in the rendered XHTML. I was able to fix the issue in minutes, but not before he had gone home for the holidays :P Doh!!

Happy New Year everyone! I'm going home too :)

cfajaxproxy / jquery - finally

I have been able to rely on Coldfusion's built-in AJAX functionality for most of my workflow project, but finally hit an instance where I needed to allow a document owner to delete a document listed in their queue. The queue is a simple tabular list of documents with enough information to help them decide which documents they want to edit/review. Here is the HTML for the table row:

          <tr id="docchoice_<cfoutput>#iter#</cfoutput>">
<!--- 1. query ldap for docobject information, and then format the following line with the objectid, full name, datetime, form submitted --->
<cfset arrLdapString = cfcUtilities.splitString( name )>
<cfset ldapString = arrLdapString[1]>
<cfset objectInfo = cfcWorkflowLdap.getDocumentObject( ldapString, 'ou=documents,#Session.AppBaseDn#')>
<cfoutput query="objectInfo">
<td width="7"><input type="radio" name="docchoice" value="<cfoutput>#docid#</cfoutput>" onclick="this.form.submit();" /></td>
<td id="docId_<cfoutput>#iter#</cfoutput>"><center><cfoutput>#docId#</cfoutput></center></td>
<td id="docFormName_<cfoutput>#iter#</cfoutput>"><center><cfoutput>#Replace( docFormName, "_", " ", "ALL")#</cfoutput></center></td>
<td id="docOwnerFullName_<cfoutput>#iter#</cfoutput>"><center><cfoutput>#docOwnerFullName#</cfoutput></center></td>
<td id="docCreationTime_<cfoutput>#iter#</cfoutput>"><center><cfoutput>#ListGetAt( docCreationTime, 1, '.')#</cfoutput></center></td>
<td><center>
<a id="delete-docchoice_<cfoutput>#iter#</cfoutput>">Delete</a>
</center></td>
</cfoutput>
</tr>


I wanted them to be able to delete each document, as necessary, so I have used jQuery to define a .click action to call RemoveDocChoice(), which performs some user interaction, then calls a cfc to remove the documentObject attribute from their state, (which basically unhooks the document from the workflow). Here is the javascript and cfajaxproxy definition:

<!--- let's open up our cfajaxproxy to cfcWorkflowLdap --->
<cfajaxproxy cfc="workflow.class.AjaxGeneral" jsclassname="setRemoveDocObjectFromState">

<script type="text/javascript">
$(
function(){
// Get any a tag with an 'id' starting with 'delete-docchoice'.
var jDeleteDocChoice = $( "a[id^='delete-docchoice']" );

// Hook up the click event.
jDeleteDocChoice
.attr( "href", "javascript:void( 0 )" )
.click(
function( objEvent ){
// on a click event, call the RemoveDocChoice function
RemoveDocChoice( objEvent);

// Prevent the default action.
objEvent.preventDefault();
return( false );
}
)
;

}
)

// This removes the doc object choice from the listdocumentsforcurrentstate.cfm.
function RemoveDocChoice( objEvent ){
// get the table row container that we would like to manipulate
var jTableRowContainer = $( "#docchoice_" + objEvent.target.id.split('_')[1] );

// get the text value for docId, so that we don't confuse the end-user with the array element,
// (which doesn't usually match the actual document id number).
var docId = $( "#docId_" + objEvent.target.id.split('_')[1] ).text();

// pop-up a confirmation box for the end-user to OK, or cancel
var answer = confirm("Would you like to delete Form ID " + docId + "?" );

// If the user has answered OK, then we remove the document object from its state
if (answer) {
// instantiate a javascript instance of our cfajaxproxy object
setRemoveDocObjectFromState = new setRemoveDocObjectFromState();

// set variables needed by our CFC to remove the document from its state
var stateDn = "<cfoutput>#Session.State#</cfoutput>";
var docOuBaseDn = "docObjectName=" + docId + ",ou=documents,<cfoutput>#Session.appBaseDn#</cfoutput>";

// we bypass setRemoveDocObjectFromState.setCallbackHandler(docObjectRemovalComplete_Handler) so that we still have
// our table row container within scope. (There is no to pass anything more than the returnStruct from our CFC
// as far as I can tell).
var returnStruct = setRemoveDocObjectFromState.setDelDocObjectFromState(stateDn=stateDn, docOuBaseDn=docOuBaseDn);

// if returnStruct.SUCCESS exists, then the LDAP removal was successful and we remove the container visually
// from the browser. Otherwise, pop-up an alert message and do nothing.
if ( returnStruct.SUCCESS )
jTableRowContainer.fadeOut(100);
} else {
msg = "We were unable to delete your document."
msg += "\n\nThe Server Replied: " + returnStruct.MESSAGE;
alert(msg);
}
}
}
</script>
<!--- end of js and cf inclusions --->


There are others who have a greater knowledge of jQuery and Coldfusion, but I wanted to point out a few things that were interesting to me, and may not have been documented in other places:

1.
<cfajaxproxy cfc="workflow.class.AjaxGeneral" jsclassname="setRemoveDocObjectFromState">


- there are many examples of CFCs being called from the same directory as the cfm which is calling it, but not many examples of loading CFCs in a different directory.
a. Login to the Coldfusion Administrator and choose "Server Settings -> Mappings".
b. Create a new Coldfusion Mapping. I map mine to the webroot of my webserver.
Logical Path - /
Directory Path - /var/www

The path to your CFC starts at the logical path, (your webroot), and ends with the CFC filename:
If path to CFC = /var/www/workflow/class/AjaxGeneral.cfc
then cfc="workflow.class.AjaxGeneral"

If path to CFC = /var/www/workflow/test/class/AjaxGeneral.cfc
then cfc="workflow.test.class.AjaxGeneral"

2. Using a jQuery selector to match dynamic "id" attributes.

- Since these are rows in a table, I have the above HTML wrapped in a cfloop, and simply add the index of the loop to the variables names so that they will be unique. All that we know is that each .click-able element will start with string "delete-docchoice":

<a id="delete-docchoice_<cfoutput>#iter#</cfoutput>">Delete</a>


jQuery has given powerful matching tools, (called selectors), to help us grab the correct elements.

var jDeleteDocChoice = $( "a[id^='delete-docchoice']" );


"a[id^='delete-docchoice']" -- our selector tells jQuery to look for an "a" tag with an "id" attribute starting with 'delete-docchoice'. Check out the following link, http://docs.jquery.com/Selectors, for a listing of options you can use to empower your element searches.

3. objEvent is the event object which is passed into the javascript, and the methods which are used to access information about the object or modify behaviors are documented here.

4. Since our table row container is unique within the table, we need to split the 'id' attribute from the event object and split the number off of the end, (which is the unique number of this row). We will use this number to obtain the correct table row container from the DOM.

var jTableRowContainer = $( "#docchoice_" + objEvent.target.id.split('_')[1] ); 


By using the objEvent.target method, we are able to grab the 'id' attribute from the event object, split it on the underscore, and use the second element, [1], of the array returned by split().

5. I included the Javascript within the listdocumentsforcurrentstate.cfm, (as opposed to the normal .js include file), as I needed access to the Coldfusion Session variables for my CFC call:

        // set variables needed by our CFC to remove the document from its state
var stateDn = "<cfoutput>#Session.State#</cfoutput>";
var docOuBaseDn = "docObjectName=" + docId + ",ou=documents,<cfoutput>#Session.appBaseDn#</cfoutput>";


6. I didn't set a setCallbackHandler, as I needed to keep the jTableRowContainer variable in scope, and didn't want to spend time figuring how to pass it into the CFC and then back out again in the returnStruct. Also, notice that you can pass any number of variables into your CFC function:

            var returnStruct = setRemoveDocObjectFromState.setDelDocObjectFromState(stateDn=stateDn, docOuBaseDn=docOuBaseDn);


7. Here is our CFC function in /var/www/workflow/class/AjaxGeneral.cfc:

    <cffunction name="setDelDocObjectFromState"
access="remote"
return="struct"
hint="deletes an object from the state, returns a struct to javascript
@return result struct">
<cfargument name="stateDn" type="string">
<cfargument name="docOuBaseDn" type="string">

<cfset var returnStruct = structNew() />
<cfset returnStruct.success = true />

<cftry>
<cfset cfcWorkflowLdap.delDocObjectFromState( stateDn, docOuBaseDn )>
<cfcatch type="any">
<cfset returnStruct.success = false />
<cfset returnStruct.message = cfcatch.message />
</cfcatch>
</cftry>
<cfreturn returnStruct />
</cffunction>



It simply calls cfcWorkflowLdap.delDocObjectFromState() to do the work. By default, returnStruct.success equals 'true', but if the call to LDAP errors for any reason, it changes that value to 'false'. NOTE: Any advice on how to make this function more secure from outside calls would be very welcome.

8. Notice that returnStruct.SUCCESS becomes uppercase in transit from Coldfusion to Javascript. Remember this very important note from Charlie Griefer's post, "cfajaxproxy - the other white meat", "javascript is case-sensitive, and ColdFusion converts structure key names to all uppercase".

        // if returnStruct.SUCCESS exists, then the LDAP removal was successful and we remove the container visually 
// from the browser. Otherwise, pop-up an alert message and do nothing.
if ( returnStruct.SUCCESS )
jTableRowContainer.fadeOut(100);

...


9. With jQuery, we make the table row magically disappear:

      jTableRowContainer.fadeOut(100);


Thanks to Charlie Griefer for the following blog posting, "cfajaxproxy - the other white meat", which helped me on my way with cfajaxproxy.

Monday, December 22, 2008

Coldfusion - autosuggest matches from beginning of string by default

After a bit of debugging foo, I realized that autosuggest in Coldfusion 8 matches results from the beginning of the string, therefore a search for "Joh" would give me a drop-down menu to choose the correct "John Smith", but a search for "Smith" would not give me a drop-down menu, even though I would receive the following result set from the server:

info:http: CFC invocation response: ["John Smith"]

Finally, I found that you have to modify CFIDE/scripts/ajax/yui/autocomplete/autocomplete-min.js, which is invoked with the autosuggest attribute and change the following from:

YAHOO.widget.DataSource.prototype.queryMatchContains = false;

To:
YAHOO.widget.DataSource.prototype.queryMatchContains = true;

What a drag for anyone that has to get this changed on a production server...

The answer was found here.

EDIT: A better solution is to copy CFIDE/scripts/ajax/yui/autocomplete/autocomplete-min.js to a folder in your web, and then import that into your header, which preempts the version in CFIDE/scripts/ajax/yui/autocomplete. This is the only solution, if your sys admin doesn't want to make the change globally, (and who would blame them?):
<script type="text/javascript" src="js/autocomplete-min.js"></script>

Friday, December 12, 2008

andLinux - access USB drives

If you need to access USB drives in andLinux, the following wiki post, "Howto: access removable media from andLinux", has the easiest option.

I was already mounting my Windows users profile directory, so I just placed the NTFS folder within Documents/usbDrive, and I can now access the USB hard drive whenever I plug it in. Nice!

/etc/rc.local:
mount -t smbfs -o credentials=/etc/smbpasswd,iocharset=iso8859-1,uid=1001,gid=1001 //192.168.11.1/myuser /home/myuser/win

Tuesday, December 9, 2008

I had an issue where the slapd init scripts were not starting slapd on Ubuntu 8.10 (andLinux). The only errors that I was getting were in syslog:

Dec  9 17:55:10 andLinux slapd[3212]: @(#) $OpenLDAP: slapd 2.4.11 (Nov  8 2008 09:42:18) $ ^Ibuildd@palmer:/build/buildd/openldap-2.4.11/debian/build/servers/slapd
Dec 9 17:55:10 andLinux slapd[3213]: bdb_db_open: database "dc=domain,dc=com" cannot be opened, err 13. Restore from backup!
Dec 9 17:55:10 andLinux slapd[3213]: bdb(dc=domain,dc=com): txn_checkpoint interface requires an environment configured for the transaction subsystem
Dec 9 17:55:10 andLinux slapd[3213]: bdb_db_close: database "dc=domain,dc=com": txn_checkpoint failed: Invalid argument (22).
Dec 9 17:55:10 andLinux slapd[3213]: backend_startup_one: bi_db_open failed! (13)
Dec 9 17:55:10 andLinux slapd[3213]: bdb_db_close: database "dc=domain,dc=com": alock_close failed
Dec 9 17:55:10 andLinux slapd[3213]: slapd stopped.


All errors pointed to a corrupt database, but I _was_ able to start slapd as root, so I checked permissions on the files /var/lib/ldap/:

Dec  9 17:55:10 andLinux slapd[3212]: @(#) $OpenLDAP: slapd 2.4.11 (Nov  8 2008 09:42:18) $ ^Ibuildd@palmer:/build/buildd/openldap-2.4.11/debian/build/servers/slapd
Dec 9 17:55:10 andLinux slapd[3213]: bdb_db_open: database "dc=domain,dc=com" cannot be opened, err 13. Restore from backup!
-rw-r----- 1 openldap openldap 96 Dec 3 15:24 DB_CONFIG
-rw------- 1 root root 24576 Dec 5 10:19 __db.005
-rw------- 1 root root 565248 Dec 5 10:19 __db.004
-rw------- 1 root root 98304 Dec 5 10:19 __db.003
-rw------- 1 root root 2629632 Dec 5 10:19 __db.002
-rw------- 1 root root 8192 Dec 5 10:19 __db.001
-rw-r----- 1 openldap openldap 28672 Dec 9 18:09 objectClass.bdb
-rw-r----- 1 openldap openldap 3408231 Dec 9 18:09 log.0000000001
-rw-r----- 1 openldap openldap 622592 Dec 9 18:09 id2entry.bdb
-rw-r----- 1 openldap openldap 352256 Dec 9 18:09 dn2id.bdb
-rw-r----- 1 openldap openldap 4096 Dec 9 18:09 alock


Ahhh! My sudo slapd -d -1 had kicked me in the bootie... I chown'd all of the files back to openldap.openldap, and sudo /etc/init.d/slapd start(ed) the daemon fine.

Monday, December 8, 2008

Winamp - do not manage USB drive

If you find that winamp is searching your USB drive every time you plug it in, then remove the pmp_usb.ini and winamp_cache_001.xml from the root of your USB device, as described here.

Saturday, December 6, 2008

Coldfusion / Eclipse - debug mappings confusion

I just got Eclipse to debug with a Coldfusion server on a remote host, but am not sure I understand mappings with the Coldfusion Extensions. Here are the specs:

localhost - Windows Vista / Eclipse 3.4 / CFEclipse 1.3.2beta / Coldfusion Extensions 1.0.191910

remote - Ubuntu 8.10 / Coldfusion 8

My mappings were (not working):
localhost - c:\Users\me\Documents\workspace\project
remote - /home/me/workspace/project ( which was a symlink to /home/me/win/Documents/workspace/project on the remote filesystem )

Those mappings gave me the dreaded, "Source Not Found - Edit Source Path Lookup" message in the debugger. During this, Eclipse was showing the template to be at:
/home/me/win/Documents/workspace/project/myfile.cfm

This completely confused me, as I was assuming that Eclipse was confused about which filesystem variable it should be using, and was grabbing the Ubuntu variable by mistake (bug). My assumption was that Eclipse should be showing me a Windows path... I was wrong. Here are the working mappings:

localhost - c:\Users\me\Documents\workspace\project
remote - /home/me/win/Documents/workspace/project

So, Eclipse was displaying the remote path, and Coldfusion, (through RDS), was returning the path that it was trying to access. This apparently isn't tied to the remote mapping set in the debug configuration... Also, it couldn't find the source code through a symlink... Here are two possibilities that I can think of:

1. Coldfusion extensions can't handle symlinks at all
2. Coldfusion extensions can't handle symlinks to (Samba?) mounted filesystems

I don't know, and just needed to get this down while it was fresh. Now it is off to bed... :P I hope this helps someone else, because I see a lot of frustration (on the web) with this particular feature of the Coldfusion Extensions, and though the localhost setups are simple and documented many times, setups between Eclipse and a remote machine are not documented as well.

Here are the links which helped me to make the mental leap:
http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=usingdebugger_4.html
http://www.fusion-reactor.com/fd/featurefocus/sourcecodelookup.cfm

Friday, December 5, 2008

Python 3.0 released

http://news.zdnet.com/2424-9595_22-253863.html

"Python 3.0, also called Python 3000 or Py3K, is the first Python release that is intentionally backwards-incompatible, according to project founder Guido van Rossum.

"Nevertheless, after digesting the changes, you'll find that Python really hasn't changed all that much — by and large, we're mostly fixing well-known annoyances and warts, and removing a lot of old cruft," van Rossum said in a document outlining the changes."

Wednesday, December 3, 2008

WinSCP - FTP transfers

My favorite Windows SCP client, WinSCP, also supports FTP :)

Also, don't forget to create a transfer rule to exclude any Subversion directories:

Exclude */.svn/

openLDAP on Cygwin is very slow

I have converted my dev environment from Debian GNU/Linux to Windows Vista, (to punish myself :P ), and have found that a Cygwin compiled openLDAP (2.4) is too slow for very large subtree searches. As a work-around, I have installed andLinux, (which uses coLinux to run alongside Vista), and have transferred my openLDAP installation into it. andLinux is nice, as they do the X server configuration for you, and allow you to place KDE, (xcfe), tools right on the desktop or in your quick launch.

Be aware that andLinux is currently at Ubuntu Gutsy, so you will need to upgrade to Ubuntu Hardy, then to Ubuntu Intrepid. (Don't go directly to Intrepid, as I found out :)) ).

openldap - slapd.conf to cn=config

With openLDAP 2.4.11, the default configuration file, slapd.conf, has been moved into the LDAP backend. They make conversion easy, by using the following command:

sudo /usr/sbin/slapd -u openldap -g openldap -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d

(This is on Ubuntu Intrepid). Be aware that if you run this command multiple times, you may get duplicate configuration files in /etc/ldap/slapd.d/. To fix this, you simply rm -Rf the offending ldif file:

sudo rm -rf slapd.d/cn=config/cn=schema/cn={2}cosine.ldif