Mercurial > defr > drupal > scald > dnd
comparison js/dnd.js @ 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 | |
| children | c2eb995212bf |
comparison
equal
deleted
inserted
replaced
| 2:5a44c430b7ac | 3:5df0783706f7 |
|---|---|
| 1 /* jQuery Drag and Drop Library for Rich Editors | |
| 2 * | |
| 3 * This library exists to provide a jQuery plugin that manages dragging | |
| 4 * and dropping of page assets into textareas and rich-text editors which use | |
| 5 * the iframe + designMode method. | |
| 6 * | |
| 7 * This plugin has a rather elaborate set of considerations based on common | |
| 8 * configurations of rich text editors and serious disparity in how browsers | |
| 9 * handle dragging and dropping assets into an iframe. | |
| 10 * | |
| 11 * The plugin scans an iframe with an embedded document with designMode enabled | |
| 12 * and tries to detect content that was injected by dragging and dropping | |
| 13 * within the browser. If it detects an injection of content, it attempts to | |
| 14 * conjure up an "editor representation" of the content based on the markup | |
| 15 * passed in. | |
| 16 * | |
| 17 * This works because links and images drop with their full HTML syntax in-tact | |
| 18 * across most browsers and platforms. This means we can set a timer on the | |
| 19 * iframe that scans for the insertion of the markup and replace it with | |
| 20 * another HTML snippet, as well as triggering actions inside the editor itself | |
| 21 * (this will perhaps be handled by a set of editor-specific plugins). | |
| 22 * | |
| 23 * Because of the mechanism of operation, it is expected that the iframe | |
| 24 * contain CSS which hides the markup dropped in to create the illusion of | |
| 25 * seamlessly dropping in the "editor representation" of the dropped item. | |
| 26 * | |
| 27 * It is expected that the implementer will be parsing the input text for | |
| 28 * the proper markup on the server side and making some decisions about how | |
| 29 * to parse and handle that markup on load and save. | |
| 30 * | |
| 31 * Of special interest is graceful degradation. There is no ideal graceful | |
| 32 * degradation path at this time. Every mainstream browser except IE 6 and | |
| 33 * IE 7 drop links and images into textareas with their href and src URIs, | |
| 34 * respectively, including querystrings. That means that the image or link | |
| 35 * url can be used for parsing, or a querystring included. But it won't work | |
| 36 * in Internet Explorer, and probably will require a server-side browser check | |
| 37 * in full blown implementations of the library system. | |
| 38 * | |
| 39 * Basic usage: | |
| 40 * | |
| 41 * $('a.my-class').dnd({targets: $('#my-iframe')}); | |
| 42 * | |
| 43 * Options: | |
| 44 * | |
| 45 * targets | |
| 46 * A jQuery object corresponding to the proper iframe(s) to enable dragging | |
| 47 * and dropping for. [Required] | |
| 48 * | |
| 49 * dropWrapper | |
| 50 * An html snippet to wrap the entire inserted content with in the editor | |
| 51 * representation (i.e. '<p class="foo"></p>') | |
| 52 * | |
| 53 * insertBefore: | |
| 54 * Markup to insert before drop (i.e. '<hr />') | |
| 55 * | |
| 56 * insertAfter: | |
| 57 * Markup to insert after drop (i.e. '<div class="clearfix"></div>') | |
| 58 * | |
| 59 * processedClass: | |
| 60 * The class to apply to links and images tagged as droppable. This class | |
| 61 * should have a style rule in the editor that sets display to 'none' for | |
| 62 * the best experience. | |
| 63 * | |
| 64 * idSelector: | |
| 65 * A callback that parses a unique id out of a droppable element. B y default | |
| 66 * this uses the id of the element, but one could parse out an ID based on | |
| 67 * any part of the URL, interior markup, etc. | |
| 68 * | |
| 69 * renderRepresentation: | |
| 70 * A callback that defines the mechanism for rendering a representation of | |
| 71 * the content. The default is currently essential useless. | |
| 72 * | |
| 73 * preprocessDrop: | |
| 74 * A callback that preprocesses the dropped snippet in the iframe, before | |
| 75 * replacing it. By default this uses a little logic to walk up the DOM | |
| 76 * tree to topmost parent of the place in the source where the item was | |
| 77 * dropped, and add the dropped element after that parent instead of | |
| 78 * inside it. | |
| 79 * | |
| 80 * postprocessDrop: | |
| 81 * A callback that postprocesses the iframe. | |
| 82 * | |
| 83 */ | |
| 84 | |
| 85 (function($) { | |
| 86 $.fn.dnd = function(opt) { | |
| 87 opt = $.extend({}, { | |
| 88 dropWrapper: '<p class="dnd-inserted"></p>', | |
| 89 insertBefore: '', | |
| 90 insertAfter: '', | |
| 91 processedClass: 'dnd-processed', | |
| 92 | |
| 93 processTargets: function(targets) { | |
| 94 return targets.each(function() { | |
| 95 $('head', $(this).contents()).append('<style type="text/css">.dnd-processed { display: none; }</style>'); | |
| 96 return this; | |
| 97 }); | |
| 98 }, | |
| 99 | |
| 100 // Must return a string | |
| 101 idSelector: function(element) { | |
| 102 if (!element.id) { | |
| 103 // @TODO sanitize output here | |
| 104 if ($(element).is('a')) { | |
| 105 return element.href; | |
| 106 } | |
| 107 if ($(element).is('img')) { | |
| 108 return element.src; | |
| 109 } | |
| 110 } | |
| 111 return element.id; | |
| 112 }, | |
| 113 | |
| 114 // @TODO: Target should be jQuery object | |
| 115 targets: $('iframe, textarea'), | |
| 116 | |
| 117 // Must return a string that DOES NOT share the id of any droppable item | |
| 118 // living outside the iframe | |
| 119 renderRepresentation: function(target, drop, representation_id) { | |
| 120 // Keep a counter of how many times this element was used | |
| 121 var count = $.data(target, representation_id +'_count'); | |
| 122 if (!count) { | |
| 123 count = 1; | |
| 124 } else { | |
| 125 count++; | |
| 126 } | |
| 127 $.data(target, representation_id +'_count', count); | |
| 128 return '<span id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</span>'; | |
| 129 }, | |
| 130 | |
| 131 // Back out markup to render in place after parent container | |
| 132 preprocessDrop: function(target, drop) { | |
| 133 var old_parent = false; | |
| 134 var element_id = ''; | |
| 135 | |
| 136 var parents = drop.parents(); | |
| 137 for (i=0; i < parents.length; i++) { | |
| 138 if ($(parents[i]).is('body')) { | |
| 139 element_id = $(drop).get(0).id; | |
| 140 $(old_parent).after(drop.clone()); | |
| 141 drop.remove(); | |
| 142 } | |
| 143 old_parent = parents[i]; | |
| 144 } | |
| 145 return $('#'+ element_id, $(target).contents()); | |
| 146 }, | |
| 147 | |
| 148 postprocessDrop: function(target, drop) { return; } | |
| 149 | |
| 150 }, opt); | |
| 151 | |
| 152 // Initialize plugin | |
| 153 var targets = opt.processTargets(opt.targets); | |
| 154 | |
| 155 // Process! | |
| 156 return this.each(function() { | |
| 157 | |
| 158 var element = this; | |
| 159 var representation_id = opt.idSelector(element); | |
| 160 | |
| 161 if (!element.id) { | |
| 162 element.id = element.tagName.toLowerCase() + '-' + representation_id; | |
| 163 } | |
| 164 | |
| 165 // Add some UI sugar and a special class | |
| 166 $(element) | |
| 167 .css('cursor', 'move') | |
| 168 .addClass(opt.processedClass); | |
| 169 | |
| 170 // We need to differentiate behavior based on the targets... I guess. | |
| 171 targets.each(function() { | |
| 172 if ($(this).is('iframe')) { | |
| 173 var target = this; | |
| 174 | |
| 175 // Watch the iframe for changes | |
| 176 t = setInterval(function() { | |
| 177 var match = $('#' + element.id, $(target).contents()); | |
| 178 if (match.length > 0) { | |
| 179 drop = opt.preprocessDrop(target, match); // Must return a jquery object | |
| 180 drop | |
| 181 .before(opt.insertBefore) | |
| 182 .after(opt.insertAfter) | |
| 183 .wrap(opt.dropWrapper) | |
| 184 .replaceWith(opt.renderRepresentation(target, drop, representation_id)); | |
| 185 opt.postprocessDrop(target, drop); | |
| 186 } | |
| 187 }, 100); | |
| 188 // @TODO track the timer with $.data() so we can clear it? | |
| 189 } else if ($(this).is('textarea')) { | |
| 190 console.log('@TODO handle textareas via.... regexp?'); | |
| 191 } | |
| 192 }); | |
| 193 }); | |
| 194 } | |
| 195 })(jQuery); |
