Mercurial > defr > drupal > scald > dnd
view 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 |
line wrap: on
line source
/* 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, and * 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 * A jQuery object corresponding to the proper iframe(s) to enable dragging * and dropping for. [Required] * * dropWrapper * An html snippet to wrap the entire inserted content with in the editor * representation (i.e. '<p class="foo"></p>') * * insertBefore: * Markup to insert before drop (i.e. '<hr />') * * insertAfter: * Markup to insert after drop (i.e. '<div class="clearfix"></div>') * * processedClass: * The class to apply to links and images tagged as droppable. This class * should have a style rule in the editor that sets display to 'none' for * the best experience. * * idSelector: * A callback that parses a unique id out of a droppable element. B y default * this uses the id of the element, but one could parse out an ID based on * any part of the URL, interior markup, etc. * * renderRepresentation: * A callback that defines the mechanism for rendering a representation of * the content. The default is currently essential useless. * * preprocessDrop: * A callback that preprocesses the dropped snippet in the iframe, before * replacing it. By default this uses a little logic to walk up the DOM * tree to topmost parent of the place in the source where the item was * dropped, and add the dropped element after that parent instead of * inside it. * * 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: false, insertAfter: false, processedClass: 'dnd-processed', interval: 100, processTargets: function(targets) { return targets.each(function() { //$('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).is('img')) { return $.url.setUrl(element.src).param('dnd_id'); } }, // @TODO: Target should be jQuery object targets: $('iframe, textarea'), // Must return a string that DOES NOT share the id of any droppable item // living outside the iframe renderRepresentation: function(target, drop, 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); return '<span id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</span>'; }, // Back out markup to render in place after parent container preprocessDrop: function(target, drop) { return drop; var old_parent = false; var element_id = ''; var parents = drop.parents(); for (var i=0; i < parents.length; i++) { if ($(parents[i]).is('body')) { element_id = $(drop).get(0).id; $(old_parent).after(drop.clone()); drop.remove(); } old_parent = parents[i]; } return $('#'+ element_id, $(target).contents()); }, postprocessDrop: function(target, drop, element) { $(element).addClass('dnd-inserted'); } }, opt); // Initialize plugin var targets = opt.processTargets(opt.targets); // Process! return this.each(function() { if ($(this).is('img')) { var element = this; // If we don't have a proper id, bail var representation_id = opt.idSelector(element); 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() { $('img', $(target).contents()).each(function() { if (opt.idSelector(this) == representation_id) { var drop = opt.preprocessDrop(target, $(this)); // 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?'); } }); } }); }; })(jQuery);