Mercurial > defr > drupal > scald > dnd
changeset 3:5df0783706f7
Reorganized layout of module and repo a bit.
author | David Eads <eads@chicagotech.org> |
---|---|
date | Tue, 17 Feb 2009 15:46:36 -0600 |
parents | 5a44c430b7ac |
children | c2eb995212bf |
files | dnd.module js/dnd.js |
diffstat | 2 files changed, 197 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/dnd.module Tue Feb 17 15:34:56 2009 -0600 +++ b/dnd.module Tue Feb 17 15:46:36 2009 -0600 @@ -77,8 +77,8 @@ function dnd_process_textarea($element, $form_state) { if ($element['#dnd-enabled']) { - drupal_add_js(drupal_get_path('module', 'dnd') .'/dnd/dnd.js'); - drupal_add_js(drupal_get_path('module', 'dnd') .'/js/dnd-library.js', 'footer'); + drupal_add_js(drupal_get_path('module', 'dnd') .'/js/dnd.js'); + drupal_add_js(drupal_get_path('module', 'dnd') .'/js/dnd-library.js'); $settings = array();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/dnd.js Tue Feb 17 15:46:36 2009 -0600 @@ -0,0 +1,195 @@ +/* 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-inserted"></p>', + insertBefore: '', + insertAfter: '', + processedClass: 'dnd-processed', + + 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) { return; } + + }, opt); + + // Initialize plugin + var targets = opt.processTargets(opt.targets); + + // 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); + } + }, 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);