diff js/jquery.draganddrop.js @ 18:0d557e6e73f7

Added beautytips and some additional event handling code to the library.
author David Eads <eads@chicagotech.org>
date Fri, 06 Mar 2009 14:11:46 -0600
parents js/dnd.js@1a77f87927dd
children 89fe0aca43d4
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/jquery.draganddrop.js	Fri Mar 06 14:11:46 2009 -0600
@@ -0,0 +1,170 @@
+/* jQuery Drag and Drop Library for Rich Editors
+ *
+ * A helper library which provides the ability to drag and drop images to Rich 
+ * Text Editors (RTEs) that use the embedded iframe + DesignMode method. DnD
+ * also provides a simple "clicky" interface for inserting the same markup 
+ * directly into a textarea.  
+ *
+ * Basic usage:
+ *
+ * $('img.my-class').dnd({targets: $('#my-iframe')});
+ *
+ * Options:
+ *
+ * targets (Required):
+ *   A jQuery object corresponding to the proper iframe(s) and/or textarea(s)
+ *   that are allowed drop targets for the current set of elements.
+ *
+ * idSelector:
+ *  A callback that parses out the unique ID of an image that is dropped in an 
+ *  iframe.  While some browsers (such as Firefox and Internet Explorer) allow
+ *  markup to be copied when dragging and dropping, Safari (and assumably
+ *  other webkit based browsers) don't.  The upshot is that the safest bet is
+ *  to parse the element's src URL.  Because querystrings seem to drop 
+ *  consistently across 
+ *
+ * processIframeDrop:
+ *   A callback that defines the mechanism for inserting and rendering the 
+ *   dropped item in an iframe.  The typical usage pattern I expect to see is
+ *   that implementers will listen for their RTE to load and then invoke DnD
+ *   with a processIframeDrop appropriate to their editor.
+ *
+ * processTextAreaDrop:
+ *   A callback that defines the mechanism for inserting and rendering the
+ *   clicked item in a textarea.  The default function just tries to insert
+ *   some markup at the caret location in the current textarea.
+ *
+ * interval:
+ *   How often to check the iframe for a drop, in milliseconds.
+ *
+ * 
+ *
+ * Usage notes:
+ *
+ * Due to cross browser flakiness, there are many many limitations on what is
+ * possible in terms of dragging and dropping to DesignMode enabled iframes.
+ *
+ * To make this work, your "droppable" elements must be image tags.  No ifs, ands, 
+ * or buts:  image tags have the best cross browser behavior when dragging.
+ *
+ * When DnD is initialized, it begins timers which periodically check your 
+ * editor iframe.  If an image is dropped in, DnD takes the element and 
+ * attempts to parse it for a unique ID which you can use to do a lookup for 
+ * an "editor representation."  If an editor representation is found, 
+ * typically the image is replaced with the representation snippet, but you are
+ * free to do whatever you want.
+ *
+ * Because of browser limitations, the safest way to parse the element is to
+ * look at the img's src attribute and use some or all of the image URL. 
+ * In my experience, the best way to deal with this is simply to parse out
+ * generate a query string in the src attribute server side that corresponds 
+ * to the proper representation ID.
+ *
+ * If the target is not an iframe but a textarea, DnD provides a very minimal
+ * system for clicking what would otherwise be dragged to insert markup into 
+ * the textarea.
+ *
+ * DnD is purely a utility library:  to make it work for any particular editor,
+ * CMS, etc:  It is very unlikely it will provide much benefit out of the box.
+ *
+ * Because DnD spawns so many timers, they are stored in a $.data element 
+ * attached to the parent document body.  Implementers should consider clearing
+ * the timers when building systems such as dynamic searches or paging 
+ * functionality to ensure memory usage and performance remains stable.
+ */
+
+(function($) {
+  $.fn.dnd = function(opt) {
+    opt = $.extend({}, {
+      interval: 250,
+      targets: $('iframe, textarea'),
+      processTargets: function(targets) {
+        return targets.each(function() {
+          $('head', $(this).contents()).append('<style type="text/css">img { display: none; } img.dnd-dropped {display: block; }</style>');
+          return this;
+        });
+      },
+      idSelector: function(element) { 
+        if ($(element).is('img')) {
+          return element.src;
+        }
+        return false;
+      }, 
+      processIframeDrop: function(target, dragged, dropped, representation_id) {
+        // Keep a counter of how many times this element was used
+        var count = $.data(target, representation_id +'_count');
+        if (!count) { 
+          count = 1; 
+        } else { 
+          count++;
+        }
+        $.data(target, representation_id +'_count', count);
+        $(dropped).replaceWith('<p id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</p>');
+      },
+      processTextAreaDrop: function(target, clicked, representation_id, e, data) {
+        var snippet = '<div><img src="'+ representation_id +'" /></div>';
+        $(target).replaceSelection(snippet, true);
+        e.preventDefault();
+      }
+
+    }, opt);
+
+    // Initialize plugin
+    var targets = opt.processTargets(opt.targets);
+
+    // Process!
+    return this.each(function() {
+      if ($(this).is('img')) {
+        var element = this, $element = $(element);
+
+        // If we don't have a proper id, bail
+        var representation_id = opt.idSelector(element);
+
+        if (!representation_id) {
+          return this;
+        };
+
+        // Add a special class
+        $(element).addClass('dnd-processed');
+
+        // We need to differentiate behavior based on the targets... I guess.
+        targets.each(function() {
+          var target = this, $target = $(target);
+          if ($target.is('iframe')) {
+
+            // Indicate this element is draggy
+            $element.css('cursor', 'move');
+
+            // Watch the iframe for changes
+            var t = setInterval(function() {              
+              $('img:not(.dnd-dropped)', $(target).contents()).each(function() {
+                var dropped = this;
+                if (opt.idSelector(dropped) == representation_id) {
+                  opt.processIframeDrop(target, element, dropped, representation_id);
+                }
+              });
+            }, opt.interval);
+
+            // Track current active timers -- developers working with DnD
+            // can implement their own garbage collection for specific 
+            // interactions, such as paging or live search.
+            var data = $(document).data('dnd_timers');
+            if (data) {
+              data[data.length] = t;
+            } else {
+              data = new Array();
+              data[0] = t;
+            }
+            $(document).data('dnd_timers', data);
+
+          } else if ($target.is('textarea')) {
+            $element.css('cursor', 'pointer');
+            $(element).click(function(e, data) {
+              opt.processTextAreaDrop(target, element, representation_id, e, data);
+            });
+          }
+        });
+      }
+    });
+  };
+})(jQuery);