changeset 12:a5b2b9fa2a1a

Cross browser compatibility changes -- winnowing the scope of possible configurations.
author David Eads <eads@chicagotech.org>
date Fri, 27 Feb 2009 11:50:59 -0600
parents 99ba5941779c
children d0e7287c7a55
files dnd.module dnd_test/dnd_test.css dnd_test/dnd_test.module dnd_test/img/icon/L.png dnd_test/img/icon/M.png dnd_test/img/icon/S.png js/dnd.js
diffstat 7 files changed, 96 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/dnd.module	Fri Feb 27 00:49:22 2009 -0600
+++ b/dnd.module	Fri Feb 27 11:50:59 2009 -0600
@@ -77,6 +77,7 @@
 function dnd_process_textarea($element, $form_state) {
   if ($element['#dnd-enabled']) {
 
+    drupal_add_js(drupal_get_path('module', 'dnd') .'/js/jquery.url.packed.js', 'footer');
     drupal_add_js(drupal_get_path('module', 'dnd') .'/js/dnd.js', 'footer');
     drupal_add_js(drupal_get_path('module', 'dnd') .'/js/dnd-library.js', 'footer');
 
--- a/dnd_test/dnd_test.css	Fri Feb 27 00:49:22 2009 -0600
+++ b/dnd_test/dnd_test.css	Fri Feb 27 11:50:59 2009 -0600
@@ -130,5 +130,5 @@
   padding: 4px 0;
 }
 .dnd-library-wrapper .editor-item.dnd-child-inserted {
-  background-color: #ffd;
+  background-color: #ccc;
 }
--- a/dnd_test/dnd_test.module	Fri Feb 27 00:49:22 2009 -0600
+++ b/dnd_test/dnd_test.module	Fri Feb 27 11:50:59 2009 -0600
@@ -26,6 +26,10 @@
  * This demonstrates how to attach Drag and Drop to a given textarea.
  */
 function dnd_test_form_alter(&$form, &$form_state) {
+
+  //global $_SERVER;
+  //dpm($_SERVER);
+
   if ($form['#id'] == 'node-form' && $form['type']['#value'] == 'page') {
     drupal_add_css(drupal_get_path('module', 'dnd_test') .'/dnd_test.css');
     $form['body_field']['body']['#dnd-enabled'] = TRUE;
@@ -94,7 +98,7 @@
 function dnd_editor_items($i) {
   $item = array();
   foreach(array(t('S'), t('M'), t('L')) as $size) {
-    $item['item-'. $i .'-'. $size] = theme('dnd_editor_item', $i, $size);
+    $item[$i .'-'. $size] = theme('dnd_editor_item', $i, $size);
   }
   return $item;
 }
@@ -103,6 +107,8 @@
  * Completely contrived edit item theme function
  */
 function template_preprocess_dnd_library_item(&$variables) {
+  global $_SERVER;
+
   $i = $variables['i'];
 
   if ($i % 3 == 0) {
@@ -115,16 +121,13 @@
     $img = 1;
   }
 
-  $variables['image'] = theme('image', drupal_get_path('module', 'dnd_test') .'/img/item-'. $img .'-thumb.jpg');
+  //$variables['image'] = theme('image', drupal_get_path('module', 'dnd_test') .'/img/item-'. $img .'-thumb.jpg');
+  $variables['image'] = '<img src="http://'. $_SERVER['SERVER_ADDR'] . base_path() . drupal_get_path('module', 'dnd_test') .'/img/item-'. $img .'-thumb.jpg?dnd_id='. $i .'-M" class="drop" />';
   $variables['title'] = t('Lorem Ipsum @count', array('@count' => $i));
   $variables['date'] = 'Feb 18 2009';
   $variables['author'] = 'David Eads';
   foreach(array(t('S'), t('M'), t('L')) as $size) {
-    $attributes = array(
-      'class' => 'drop size-'. $size, 
-      'id' => 'item-'. $i .'-'. $size,
-    );
-    $sizes[] = theme('image', drupal_get_path('module', 'dnd_test') .'/img/icon/'. $size .'.png', $size, $size, $attributes);
+    $sizes[] = '<img src="http://'. $_SERVER['SERVER_ADDR'] . base_path() . drupal_get_path('module', 'dnd_test') .'/img/icon/'. $size .'.png?dnd_id='. $i .'-'. $size .'" class="drop" />';
   }
   $variables['sizes'] = '<ul><li>'. implode('</li><li>', $sizes) .'</li></ul>';
 }
Binary file dnd_test/img/icon/L.png has changed
Binary file dnd_test/img/icon/M.png has changed
Binary file dnd_test/img/icon/S.png has changed
--- a/js/dnd.js	Fri Feb 27 00:49:22 2009 -0600
+++ b/js/dnd.js	Fri Feb 27 11:50:59 2009 -0600
@@ -1,44 +1,13 @@
 /* jQuery Drag and Drop Library for Rich Editors
  *
- * This library exists to provide a jQuery plugin that manages dragging
- * and dropping of page assets into textareas and rich-text editors which use
- * the iframe + designMode method.
- *
- * This plugin has a rather elaborate set of considerations based on common
- * configurations of rich text editors and serious disparity in how browsers
- * handle dragging and dropping assets into an iframe.
- *
- * The plugin scans an iframe with an embedded document with designMode enabled
- * and tries to detect content that was injected by dragging and dropping
- * within the browser.  If it detects an injection of content, it attempts to 
- * conjure up an "editor representation" of the content based on the markup
- * passed in.
- *
- * This works because links and images drop with their full HTML syntax in-tact
- * across most browsers and platforms.  This means we can set a timer on the
- * iframe that scans for the insertion of the markup and replace it with
- * another HTML snippet, as well as triggering actions inside the editor itself
- * (this will perhaps be handled by a set of editor-specific plugins).
- *
- * Because of the mechanism of operation, it is expected that the iframe
- * contain CSS which hides the markup dropped in to create the illusion of
- * seamlessly dropping in the "editor representation" of the dropped item.
- *
- * It is expected that the implementer will be parsing the input text for 
- * the proper markup on the server side and making some decisions about how 
- * to parse and handle that markup on load and save.  
- *
- * Of special interest is graceful degradation.  There is no ideal graceful
- * degradation path at this time.  Every mainstream browser except IE 6 and 
- * IE 7 drop links and images into textareas with their href and src URIs, 
- * respectively, including querystrings.  That means that the image or link
- * url can be used for parsing, or a querystring included.  But it won't work
- * in Internet Explorer, and probably will require a server-side browser check
- * in full blown implementations of the library system.
+ * A helper library which provides the ability to drag and drop images to Rich 
+ * Text Editors (RTEs) that use the embedded iframe + DesignMode method, and
+ * provides a simple "clicky" interface for inserting the same markup directly
+ * into a textarea.  
  *
  * Basic usage:
  *
- * $('a.my-class').dnd({targets: $('#my-iframe')});
+ * $('img.my-class').dnd({targets: $('#my-iframe')});
  *
  * Options:
  *
@@ -80,36 +49,55 @@
  * postprocessDrop:
  *   A callback that postprocesses the iframe.
  *
+ * interval:
+ *   How often to check the iframe for a drop, in milliseconds.
+ *
+ * Usage notes:
+ *
+ * This is a very tricky problem and to achieve cross browser (as of writing, 
+ * IE, Safari, and Firefox) support, severe limitations must be made on the 
+ * nature of DOM elements that are dropped.
+ *
+ * Droppable elements must be <img> tags.  Internet Explorer will not accept
+ * anchors, and Safari strips attributes and additional markup from
+ * dropped anchors, images, and snippets.
+ *
+ * While the idSelector option allows you to parse the dropped element for
+ * any attribute that "comes along" with the element when it is dropped in a
+ * designmode-enabled iframe, the only safe attribute to scan is the 'src' 
+ * attribute, and to avoid strange "relativization" of links, the src should
+ * always be expressed as an absolute url with a fully qualified domain name.
+ *
+ * Implementation notes and todos:
+ *
+ * Currently, there is no garbage collection instituted for the many many
+ * timers that are created, so memory usage could become as issue in scenarios
+ * where the user does a lot of paging or otherwise winds up invoking 
+ * drag and drop on large numbers of elements.
  */
 
 (function($) {
   $.fn.dnd = function(opt) {
     opt = $.extend({}, {
       dropWrapper: '<p class="dnd-dropped"></p>',
-      insertBefore: '',
-      insertAfter: '',
+      insertBefore: false,
+      insertAfter: false,
       processedClass:  'dnd-processed',
-      disableClick: true,
+      interval: 100,
 
       processTargets: function(targets) {
         return targets.each(function() {
-          $('head', $(this).contents()).append('<style type="text/css">.dnd-processed { display: none; }</style>');
+          //$('head', $(this).contents()).append('<style type="text/css">.dnd-processed { display: none; }</style>');
+          //@TODO use jQuery.rules()
           return this;
         });
       },
 
       // Must return a string
       idSelector: function(element) { 
-        if (!element.id) {
-          // @TODO sanitize output here
-          if ($(element).is('a')) {
-            return element.href;
-          }
-          if ($(element).is('img')) {
-            return element.src;
-          }
+        if ($(element).is('img')) {
+          return $.url.setUrl(element.src).param('dnd_id');
         }
-        return element.id; 
       }, 
 
       // @TODO:  Target should be jQuery object
@@ -131,6 +119,7 @@
 
       // Back out markup to render in place after parent container
       preprocessDrop: function(target, drop) {
+        return drop;
         var old_parent = false;
         var element_id = '';
 
@@ -154,46 +143,58 @@
 
     // Initialize plugin
     var targets = opt.processTargets(opt.targets);
-    if (opt.disableClick) { this.click(function() { return false; }); }
 
     // Process!
     return this.each(function() {
+      if ($(this).is('img')) {
+        var element = this;
 
-      var element = this;
-      var representation_id = opt.idSelector(element);
+        // If we don't have a proper id, bail
+        var representation_id = opt.idSelector(element);
 
-      if (!element.id) {
-        element.id = element.tagName.toLowerCase() + '-' + representation_id;
+        if (!representation_id) {
+          return this;
+        };
+
+        // Add some UI sugar and a special class
+        $(element)
+          .css('cursor', 'move')
+          .addClass(opt.processedClass);
+
+        // We need to differentiate behavior based on the targets... I guess.
+        targets.each(function() {
+          if ($(this).is('iframe')) {
+            var target = this;
+            var selector = 'img[src='+ element.src +']';
+
+            // Watch the iframe for changes
+            var t = setInterval(function() {              
+              var match = $(selector, $(target).contents());
+              if (match.length > 0) {
+                var drop = opt.preprocessDrop(target, match); // Must return a jquery object
+                var representation = opt.renderRepresentation(target, drop, representation_id);
+    
+                if (representation) {
+                  if (opt.dropWrapper) {
+                    drop.wrap(opt.dropWrapper);
+                  }
+                  if (opt.insertBefore) {
+                    drop.before(opt.insertBefore);
+                  }
+                  if (opt.insertAfter) {
+                    drop.after(opt.insertAfter);
+                  }
+                  drop.replaceWith(representation);
+                  opt.postprocessDrop(target, drop, element);
+                }
+              }
+            }, opt.interval);
+            // @TODO track the timer with $.data() so we can clear it?
+          } else if ($(this).is('textarea')) {
+            //console.log('@TODO handle textareas via.... regexp?');
+          }
+        });
       }
-
-      // Add some UI sugar and a special class
-      $(element)
-        .css('cursor', 'move')
-        .addClass(opt.processedClass);
-
-      // We need to differentiate behavior based on the targets... I guess.
-      targets.each(function() {
-        if ($(this).is('iframe')) {
-          var target = this;
-
-          // Watch the iframe for changes
-          var t = setInterval(function() {
-            var match = $('#' + element.id, $(target).contents());
-            if (match.length > 0) {
-              var drop = opt.preprocessDrop(target, match); // Must return a jquery object
-              drop
-                .before(opt.insertBefore)
-                .after(opt.insertAfter)
-                .wrap(opt.dropWrapper)
-                .replaceWith(opt.renderRepresentation(target, drop, representation_id));
-              opt.postprocessDrop(target, drop, element);
-            }
-          }, 100);
-          // @TODO track the timer with $.data() so we can clear it?
-        } else if ($(this).is('textarea')) {
-          //console.log('@TODO handle textareas via.... regexp?');
-        }
-      });
     });
   };
 })(jQuery);