annotate js/dnd.js @ 8:b9cd179a30a8

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