/************************************************************************ BogoFolders is a jQuery plugin providing very a rudimentary folder methaphor, similar to a filesystem browser (e.g. Konqueror or Windows Explorer). It is currently quite BETA and far from feature-complete. Usage: $('#MyFolderHolder').initBogoFolders( { ... general options ... }, { name:"Root Folder", children:[{ ... folder definitions ... }] } ); The arguments are documented below... //////////////////////////////////////////////////////////////////////// General options: .target = Required selector referring to an HTML element capable of holding "content" generated by clicking on folders. It must resolve to a single element, e.g. '#ContentArea'. .icon: optional default icon graphic URI for the folders. If no icons are set then simple text labels are used. .separator: optional separator (HTML) to place between entries. Default is empty string, since style sheets can effectively space the items, but if you want a newline after each item then this is the way to do it. The separator is NOT added after the final item. .parentDirLabel: optional string literal which gets used as the label for the '..' (parent directory) entry. The default is '[..]'. .debuggering: boolean (default=false). Turns on internal debugging info for this plugin. Do not enable it for normal use, but it might be interesting for experimentation and testing purposes. //////////////////////////////////////////////////////////////////////// Folder definitions: These are defined as a tree of objects in this form: {name: String, children:[Array of folder definitions] } Note that the root object is never directly shown, and therefore its properties are pretty insignificant except for .children. Arguments are: .name: stringified label. May be null. .icon: optional icon graphic URI. If both name and icon are null then the results are undefined. .content: content string or function. If it is a function, it is called (with no arguments) to generate the content each time the folder is clicked. How the content is handled is described below. It may be null, but this is normally only useful if .children is not null. .contentType: option string describing the content type. It may be any of the following: a) 'html' (default): the content is added to .target using jQuery.html() b) 'text': the content is added to .target using jQuery.text() c) 'object': the content is assumed to be passable to jQuery.append(). Due to unfortunate implementation details, each folder definition object which is itself a child of another folder will be modified by initBogoTabs(). A field named .parentFolder is added to it, a reference at the parent folder definition. Additionally, other internal-only properties may be added. This is normally not significant but can be a problem if client code needs to traverse the folder objects programatically. This will be fixed when this plugin is extended to use a class to hold folder info. //////////////////////////////////////////////////////////////////////// How content is handled: Each time a folder is clicked, the .target element is emptied and its content is set from the .content field of the clicked folder, using the .contentType hint to determine how to render the content. If .content is null then the target is left empty. If a clicked folder has children then the DOM element containing the folders (that is, the one which is used in $('#Foo').initBogoFolders()) is emptied and the list of folders is replaced with the list of the current folder's children plus a special entry labeled "..", which means "go up one directory." //////////////////////////////////////////////////////////////////////// Known bugs, limitations, and TODOs: BUGS: - Missing several significant features (see TODO list). - When chdir'ing to a folder which itself has no content text, the style of the content area may not be completely rendered (in my test case, the border disappears). Not at all sure what causes this. TODOs: - More flexible content types. Explicit support for AJAX. - More flexibility for the ".." folder, at least to set its icon. - Add option to show the current "path" in the tree, such that the user can click on a specific element to jump to that part of the tree. e.g.: /folder1/folder2 - Bookmarks??? (Local to the current session.) - Ability to use jQuery selectors as content. This cannot work without some internal changes because selecting a folder deletes the content, which means that the pointed-to content could not be re-used. The current workaround is to hide "content areas" at startup and use a content function to fetch the content. e.g. content:function(){ return $('#Foo').html(); } ... $('#Foo').hide(); - Optional ToolTips on the folders. (Requires a tooltip plugin, of course.) - More intelligent auto-deduction of the icon. e.g. a folder icon only if the object has children, else a document icon. - Have two default icons: one for items with children and one for items with no children. (e.g., folder vs document) - Add ability to programmatically click an item? - Add ability to use functions as children, similarly to how content does. The main question is whether or not this function should be called each time the tree is displayed, or only once. (i tend towards each time, as that is much simpler and consistent with the way .content functions are handled.) - Create a class to hold the folder info and a virtual method to get the content. - Add ability to select starting dir. There is code for this but it is failing when a nested item is selected this way. ??? //////////////////////////////////////////////////////////////////////// BogoTabs home page: http://wanderinghorse.net/computing/javascript/jquery/bogofolders/ License: Public Domain Author: stephan beal (http://wanderinghorse.net/home/stephan/) Terse revision history (newest at the top): 20070725: - Fixed: plugin no longer attaches new properties to 'this', to avoid potential collisions with jQuery properties. 200707??: - Fixed: .separator applied after ".." entry in subdirs. - Added opts.parentDirLabel - Believed fixed: incorrect parent dir icon. - s/debugBogoFolders/debuggering, for consistency with my other plugins. 20070716: initial release ************************************************************************/ jQuery.fn.initBogoFolders = function(opts,rootFolder) { var self = this; opts = jQuery.extend({ //icon:'folder.png', separator:'', debuggering:false, parentDirLabel:'..' }, opts ? opts : {}); var dbgdiv = null; var startingDir = rootFolder; var target = $(opts.target); /** Internal debuggering function. */ function dbg(msg) { if( dbgdiv ) dbgdiv.prepend("BogoFolders: "+msg+"
"); }; if( opts.debuggering ) { target.after("
BogoFolders debugging area
").after('
'); dbgdiv = jQuery('#BogoFoldersDebugDiv'); dbgdiv.css('border','1px dashed #000'); dbg("debugging activated."); } /* i hate this. Walk the folders and add parent information so 'cd ..' can work. */ function tagFolders(theFolder,parentFolder) { dbg("Tagging folder named \""+theFolder.name+"\""); theFolder.parentFolder = parentFolder; if( theFolder.selected ) { startingDir = theFolder; } if( ! theFolder.children ) { // looks like it has no walkable children. return; } for( ndx in theFolder.children ) { tagFolders( theFolder.children[ndx], theFolder ); } }; tagFolders(rootFolder,null); function showContent(theFolder) { target.empty(); var ctype = theFolder.contentType ? theFolder.contentType : 'html'; var content = theFolder.content ? theFolder.content : ''; dbg('typeof content == '+typeof(content)); // weird: typeof(null) == 'object' if( 'function' == typeof(content) ) { dbg("Calling .content function."); content = content(); } if( 'object' == typeof(content) ) { ctype = 'object'; } switch( ctype ) { case 'object': target.append( content ); break; case 'html': target.html( content ); break; case 'text': case 'txt': target.text( content ); break; default: target.html("Error: theFolder.contentType could not be determined"); break; }; }; // showContent() var addFolderEntry = function(theFolder) { var label = theFolder.name ? theFolder.name.replace( /\'/, ''' ) : null; if( theFolder.chdirProxy ) { // kludge for chdir() var tmp = theFolder.chdirProxy; delete theFolder.chdirProxy; theFolder = tmp; } var icon = (theFolder.icon || (theFolder.icon===null)) ? theFolder.icon : opts.icon; var anchor = jQuery(""); anchor[0].folderObj = theFolder; theFolder.anchor = anchor; // i don't like this. It's only here to support startingDir. if( icon ) { anchor.append(""+(label ? label : "); } if( label ) { anchor.append( label ); } anchor.click(function(){ showContent( this.folderObj ); if( this.folderObj.children ) chdir( this.folderObj ); return false; }); self.append( anchor ); return self; }; var chdir = function(theFolder) { dbg('chdir('+(theFolder.name?theFolder.name:theFolder.toSource())+")"); self.empty(); if( theFolder.parentFolder ) { var cdup = {name:opts.parentDirLabel,chdirProxy:theFolder.parentFolder}; //dbg("Adding '..' entry "+cdup.toSource()+""); addFolderEntry(cdup); } if( ! theFolder.children ) { return; } var count = theFolder.children.length; if( count && theFolder.parentFolder ) { self.append(opts.separator); } for( var i = 0; i < count; ++i ) { addFolderEntry(theFolder.children[i]); if( (i!=(count-1)) && opts.separator ) { self.append(opts.separator); } } }; chdir( rootFolder ); // i would like to do the following instead, but the problem is that the root node // is special case (blech!) and doesn't get an associated anchor. //if( startingDir ) { // startingDir.anchor.click(); //} showContent( rootFolder ); return self; }; // initBogoFolders()