Mercurial > defr > drupal > scald > dnd
view js/dnd.js @ 15:7a5f74482ee3
More cross browser work, garbage collection for timers, etc.
author | David Eads <eads@chicagotech.org> |
---|---|
date | Mon, 02 Mar 2009 23:22:37 -0600 |
parents | ef7ad7b5baa4 |
children | bb68dc3ad56f |
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. By 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({}, { interval: 250, dropWrapper: '<p class="dnd-dropped-wrapper"></p>', insertBefore: false, insertAfter: false, processedClass: 'dnd-processed', ignoreClass: 'dnd-dropped', processTargets: function(targets) { return targets.each(function() { $('head', $(this).contents()).append('<style type="text/css">img { display: none; } img.dnd-dropped {display: block; }</style>'); 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>'; }, preprocessDrop: function(target, drop) { return drop; }, 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:not(.' + opt.ignoreClass + ')'; // Watch the iframe for changes var t = setInterval(function() { $(selector, $(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) { // Breaks IE 7! /*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); // Track current active timers -- this means you can implement // your own garbage collection for specific interactions, such // as paging. var data = $(document).data('dnd_timers'); if (data) { data[data.length] = t; } else { data = new Array(); data[0] = t; } $(document).data('dnd_timers', data); } else if ($(this).is('textarea')) { //console.log('@TODO handle textareas via.... regexp?'); } }); } }); }; })(jQuery);