annotate js/dnd.js @ 14:ef7ad7b5baa4

Slightly changed matching mechanism to handle Firefox's crazy desire to make dropped src attributes relative.
author David Eads <eads@chicagotech.org>
date Fri, 27 Feb 2009 12:30:42 -0600
parents a5b2b9fa2a1a
children 7a5f74482ee3
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@12 4 * Text Editors (RTEs) that use the embedded iframe + DesignMode method, and
eads@12 5 * provides a simple "clicky" interface for inserting the same markup directly
eads@12 6 * 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@3 14 * targets
eads@3 15 * A jQuery object corresponding to the proper iframe(s) to enable dragging
eads@3 16 * and dropping for. [Required]
eads@3 17 *
eads@3 18 * dropWrapper
eads@3 19 * An html snippet to wrap the entire inserted content with in the editor
eads@3 20 * representation (i.e. '<p class="foo"></p>')
eads@3 21 *
eads@3 22 * insertBefore:
eads@3 23 * Markup to insert before drop (i.e. '<hr />')
eads@3 24 *
eads@3 25 * insertAfter:
eads@3 26 * Markup to insert after drop (i.e. '<div class="clearfix"></div>')
eads@3 27 *
eads@3 28 * processedClass:
eads@3 29 * The class to apply to links and images tagged as droppable. This class
eads@3 30 * should have a style rule in the editor that sets display to 'none' for
eads@3 31 * the best experience.
eads@3 32 *
eads@3 33 * idSelector:
eads@3 34 * A callback that parses a unique id out of a droppable element. B y default
eads@3 35 * this uses the id of the element, but one could parse out an ID based on
eads@3 36 * any part of the URL, interior markup, etc.
eads@3 37 *
eads@3 38 * renderRepresentation:
eads@3 39 * A callback that defines the mechanism for rendering a representation of
eads@3 40 * the content. The default is currently essential useless.
eads@3 41 *
eads@3 42 * preprocessDrop:
eads@3 43 * A callback that preprocesses the dropped snippet in the iframe, before
eads@3 44 * replacing it. By default this uses a little logic to walk up the DOM
eads@3 45 * tree to topmost parent of the place in the source where the item was
eads@3 46 * dropped, and add the dropped element after that parent instead of
eads@3 47 * inside it.
eads@3 48 *
eads@3 49 * postprocessDrop:
eads@3 50 * A callback that postprocesses the iframe.
eads@3 51 *
eads@12 52 * interval:
eads@12 53 * How often to check the iframe for a drop, in milliseconds.
eads@12 54 *
eads@12 55 * Usage notes:
eads@12 56 *
eads@12 57 * This is a very tricky problem and to achieve cross browser (as of writing,
eads@12 58 * IE, Safari, and Firefox) support, severe limitations must be made on the
eads@12 59 * nature of DOM elements that are dropped.
eads@12 60 *
eads@12 61 * Droppable elements must be <img> tags. Internet Explorer will not accept
eads@12 62 * anchors, and Safari strips attributes and additional markup from
eads@12 63 * dropped anchors, images, and snippets.
eads@12 64 *
eads@12 65 * While the idSelector option allows you to parse the dropped element for
eads@12 66 * any attribute that "comes along" with the element when it is dropped in a
eads@12 67 * designmode-enabled iframe, the only safe attribute to scan is the 'src'
eads@12 68 * attribute, and to avoid strange "relativization" of links, the src should
eads@12 69 * always be expressed as an absolute url with a fully qualified domain name.
eads@12 70 *
eads@12 71 * Implementation notes and todos:
eads@12 72 *
eads@12 73 * Currently, there is no garbage collection instituted for the many many
eads@12 74 * timers that are created, so memory usage could become as issue in scenarios
eads@12 75 * where the user does a lot of paging or otherwise winds up invoking
eads@12 76 * drag and drop on large numbers of elements.
eads@3 77 */
eads@3 78
eads@3 79 (function($) {
eads@3 80 $.fn.dnd = function(opt) {
eads@3 81 opt = $.extend({}, {
eads@4 82 dropWrapper: '<p class="dnd-dropped"></p>',
eads@12 83 insertBefore: false,
eads@12 84 insertAfter: false,
eads@3 85 processedClass: 'dnd-processed',
eads@12 86 interval: 100,
eads@3 87
eads@3 88 processTargets: function(targets) {
eads@3 89 return targets.each(function() {
eads@12 90 //$('head', $(this).contents()).append('<style type="text/css">.dnd-processed { display: none; }</style>');
eads@12 91 //@TODO use jQuery.rules()
eads@3 92 return this;
eads@3 93 });
eads@3 94 },
eads@3 95
eads@3 96 // Must return a string
eads@3 97 idSelector: function(element) {
eads@12 98 if ($(element).is('img')) {
eads@12 99 return $.url.setUrl(element.src).param('dnd_id');
eads@3 100 }
eads@3 101 },
eads@3 102
eads@3 103 // @TODO: Target should be jQuery object
eads@3 104 targets: $('iframe, textarea'),
eads@3 105
eads@3 106 // Must return a string that DOES NOT share the id of any droppable item
eads@3 107 // living outside the iframe
eads@3 108 renderRepresentation: function(target, drop, representation_id) {
eads@3 109 // Keep a counter of how many times this element was used
eads@3 110 var count = $.data(target, representation_id +'_count');
eads@3 111 if (!count) {
eads@3 112 count = 1;
eads@3 113 } else {
eads@3 114 count++;
eads@3 115 }
eads@3 116 $.data(target, representation_id +'_count', count);
eads@3 117 return '<span id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</span>';
eads@3 118 },
eads@3 119
eads@3 120 // Back out markup to render in place after parent container
eads@3 121 preprocessDrop: function(target, drop) {
eads@12 122 return drop;
eads@3 123 var old_parent = false;
eads@3 124 var element_id = '';
eads@3 125
eads@3 126 var parents = drop.parents();
eads@11 127 for (var i=0; i < parents.length; i++) {
eads@3 128 if ($(parents[i]).is('body')) {
eads@3 129 element_id = $(drop).get(0).id;
eads@3 130 $(old_parent).after(drop.clone());
eads@3 131 drop.remove();
eads@3 132 }
eads@3 133 old_parent = parents[i];
eads@3 134 }
eads@3 135 return $('#'+ element_id, $(target).contents());
eads@3 136 },
eads@3 137
eads@4 138 postprocessDrop: function(target, drop, element) {
eads@4 139 $(element).addClass('dnd-inserted');
eads@4 140 }
eads@3 141
eads@3 142 }, opt);
eads@3 143
eads@3 144 // Initialize plugin
eads@3 145 var targets = opt.processTargets(opt.targets);
eads@3 146
eads@3 147 // Process!
eads@3 148 return this.each(function() {
eads@12 149 if ($(this).is('img')) {
eads@12 150 var element = this;
eads@3 151
eads@12 152 // If we don't have a proper id, bail
eads@12 153 var representation_id = opt.idSelector(element);
eads@3 154
eads@12 155 if (!representation_id) {
eads@12 156 return this;
eads@12 157 };
eads@12 158
eads@12 159 // Add some UI sugar and a special class
eads@12 160 $(element)
eads@12 161 .css('cursor', 'move')
eads@12 162 .addClass(opt.processedClass);
eads@12 163
eads@12 164 // We need to differentiate behavior based on the targets... I guess.
eads@12 165 targets.each(function() {
eads@12 166 if ($(this).is('iframe')) {
eads@12 167 var target = this;
eads@12 168 var selector = 'img[src='+ element.src +']';
eads@12 169
eads@12 170 // Watch the iframe for changes
eads@12 171 var t = setInterval(function() {
eads@14 172 $('img', $(target).contents()).each(function() {
eads@14 173 if (opt.idSelector(this) == representation_id) {
eads@14 174 var drop = opt.preprocessDrop(target, $(this)); // Must return a jquery object
eads@14 175 var representation = opt.renderRepresentation(target, drop, representation_id);
eads@14 176 if (representation) {
eads@14 177 if (opt.dropWrapper) {
eads@14 178 drop.wrap(opt.dropWrapper);
eads@14 179 }
eads@14 180 if (opt.insertBefore) {
eads@14 181 drop.before(opt.insertBefore);
eads@14 182 }
eads@14 183 if (opt.insertAfter) {
eads@14 184 drop.after(opt.insertAfter);
eads@14 185 }
eads@14 186 drop.replaceWith(representation);
eads@14 187 opt.postprocessDrop(target, drop, element);
eads@12 188 }
eads@12 189 }
eads@14 190 });
eads@12 191 }, opt.interval);
eads@12 192 // @TODO track the timer with $.data() so we can clear it?
eads@12 193 } else if ($(this).is('textarea')) {
eads@12 194 //console.log('@TODO handle textareas via.... regexp?');
eads@12 195 }
eads@12 196 });
eads@3 197 }
eads@3 198 });
eads@11 199 };
eads@3 200 })(jQuery);