eads@3: /* jQuery Drag and Drop Library for Rich Editors eads@3: * eads@12: * A helper library which provides the ability to drag and drop images to Rich eads@16: * Text Editors (RTEs) that use the embedded iframe + DesignMode method. DnD eads@16: * also provides a simple "clicky" interface for inserting the same markup eads@16: * directly into a textarea. eads@3: * eads@3: * Basic usage: eads@3: * eads@12: * $('img.my-class').dnd({targets: $('#my-iframe')}); eads@3: * eads@3: * Options: eads@3: * eads@16: * targets (Required): eads@16: * A jQuery object corresponding to the proper iframe(s) and/or textarea(s) eads@16: * that are allowed drop targets for the current set of elements. eads@3: * eads@16: * idSelector: eads@16: * A callback that parses out the unique ID of an image that is dropped in an eads@16: * iframe. While some browsers (such as Firefox and Internet Explorer) allow eads@16: * markup to be copied when dragging and dropping, Safari (and assumably eads@16: * other webkit based browsers) don't. The upshot is that the safest bet is eads@16: * to parse the element's src URL. Because querystrings seem to drop eads@16: * consistently across eads@3: * eads@16: * processIframeDrop: eads@16: * A callback that defines the mechanism for inserting and rendering the eads@16: * dropped item in an iframe. The typical usage pattern I expect to see is eads@16: * that implementers will listen for their RTE to load and then invoke DnD eads@16: * with a processIframeDrop appropriate to their editor. eads@3: * eads@16: * processTextAreaDrop: eads@16: * A callback that defines the mechanism for inserting and rendering the eads@16: * clicked item in a textarea. The default function just tries to insert eads@16: * some markup at the caret location in the current textarea. eads@3: * eads@12: * interval: eads@12: * How often to check the iframe for a drop, in milliseconds. eads@12: * eads@16: * eads@16: * eads@12: * Usage notes: eads@12: * eads@16: * Due to cross browser flakiness, there are many many limitations on what is eads@16: * possible in terms of dragging and dropping to DesignMode enabled iframes. eads@12: * eads@16: * To make this work, your "droppable" elements must be image tags. No ifs, ands, eads@16: * or buts: image tags have the best cross browser behavior when dragging. eads@12: * eads@16: * When DnD is initialized, it begins timers which periodically check your eads@16: * editor iframe. If an image is dropped in, DnD takes the element and eads@16: * attempts to parse it for a unique ID which you can use to do a lookup for eads@16: * an "editor representation." If an editor representation is found, eads@16: * typically the image is replaced with the representation snippet, but you are eads@16: * free to do whatever you want. eads@12: * eads@16: * Because of browser limitations, the safest way to parse the element is to eads@16: * look at the img's src attribute and use some or all of the image URL. eads@16: * In my experience, the best way to deal with this is simply to parse out eads@16: * generate a query string in the src attribute server side that corresponds eads@16: * to the proper representation ID. eads@12: * eads@16: * If the target is not an iframe but a textarea, DnD provides a very minimal eads@16: * system for clicking what would otherwise be dragged to insert markup into eads@16: * the textarea. eads@16: * eads@16: * DnD is purely a utility library: to make it work for any particular editor, eads@16: * CMS, etc: It is very unlikely it will provide much benefit out of the box. eads@16: * eads@16: * Because DnD spawns so many timers, they are stored in a $.data element eads@16: * attached to the parent document body. Implementers should consider clearing eads@16: * the timers when building systems such as dynamic searches or paging eads@16: * functionality to ensure memory usage and performance remains stable. eads@3: */ eads@3: eads@3: (function($) { eads@3: $.fn.dnd = function(opt) { eads@3: opt = $.extend({}, { eads@15: interval: 250, eads@16: targets: $('iframe, textarea'), eads@3: processTargets: function(targets) { eads@3: return targets.each(function() { eads@15: $('head', $(this).contents()).append(''); eads@3: return this; eads@3: }); eads@3: }, eads@3: idSelector: function(element) { eads@12: if ($(element).is('img')) { eads@16: return element.src; eads@3: } eads@16: return false; eads@3: }, eads@16: processIframeDrop: function(target, dragged, dropped, representation_id) { eads@3: // Keep a counter of how many times this element was used eads@3: var count = $.data(target, representation_id +'_count'); eads@3: if (!count) { eads@3: count = 1; eads@3: } else { eads@3: count++; eads@3: } eads@3: $.data(target, representation_id +'_count', count); eads@16: $(dropped).replaceWith('

' + representation_id + '

'); eads@3: }, eads@16: processTextAreaDrop: function(target, clicked, representation_id, e, data) { eads@16: var snippet = '
'; eads@16: $(target).replaceSelection(snippet, true); eads@16: e.preventDefault(); eads@16: } eads@3: eads@3: }, opt); eads@3: eads@3: // Initialize plugin eads@3: var targets = opt.processTargets(opt.targets); eads@3: eads@3: // Process! eads@3: return this.each(function() { eads@12: if ($(this).is('img')) { eads@16: var element = this, $element = $(element); eads@3: eads@12: // If we don't have a proper id, bail eads@12: var representation_id = opt.idSelector(element); eads@3: eads@12: if (!representation_id) { eads@12: return this; eads@12: }; eads@12: eads@16: // Add a special class eads@16: $(element).addClass('dnd-processed'); eads@12: eads@12: // We need to differentiate behavior based on the targets... I guess. eads@12: targets.each(function() { eads@16: var target = this, $target = $(target); eads@16: if ($target.is('iframe')) { eads@15: eads@16: // Indicate this element is draggy eads@16: $element.css('cursor', 'move'); eads@16: eads@12: // Watch the iframe for changes eads@12: var t = setInterval(function() { eads@16: $('img:not(.dnd-dropped)', $(target).contents()).each(function() { eads@16: var dropped = this; eads@16: if (opt.idSelector(dropped) == representation_id) { eads@16: opt.processIframeDrop(target, element, dropped, representation_id); eads@16: } eads@14: }); eads@12: }, opt.interval); eads@15: eads@16: // Track current active timers -- developers working with DnD eads@16: // can implement their own garbage collection for specific eads@16: // interactions, such as paging or live search. eads@15: var data = $(document).data('dnd_timers'); eads@15: if (data) { eads@15: data[data.length] = t; eads@15: } else { eads@15: data = new Array(); eads@15: data[0] = t; eads@15: } eads@15: $(document).data('dnd_timers', data); eads@15: eads@16: } else if ($target.is('textarea')) { eads@17: $element.css('cursor', 'pointer'); eads@16: $(element).click(function(e, data) { eads@16: opt.processTextAreaDrop(target, element, representation_id, e, data); eads@16: }); eads@12: } eads@12: }); eads@3: } eads@3: }); eads@11: }; eads@3: })(jQuery);