changeset 20:89fe0aca43d4

Refactored the entire interval system, and started on form behavior.
author David Eads <eads@chicagotech.org>
date Sun, 08 Mar 2009 20:29:57 -0500
parents d83073a08b25
children 69db20fdbac2
files dnd.module dnd_test/dnd_test.module js/bt/.jquery.bt.js.swp js/dnd-library.js js/jquery.draganddrop.js
diffstat 5 files changed, 120 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- a/dnd.module	Fri Mar 06 14:26:06 2009 -0600
+++ b/dnd.module	Sun Mar 08 20:29:57 2009 -0500
@@ -76,10 +76,18 @@
  */
 function dnd_process_textarea($element, $form_state) {
   if ($element['#dnd-enabled']) {
+
+    // BeautyTips
     drupal_add_js(drupal_get_path('module', 'dnd') .'/js/bt/other_libs/excanvas_0002/excanvas-compressed.js');
+    drupal_add_js(drupal_get_path('module', 'dnd') .'/js/bt/other_libs/jquery.hoverIntent.minified.js');
+    drupal_add_js(drupal_get_path('module', 'dnd') .'/js/bt/jquery.bt.js');
+
+    // Dependencies
     drupal_add_js(drupal_get_path('module', 'dnd') .'/js/jquery.url.packed.js');
     drupal_add_js(drupal_get_path('module', 'dnd') .'/js/jquery.fieldselection.js');
-    drupal_add_js(drupal_get_path('module', 'dnd') .'/js/bt/jquery.bt.js');
+    drupal_add_js('misc/jquery.form.js');
+
+    // Drag and drop
     drupal_add_js(drupal_get_path('module', 'dnd') .'/js/jquery.draganddrop.js');
     drupal_add_js(drupal_get_path('module', 'dnd') .'/js/dnd-library.js');
 
@@ -135,14 +143,14 @@
   // Generate an array of editor representations to add
   if (is_array($json['editor_representations'])) {
     foreach ($json['editor_representations'] as $editor_id=>$editor_item) {
-      $editor_representations[$editor_id] = filter_xss_admin($editor_item);
+      $editor_representations[$editor_id] = $editor_item;
     }
   }
 
   // Generate an array of library previews to add
   if (is_array($json['library_previews'])) {
     foreach ($json['library_previews'] as $preview_id=>$preview_item) {
-      $library_previews[$preview_id] = filter_xss_admin($preview_item);
+      $library_previews[$preview_id] = $preview_item;
     }
   }
 
@@ -153,7 +161,7 @@
   ), 'setting');
 
   $variables['library_id'] = $settings['library_id'];
-  $variables['header']     = filter_xss_admin($json['header']);
-  $variables['library']    = filter_xss_admin($json['library']);
-  $variables['footer']     = filter_xss_admin($json['footer']);
+  $variables['header']     = $json['header'];
+  $variables['library']    = $json['library'];
+  $variables['footer']     = $json['footer'];
 }
--- a/dnd_test/dnd_test.module	Fri Mar 06 14:26:06 2009 -0600
+++ b/dnd_test/dnd_test.module	Sun Mar 08 20:29:57 2009 -0500
@@ -54,6 +54,10 @@
       'arguments' => array('i' => NULL),
       'template'  => 'dnd-library-preview',
     ),
+    'dnd_library_header' => array(
+      'arguments' => array('page' => NULL),
+      'template'  => 'dnd-library-header',
+    ),
   );
 }
 
@@ -64,7 +68,7 @@
   $page = ($_GET['page']) ? $_GET['page'] : 1;
   $test_library = dnd_test_generate_library($page);
   return drupal_json(array(
-    'header'  => '<h3>'. t('Test library: Page @page', array('@page' => $page)) .'</h3>',
+    'header'  => theme('dnd_library_header', $page),
     'library' => $test_library['library'],
     'editor_representations' => $test_library['editor_representations'],
     'library_previews' => $test_library['library_previews'],
@@ -150,7 +154,6 @@
   $variables['sizes'] = '<ul><li>'. implode('</li><li>', $sizes) .'</li></ul>';
 }
 
-
 function template_preprocess_dnd_editor_item(&$variables) {
   list($i, $size) = array($variables['i'], $variables['size']);
 
@@ -165,3 +168,5 @@
   }
   $variables['image'] = theme('image', drupal_get_path('module', 'dnd_test') .'/img/item-'. $img .'-'. $size .'.jpg', 'foo', 'foo', array('class' => 'dnd-dropped'));
 }
+
+function template_preprocess_dnd_library_header(&$variables) {}
Binary file js/bt/.jquery.bt.js.swp has changed
--- a/js/dnd-library.js	Fri Mar 06 14:26:06 2009 -0600
+++ b/js/dnd-library.js	Sun Mar 08 20:29:57 2009 -0500
@@ -32,36 +32,41 @@
     // This is a bad hack to lop off '-dnd-library' from the id to get the editor name
     var $editor = $('#' + this.id.slice(0, -12)); 
 
-
     // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg
     $editor.bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library);
     $editor.bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library);
 
+    // Add preview behavior to editor items (thanks, BeautyTips!)
     $('.editor-item', context).each(function () {
       $(this).bt(Drupal.settings.dndLibraryPreviews[this.id], {
-        'trigger' : 'none',
-        'width' : 300,
-        'spikeLength' : 7,
-        'spikeGirth' : 9,
+        'trigger': 'none',
+        'width': 300,
+        'spikeLength': 7,
+        'spikeGirth': 9,
         'corner-radius' : 3,
-        'strokeWidth' : 1,
-        'fill' : '#eee',
+        'strokeWidth': 1,
+        'fill': '#ffd',
         'strokeStyle': '#555'
       });
-      $(this).hover(function() {
-        var $this = $(this);
-        this.btOn();
-        // Remove the preview once dragging of any image has commenced
-        $('img', $this).bind('drag', function(e) {
-          $this.btOff();
-        });
-      }, function() {
-        this.btOff();
+      $(this).hoverIntent({
+        'interval': 500,
+        'timeout' : 0,
+        'over': function() {
+          var $this = $(this);
+          this.btOn();
+          // Remove the preview once dragging of any image has commenced
+          $('img', $this).bind('drag', function(e) {
+            $this.btOff();
+          });
+          $('img', $this).bind('click', function(e) {
+            $this.btOff();
+          });
+        }, 
+        'out': function() { this.btOff(); }
       });
     });
 
-
-    // Ajax pager
+    // Simple AJAX pager
     $('.pager a', $this).click(function() {
       $.getJSON(this.href, function(data) {
         Drupal.behaviors.dndLibrary.refreshLibrary.call($this.get(0), data, $editor);
@@ -70,6 +75,18 @@
       return false;
     });
 
+    $('.view-filters form', $this).submit(function() {
+      $(this).ajaxSubmit({
+        // @TODO add URL from settings and target more specifically...
+        'dataType': 'json',
+        'success': function(responsetext, statustext) {
+          drupal.behaviors.dndlibrary.responsetext.call($this.get(0), responsetext, $editor);
+          drupal.behaviors.dndlibrary(); 
+        }
+      });
+      return false;
+    });
+
     // Preload images in editor representations
     var cached = $.data($editor, 'dnd_preload') || {};
     for (editor_id in Drupal.settings.dndEditorRepresentations) {
@@ -132,7 +149,7 @@
 Drupal.behaviors.dndLibrary.attach_none = function(data, settings) {
   settings = $.extend({
     targets: $('#'+ data.field),
-    processTextAreaDrop: function(target, clicked, representation_id, e, data) {
+    processTextAreaClick: function(target, clicked, representation_id, e, data) {
       var snippet = '<p class="dnd-dropped-wrapper">' + Drupal.settings.dndEditorRepresentations[representation_id] + '</p>';
       $(target).replaceSelection(snippet, true);
     }
@@ -172,13 +189,13 @@
 
   settings = $.extend({
     targets: $('#'+ data.field +'-wrapper iframe'),
-    interval: 100,
-    processIframeDrop: function(target, dragged, dropped, representation_id) {
+    processIframeDrop: function(drop, id_selector) {
+      var representation_id = id_selector.call(this, drop);
       var representation = Drupal.settings.dndEditorRepresentations[representation_id];
-      var $target = $(target), $dropped = $(dropped), $dragged = $(dragged), block;
+      var target = this, $target = $(target), $drop = $(drop), block;
 
       // Search through block level parents
-      $dropped.parents().each(function() {
+      $drop.parents().each(function() {
         var $this = $(this);
         if ($this.css('display') == 'block') {
           block = this;
@@ -187,7 +204,7 @@
       });
 
       // Remove dropped item
-      $dropped.remove();
+      $drop.remove();
 
       // Create an element to insert
       var insert = dom.create('p', {'class' : 'dnd-dropped-wrapper', 'id' : 'dnd-inserted'}, representation);
@@ -236,7 +253,7 @@
       // If the next item exists and isn't an editor representation, drop the
       // caret at the beginning of the element, otherwise make a new paragraph
       // to advance the caret to.
-      if (next && !$(next).hasClass('dnd-dropped-wrapper')) {
+      if (next && !$(next).hasClass('dnd-droped-wrapper')) {
         $(next).prepend('<span id="__caret">_</span>');
       }
       else {
@@ -252,11 +269,6 @@
       s.select(c);
       ed.execCommand('Delete', false, null);
       dom.remove(c);
-
-      // Add some classes to the library items
-      $(dragged).addClass('dnd-inserted'); 
-      drag_parents = $(dragged).parents('.editor-item');
-      drag_parents.addClass('dnd-child-inserted');
     }
   }, settings);
 
--- a/js/jquery.draganddrop.js	Fri Mar 06 14:26:06 2009 -0600
+++ b/js/jquery.draganddrop.js	Sun Mar 08 20:29:57 2009 -0500
@@ -1,4 +1,5 @@
-/* jQuery Drag and Drop Library for Rich Editors
+/**
+ * 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
@@ -7,7 +8,7 @@
  *
  * Basic usage:
  *
- * $('img.my-class').dnd({targets: $('#my-iframe')});
+ * $('img.my-draggable-class').dnd({targets: $('#my-rte-iframe')});
  *
  * Options:
  *
@@ -21,24 +22,34 @@
  *  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 
+ *  consistently across browsers, encoding the id in a query string is a good
+ *  option.
  *
  * 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.
+ *   with a processIframeDrop appropriate to their editor.  For performance
+ *   reasons, this only runs once per invocation of DnD -- to compensate, we 
+ *   must pass in an idselector callback as an argument. 
  *
- * processTextAreaDrop:
+ * processTextAreaClick:
  *   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.
  *
+ * iframeTargetClass:
+ *   A class to add to a draggable item if it can be dragged to an iframe.
+ *
+ * textareaTargetClass:
+ *   A class to add to a draggable item if it can be dragged to a textarea.
+ *
+ * processClass:
+ *   A class to add to draggable items processed by DnD.
+ *
  * 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
@@ -47,7 +58,7 @@
  * 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 
+ * When DnD is invoked, it begins a timer which periodically checks 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, 
@@ -64,9 +75,6 @@
  * 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 
@@ -90,34 +98,49 @@
         }
         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>');
+      processIframeDrop: function(drop, id_selector) {
+        var representation_id = opt.idSelector(drop);
+        $(drop).replaceWith(representation_id).wrap('<p class="dnd-dropped"></p>');
       },
-      processTextAreaDrop: function(target, clicked, representation_id, e, data) {
+      processTextAreaClick: function(target, clicked, representation_id) {
         var snippet = '<div><img src="'+ representation_id +'" /></div>';
         $(target).replaceSelection(snippet, true);
         e.preventDefault();
-      }
+      },
+      processIframeClick: function (target, clicked, representation_id) { return true; },
+
+      iframeTargetClass: 'dnd-iframe-target',
+      textareaTargetClass: 'dnd-textarea-target',
+      processedClass: 'dnd-processed'
 
     }, opt);
 
     // Initialize plugin
     var targets = opt.processTargets(opt.targets);
 
-    // Process!
+    // Watch iframes for changes
+    $(targets).filter('iframe').each(function() {
+      var target = this; 
+      var t = setInterval(function() {              
+        $('img:not(.dnd-dropped)', $(target).contents()).each(function() {
+          opt.processIframeDrop.call(target, this, opt.idSelector);
+        });
+      }, 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[target.id] = t;
+      $(document).data('dnd_timers', data);
+    });
+
+    // Process each draggable element
     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) {
@@ -125,42 +148,20 @@
         };
 
         // Add a special class
-        $(element).addClass('dnd-processed');
+        $(element).addClass(opt.processedClass);
 
-        // We need to differentiate behavior based on the targets... I guess.
+        // We need to differentiate behavior based on the targets
         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);
-
+            $(element).addClass(opt.iframeTargetClass);
+            $(element).click(function() {
+              opt.processIframeClick.call(target, element, representation_id);
+            });
           } else if ($target.is('textarea')) {
-            $element.css('cursor', 'pointer');
-            $(element).click(function(e, data) {
-              opt.processTextAreaDrop(target, element, representation_id, e, data);
+            $(element).addClass(opt.textareaTargetClass);
+            $(element).click(function() {
+              opt.processTextAreaClick.call(target, element, representation_id);
             });
           }
         });