Mercurial > defr > drupal > scald > dnd
view js/dnd.js @ 4:c2eb995212bf
TONS of fixes in this commit.
author | David Eads <eads@chicagotech.org> |
---|---|
date | Thu, 19 Feb 2009 12:33:19 -0600 |
parents | 5df0783706f7 |
children | 99ba5941779c |
line wrap: on
line source
/* jQuery Drag and Drop Library for Rich Editors * * This library exists to provide a jQuery plugin that manages dragging * and dropping of page assets into textareas and rich-text editors which use * the iframe + designMode method. * * This plugin has a rather elaborate set of considerations based on common * configurations of rich text editors and serious disparity in how browsers * handle dragging and dropping assets into an iframe. * * The plugin scans an iframe with an embedded document with designMode enabled * and tries to detect content that was injected by dragging and dropping * within the browser. If it detects an injection of content, it attempts to * conjure up an "editor representation" of the content based on the markup * passed in. * * This works because links and images drop with their full HTML syntax in-tact * across most browsers and platforms. This means we can set a timer on the * iframe that scans for the insertion of the markup and replace it with * another HTML snippet, as well as triggering actions inside the editor itself * (this will perhaps be handled by a set of editor-specific plugins). * * Because of the mechanism of operation, it is expected that the iframe * contain CSS which hides the markup dropped in to create the illusion of * seamlessly dropping in the "editor representation" of the dropped item. * * It is expected that the implementer will be parsing the input text for * the proper markup on the server side and making some decisions about how * to parse and handle that markup on load and save. * * Of special interest is graceful degradation. There is no ideal graceful * degradation path at this time. Every mainstream browser except IE 6 and * IE 7 drop links and images into textareas with their href and src URIs, * respectively, including querystrings. That means that the image or link * url can be used for parsing, or a querystring included. But it won't work * in Internet Explorer, and probably will require a server-side browser check * in full blown implementations of the library system. * * Basic usage: * * $('a.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. * */ (function($) { $.fn.dnd = function(opt) { opt = $.extend({}, { dropWrapper: '<p class="dnd-dropped"></p>', insertBefore: '', insertAfter: '', processedClass: 'dnd-processed', disableClick: true, processTargets: function(targets) { return targets.each(function() { $('head', $(this).contents()).append('<style type="text/css">.dnd-processed { display: none; }</style>'); return this; }); }, // Must return a string idSelector: function(element) { if (!element.id) { // @TODO sanitize output here if ($(element).is('a')) { return element.href; } if ($(element).is('img')) { return element.src; } } return element.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) { var old_parent = false; var element_id = ''; var parents = drop.parents(); for (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); if (opt.disableClick) { this.click(function() { return false; }) } // Process! return this.each(function() { var element = this; var representation_id = opt.idSelector(element); if (!element.id) { element.id = element.tagName.toLowerCase() + '-' + representation_id; } // 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; // Watch the iframe for changes t = setInterval(function() { var match = $('#' + element.id, $(target).contents()); if (match.length > 0) { drop = opt.preprocessDrop(target, match); // Must return a jquery object drop .before(opt.insertBefore) .after(opt.insertAfter) .wrap(opt.dropWrapper) .replaceWith(opt.renderRepresentation(target, drop, representation_id)); opt.postprocessDrop(target, drop, element); } }, 100); // @TODO track the timer with $.data() so we can clear it? } else if ($(this).is('textarea')) { console.log('@TODO handle textareas via.... regexp?'); } }); }); } })(jQuery);