annotate js/dnd.js @ 16:bb68dc3ad56f

Major refactor to provide better TinyMCE support and less configuration options, added new jquery dependency, etc.
author David Eads <eads@chicagotech.org>
date Tue, 03 Mar 2009 16:57:39 -0600
parents 7a5f74482ee3
children 1a77f87927dd
rev   line source
eads@3 1 /* jQuery Drag and Drop Library for Rich Editors
eads@3 2 *
eads@12 3 * A helper library which provides the ability to drag and drop images to Rich
eads@16 4 * Text Editors (RTEs) that use the embedded iframe + DesignMode method. DnD
eads@16 5 * also provides a simple "clicky" interface for inserting the same markup
eads@16 6 * directly into a textarea.
eads@3 7 *
eads@3 8 * Basic usage:
eads@3 9 *
eads@12 10 * $('img.my-class').dnd({targets: $('#my-iframe')});
eads@3 11 *
eads@3 12 * Options:
eads@3 13 *
eads@16 14 * targets (Required):
eads@16 15 * A jQuery object corresponding to the proper iframe(s) and/or textarea(s)
eads@16 16 * that are allowed drop targets for the current set of elements.
eads@3 17 *
eads@16 18 * idSelector:
eads@16 19 * A callback that parses out the unique ID of an image that is dropped in an
eads@16 20 * iframe. While some browsers (such as Firefox and Internet Explorer) allow
eads@16 21 * markup to be copied when dragging and dropping, Safari (and assumably
eads@16 22 * other webkit based browsers) don't. The upshot is that the safest bet is
eads@16 23 * to parse the element's src URL. Because querystrings seem to drop
eads@16 24 * consistently across
eads@3 25 *
eads@16 26 * processIframeDrop:
eads@16 27 * A callback that defines the mechanism for inserting and rendering the
eads@16 28 * dropped item in an iframe. The typical usage pattern I expect to see is
eads@16 29 * that implementers will listen for their RTE to load and then invoke DnD
eads@16 30 * with a processIframeDrop appropriate to their editor.
eads@3 31 *
eads@16 32 * processTextAreaDrop:
eads@16 33 * A callback that defines the mechanism for inserting and rendering the
eads@16 34 * clicked item in a textarea. The default function just tries to insert
eads@16 35 * some markup at the caret location in the current textarea.
eads@3 36 *
eads@12 37 * interval:
eads@12 38 * How often to check the iframe for a drop, in milliseconds.
eads@12 39 *
eads@16 40 *
eads@16 41 *
eads@12 42 * Usage notes:
eads@12 43 *
eads@16 44 * Due to cross browser flakiness, there are many many limitations on what is
eads@16 45 * possible in terms of dragging and dropping to DesignMode enabled iframes.
eads@12 46 *
eads@16 47 * To make this work, your "droppable" elements must be image tags. No ifs, ands,
eads@16 48 * or buts: image tags have the best cross browser behavior when dragging.
eads@12 49 *
eads@16 50 * When DnD is initialized, it begins timers which periodically check your
eads@16 51 * editor iframe. If an image is dropped in, DnD takes the element and
eads@16 52 * attempts to parse it for a unique ID which you can use to do a lookup for
eads@16 53 * an "editor representation." If an editor representation is found,
eads@16 54 * typically the image is replaced with the representation snippet, but you are
eads@16 55 * free to do whatever you want.
eads@12 56 *
eads@16 57 * Because of browser limitations, the safest way to parse the element is to
eads@16 58 * look at the img's src attribute and use some or all of the image URL.
eads@16 59 * In my experience, the best way to deal with this is simply to parse out
eads@16 60 * generate a query string in the src attribute server side that corresponds
eads@16 61 * to the proper representation ID.
eads@12 62 *
eads@16 63 * If the target is not an iframe but a textarea, DnD provides a very minimal
eads@16 64 * system for clicking what would otherwise be dragged to insert markup into
eads@16 65 * the textarea.
eads@16 66 *
eads@16 67 * DnD is purely a utility library: to make it work for any particular editor,
eads@16 68 * CMS, etc: It is very unlikely it will provide much benefit out of the box.
eads@16 69 *
eads@16 70 * Because DnD spawns so many timers, they are stored in a $.data element
eads@16 71 * attached to the parent document body. Implementers should consider clearing
eads@16 72 * the timers when building systems such as dynamic searches or paging
eads@16 73 * functionality to ensure memory usage and performance remains stable.
eads@3 74 */
eads@3 75
eads@3 76 (function($) {
eads@3 77 $.fn.dnd = function(opt) {
eads@3 78 opt = $.extend({}, {
eads@15 79 interval: 250,
eads@16 80 targets: $('iframe, textarea'),
eads@3 81 processTargets: function(targets) {
eads@3 82 return targets.each(function() {
eads@15 83 $('head', $(this).contents()).append('<style type="text/css">img { display: none; } img.dnd-dropped {display: block; }</style>');
eads@3 84 return this;
eads@3 85 });
eads@3 86 },
eads@3 87 idSelector: function(element) {
eads@12 88 if ($(element).is('img')) {
eads@16 89 return element.src;
eads@3 90 }
eads@16 91 return false;
eads@3 92 },
eads@16 93 processIframeDrop: function(target, dragged, dropped, representation_id) {
eads@3 94 // Keep a counter of how many times this element was used
eads@3 95 var count = $.data(target, representation_id +'_count');
eads@3 96 if (!count) {
eads@3 97 count = 1;
eads@3 98 } else {
eads@3 99 count++;
eads@3 100 }
eads@3 101 $.data(target, representation_id +'_count', count);
eads@16 102 $(dropped).replaceWith('<p id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</p>');
eads@3 103 },
eads@16 104 processTextAreaDrop: function(target, clicked, representation_id, e, data) {
eads@16 105 var snippet = '<div><img src="'+ representation_id +'" /></div>';
eads@16 106 $(target).replaceSelection(snippet, true);
eads@16 107 e.preventDefault();
eads@16 108 }
eads@3 109
eads@3 110 }, opt);
eads@3 111
eads@3 112 // Initialize plugin
eads@3 113 var targets = opt.processTargets(opt.targets);
eads@3 114
eads@3 115 // Process!
eads@3 116 return this.each(function() {
eads@12 117 if ($(this).is('img')) {
eads@16 118 var element = this, $element = $(element);
eads@3 119
eads@12 120 // If we don't have a proper id, bail
eads@12 121 var representation_id = opt.idSelector(element);
eads@3 122
eads@12 123 if (!representation_id) {
eads@12 124 return this;
eads@12 125 };
eads@12 126
eads@16 127 // Add a special class
eads@16 128 $(element).addClass('dnd-processed');
eads@12 129
eads@12 130 // We need to differentiate behavior based on the targets... I guess.
eads@12 131 targets.each(function() {
eads@16 132 var target = this, $target = $(target);
eads@16 133 if ($target.is('iframe')) {
eads@15 134
eads@16 135 // Indicate this element is draggy
eads@16 136 $element.css('cursor', 'move');
eads@16 137
eads@12 138
eads@12 139 // Watch the iframe for changes
eads@12 140 var t = setInterval(function() {
eads@16 141 $('img:not(.dnd-dropped)', $(target).contents()).each(function() {
eads@16 142 var dropped = this;
eads@16 143 if (opt.idSelector(dropped) == representation_id) {
eads@16 144 opt.processIframeDrop(target, element, dropped, representation_id);
eads@16 145 }
eads@14 146 });
eads@12 147 }, opt.interval);
eads@15 148
eads@16 149 // Track current active timers -- developers working with DnD
eads@16 150 // can implement their own garbage collection for specific
eads@16 151 // interactions, such as paging or live search.
eads@15 152 var data = $(document).data('dnd_timers');
eads@15 153 if (data) {
eads@15 154 data[data.length] = t;
eads@15 155 } else {
eads@15 156 data = new Array();
eads@15 157 data[0] = t;
eads@15 158 }
eads@15 159 $(document).data('dnd_timers', data);
eads@15 160
eads@16 161 } else if ($target.is('textarea')) {
eads@16 162 $(element).click(function(e, data) {
eads@16 163 opt.processTextAreaDrop(target, element, representation_id, e, data);
eads@16 164 });
eads@12 165 }
eads@12 166 });
eads@3 167 }
eads@3 168 });
eads@11 169 };
eads@3 170 })(jQuery);