eads@3: /* jQuery Drag and Drop Library for Rich Editors eads@3: * eads@3: * This library exists to provide a jQuery plugin that manages dragging eads@3: * and dropping of page assets into textareas and rich-text editors which use eads@3: * the iframe + designMode method. eads@3: * eads@3: * This plugin has a rather elaborate set of considerations based on common eads@3: * configurations of rich text editors and serious disparity in how browsers eads@3: * handle dragging and dropping assets into an iframe. eads@3: * eads@3: * The plugin scans an iframe with an embedded document with designMode enabled eads@3: * and tries to detect content that was injected by dragging and dropping eads@3: * within the browser. If it detects an injection of content, it attempts to eads@3: * conjure up an "editor representation" of the content based on the markup eads@3: * passed in. eads@3: * eads@3: * This works because links and images drop with their full HTML syntax in-tact eads@3: * across most browsers and platforms. This means we can set a timer on the eads@3: * iframe that scans for the insertion of the markup and replace it with eads@3: * another HTML snippet, as well as triggering actions inside the editor itself eads@3: * (this will perhaps be handled by a set of editor-specific plugins). eads@3: * eads@3: * Because of the mechanism of operation, it is expected that the iframe eads@3: * contain CSS which hides the markup dropped in to create the illusion of eads@3: * seamlessly dropping in the "editor representation" of the dropped item. eads@3: * eads@3: * It is expected that the implementer will be parsing the input text for eads@3: * the proper markup on the server side and making some decisions about how eads@3: * to parse and handle that markup on load and save. eads@3: * eads@3: * Of special interest is graceful degradation. There is no ideal graceful eads@3: * degradation path at this time. Every mainstream browser except IE 6 and eads@3: * IE 7 drop links and images into textareas with their href and src URIs, eads@3: * respectively, including querystrings. That means that the image or link eads@3: * url can be used for parsing, or a querystring included. But it won't work eads@3: * in Internet Explorer, and probably will require a server-side browser check eads@3: * in full blown implementations of the library system. eads@3: * eads@3: * Basic usage: eads@3: * eads@3: * $('a.my-class').dnd({targets: $('#my-iframe')}); eads@3: * eads@3: * Options: eads@3: * eads@3: * targets eads@3: * A jQuery object corresponding to the proper iframe(s) to enable dragging eads@3: * and dropping for. [Required] eads@3: * eads@3: * dropWrapper eads@3: * An html snippet to wrap the entire inserted content with in the editor eads@3: * representation (i.e. '

') eads@3: * eads@3: * insertBefore: eads@3: * Markup to insert before drop (i.e. '
') eads@3: * eads@3: * insertAfter: eads@3: * Markup to insert after drop (i.e. '
') eads@3: * eads@3: * processedClass: eads@3: * The class to apply to links and images tagged as droppable. This class eads@3: * should have a style rule in the editor that sets display to 'none' for eads@3: * the best experience. eads@3: * eads@3: * idSelector: eads@3: * A callback that parses a unique id out of a droppable element. B y default eads@3: * this uses the id of the element, but one could parse out an ID based on eads@3: * any part of the URL, interior markup, etc. eads@3: * eads@3: * renderRepresentation: eads@3: * A callback that defines the mechanism for rendering a representation of eads@3: * the content. The default is currently essential useless. eads@3: * eads@3: * preprocessDrop: eads@3: * A callback that preprocesses the dropped snippet in the iframe, before eads@3: * replacing it. By default this uses a little logic to walk up the DOM eads@3: * tree to topmost parent of the place in the source where the item was eads@3: * dropped, and add the dropped element after that parent instead of eads@3: * inside it. eads@3: * eads@3: * postprocessDrop: eads@3: * A callback that postprocesses the iframe. eads@3: * eads@3: */ eads@3: eads@3: (function($) { eads@3: $.fn.dnd = function(opt) { eads@3: opt = $.extend({}, { eads@3: dropWrapper: '

', eads@3: insertBefore: '', eads@3: insertAfter: '', eads@3: processedClass: 'dnd-processed', eads@3: eads@3: processTargets: function(targets) { eads@3: return targets.each(function() { eads@3: $('head', $(this).contents()).append(''); eads@3: return this; eads@3: }); eads@3: }, eads@3: eads@3: // Must return a string eads@3: idSelector: function(element) { eads@3: if (!element.id) { eads@3: // @TODO sanitize output here eads@3: if ($(element).is('a')) { eads@3: return element.href; eads@3: } eads@3: if ($(element).is('img')) { eads@3: return element.src; eads@3: } eads@3: } eads@3: return element.id; eads@3: }, eads@3: eads@3: // @TODO: Target should be jQuery object eads@3: targets: $('iframe, textarea'), eads@3: eads@3: // Must return a string that DOES NOT share the id of any droppable item eads@3: // living outside the iframe eads@3: renderRepresentation: function(target, drop, 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@3: return '' + representation_id + ''; eads@3: }, eads@3: eads@3: // Back out markup to render in place after parent container eads@3: preprocessDrop: function(target, drop) { eads@3: var old_parent = false; eads@3: var element_id = ''; eads@3: eads@3: var parents = drop.parents(); eads@3: for (i=0; i < parents.length; i++) { eads@3: if ($(parents[i]).is('body')) { eads@3: element_id = $(drop).get(0).id; eads@3: $(old_parent).after(drop.clone()); eads@3: drop.remove(); eads@3: } eads@3: old_parent = parents[i]; eads@3: } eads@3: return $('#'+ element_id, $(target).contents()); eads@3: }, eads@3: eads@3: postprocessDrop: function(target, drop) { return; } 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@3: eads@3: var element = this; eads@3: var representation_id = opt.idSelector(element); eads@3: eads@3: if (!element.id) { eads@3: element.id = element.tagName.toLowerCase() + '-' + representation_id; eads@3: } eads@3: eads@3: // Add some UI sugar and a special class eads@3: $(element) eads@3: .css('cursor', 'move') eads@3: .addClass(opt.processedClass); eads@3: eads@3: // We need to differentiate behavior based on the targets... I guess. eads@3: targets.each(function() { eads@3: if ($(this).is('iframe')) { eads@3: var target = this; eads@3: eads@3: // Watch the iframe for changes eads@3: t = setInterval(function() { eads@3: var match = $('#' + element.id, $(target).contents()); eads@3: if (match.length > 0) { eads@3: drop = opt.preprocessDrop(target, match); // Must return a jquery object eads@3: drop eads@3: .before(opt.insertBefore) eads@3: .after(opt.insertAfter) eads@3: .wrap(opt.dropWrapper) eads@3: .replaceWith(opt.renderRepresentation(target, drop, representation_id)); eads@3: opt.postprocessDrop(target, drop); eads@3: } eads@3: }, 100); eads@3: // @TODO track the timer with $.data() so we can clear it? eads@3: } else if ($(this).is('textarea')) { eads@3: console.log('@TODO handle textareas via.... regexp?'); eads@3: } eads@3: }); eads@3: }); eads@3: } eads@3: })(jQuery);