annotate js/dnd-library.js @ 18:0d557e6e73f7

Added beautytips and some additional event handling code to the library.
author David Eads <eads@chicagotech.org>
date Fri, 06 Mar 2009 14:11:46 -0600
parents 1a77f87927dd
children d83073a08b25
rev   line source
eads@16 1 /**
eads@16 2 * Drag and Drop Library For Drupal
eads@16 3 *
eads@16 4 * This builds on the DnD jQuery plugin written to provide drag and drop media
eads@16 5 * handling to Rich Text Editors to consume, display, and attach behavior to
eads@16 6 * a "media library" provided via JSON and implemented for Drupal running
eads@16 7 * the Wysiwyg plugin.
eads@16 8 */
eads@16 9
eads@16 10 /**
eads@16 11 * Extend jQuery a bit
eads@16 12 *
eads@16 13 * We add a selector to look for "empty" elements (empty elements in TinyMCE
eads@17 14 * often have non-breaking spaces and <br /> tags). An exception is required
eads@17 15 * to make this work in IE.
eads@16 16 */
eads@16 17 (function($) {
eads@16 18 // Custom selectors
eads@16 19 $.extend($.expr[":"], {
eads@16 20 'empty' : function(a, i, m) {
eads@17 21 return !$(a).filter(function(i) {
eads@17 22 return !$(this).is('br');
eads@17 23 }).length && !$.trim(a.textContent || a.innerText||$(a).text()||"");
eads@16 24 }
eads@16 25 });
eads@16 26 }) (jQuery);
eads@16 27
eads@2 28 Drupal.behaviors.dndLibrary = function(context) {
eads@16 29 $('.dnd-library-wrapper', context).each(function() {
eads@4 30 var $this = $(this);
eads@2 31
eads@4 32 // This is a bad hack to lop off '-dnd-library' from the id to get the editor name
eads@17 33 var $editor = $('#' + this.id.slice(0, -12));
eads@17 34
eads@2 35
eads@16 36 // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg
eads@17 37 $editor.bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library);
eads@17 38 $editor.bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library);
eads@17 39
eads@18 40 $('.editor-item', context).each(function () {
eads@18 41 $(this).bt(Drupal.settings.dndLibraryPreviews[this.id], {
eads@18 42 'trigger' : 'none',
eads@18 43 'width' : 300,
eads@18 44 'spikeLength' : 7,
eads@18 45 'spikeGirth' : 9,
eads@18 46 'corner-radius' : 3,
eads@18 47 'strokeWidth' : 1,
eads@18 48 'fill' : '#eee',
eads@18 49 'strokeStyle': '#555'
eads@17 50 });
eads@18 51 $(this).hover(function() {
eads@18 52 var $this = $(this);
eads@18 53 this.btOn();
eads@18 54 // Remove the preview once dragging of any image has commenced
eads@18 55 $('img', $this).bind('drag', function(e) {
eads@18 56 $this.btOff();
eads@18 57 });
eads@18 58 }, function() {
eads@18 59 this.btOff();
eads@18 60 });
eads@17 61 });
eads@4 62
eads@18 63
eads@4 64 // Ajax pager
eads@17 65 $('.pager a', $this).click(function() {
eads@2 66 $.getJSON(this.href, function(data) {
eads@17 67 Drupal.behaviors.dndLibrary.refreshLibrary.call($this.get(0), data, $editor);
eads@2 68 });
eads@17 69
eads@17 70 // Reattach behaviors
eads@17 71 Drupal.behaviors.dndLibrary();
eads@17 72
eads@2 73 return false;
eads@2 74 });
eads@17 75
eads@17 76 // Preload images in editor representations
eads@17 77 var cached = $.data($editor, 'dnd_preload') || {};
eads@17 78 for (editor_id in Drupal.settings.dndEditorRepresentations) {
eads@17 79 if (!cached[editor_id]) {
eads@17 80 $representation = $(Drupal.settings.dndEditorRepresentations[editor_id]);
eads@17 81 if ($representation.is('img') && $representation.get(0).src) {
eads@17 82 $representation.attr('src', $representation.get(0).src);
eads@17 83 } else {
eads@17 84 $('img', $representation).each(function() {
eads@17 85 this.attr('src', this.src);
eads@17 86 });
eads@17 87 }
eads@17 88 }
eads@17 89 }
eads@17 90 $.data($editor, 'dnd_preload', cached);
eads@4 91 });
eads@4 92 }
eads@2 93
eads@17 94 Drupal.behaviors.dndLibrary.refreshLibrary = function(data, editor) {
eads@17 95 $this = $(this);
eads@17 96
eads@17 97 $('.header', $this).html(data.header);
eads@17 98 $('.library', $this).html(data.library);
eads@17 99 $('.footer', $this).html(data.footer);
eads@17 100
eads@17 101 var params = Drupal.wysiwyg.instances[editor.get(0).id];
eads@17 102 editor.trigger('wysiwygDetach', params);
eads@17 103 editor.trigger('wysiwygAttach', params);
eads@17 104
eads@17 105 for (editor_id in data.editor_representations) {
eads@17 106 Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id];
eads@17 107 }
eads@17 108 for (preview_id in data.library_previews) {
eads@17 109 Drupal.settings.dndLibraryPreviews[preview_id] = data.library_previews[preview_id];
eads@17 110 }
eads@17 111 }
eads@17 112
eads@17 113
eads@16 114 // Dynamically compose a callback based on the editor name
eads@4 115 Drupal.behaviors.dndLibrary.attach_library = function(e, data) {
eads@17 116 var settings = $.extend({idSelector: Drupal.behaviors.dndLibrary.idSelector}, Drupal.settings.dndEnabledLibraries[data.field]);
eads@4 117 var editor_fn = 'attach_' + data.editor;
eads@4 118 if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) {
eads@17 119 window.Drupal.behaviors.dndLibrary[editor_fn](data, settings);
eads@2 120 }
eads@2 121 }
eads@2 122
eads@16 123 // Do garbage collection on detach
eads@4 124 Drupal.behaviors.dndLibrary.detach_library = function(e, data) {
eads@15 125 for (t in $(document).data('dnd_timers')) {
eads@15 126 clearInterval(t);
eads@15 127 }
eads@15 128 $(document).removeData('dnd_timers');
eads@4 129 }
eads@4 130
eads@16 131 // Basic textareas
eads@16 132 Drupal.behaviors.dndLibrary.attach_none = function(data, settings) {
eads@16 133 settings = $.extend({
eads@16 134 targets: $('#'+ data.field),
eads@17 135 processTextAreaDrop: function(target, clicked, representation_id, e, data) {
eads@17 136 var snippet = '<p class="dnd-dropped-wrapper">' + Drupal.settings.dndEditorRepresentations[representation_id] + '</p>';
eads@16 137 $(target).replaceSelection(snippet, true);
eads@16 138 }
eads@16 139 }, settings);
eads@16 140 $(settings.drop_selector).dnd(settings);
eads@16 141 }
eads@4 142
eads@16 143 // Attach TinyMCE
eads@2 144 Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) {
eads@4 145 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 146
eads@4 147 // If the Tiny instance exists, attach directly, otherwise wait until Tiny
eads@4 148 // has registered a new instance.
eads@4 149 if (tiny_instance) {
eads@4 150 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4 151 } else {
eads@4 152 var t = setInterval(function() {
eads@4 153 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 154 if (tiny_instance) {
eads@4 155 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4 156 clearInterval(t);
eads@2 157 }
eads@4 158 }, 100);
eads@4 159 }
eads@4 160 }
eads@2 161
eads@17 162 Drupal.behaviors.dndLibrary.idSelector = function(element) {
eads@17 163 if ($(element).is('img')) {
eads@17 164 return $.url.setUrl(element.src).param('dnd_id');
eads@17 165 }
eads@17 166 return false;
eads@17 167 }
eads@17 168
eads@16 169 // Really attach TinyMCE
eads@4 170 Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) {
eads@16 171 var ed = tiny_instance, dom = ed.dom, s = ed.selection;
eads@16 172
eads@4 173 settings = $.extend({
eads@4 174 targets: $('#'+ data.field +'-wrapper iframe'),
eads@16 175 interval: 100,
eads@16 176 processIframeDrop: function(target, dragged, dropped, representation_id) {
eads@16 177 var representation = Drupal.settings.dndEditorRepresentations[representation_id];
eads@16 178 var $target = $(target), $dropped = $(dropped), $dragged = $(dragged), block;
eads@15 179
eads@16 180 // Search through block level parents
eads@16 181 $dropped.parents().each(function() {
eads@16 182 var $this = $(this);
eads@16 183 if ($this.css('display') == 'block') {
eads@16 184 block = this;
eads@16 185 return false;
eads@16 186 }
eads@16 187 });
eads@16 188
eads@16 189 // Remove dropped item
eads@16 190 $dropped.remove();
eads@16 191
eads@16 192 // Create an element to insert
eads@16 193 var insert = dom.create('p', {'class' : 'dnd-dropped-wrapper', 'id' : 'dnd-inserted'}, representation);
eads@16 194
eads@16 195 // The no-parent case
eads@16 196 if ($(block).is('body')) {
eads@16 197 s.setNode(insert);
eads@16 198 }
eads@16 199 else {
eads@16 200 var old_id = block.id;
eads@16 201 block.id = 'target-block';
eads@16 202 $block = $('#target-block', $target.contents());
eads@16 203
eads@16 204 // @TODO is finding the parent broken in safari??
eads@16 205 $block.after('<p class="dnd-dropped-wrapper" id="dnd-inserted">' + representation + '</p>');
eads@16 206
eads@16 207 // The active target block should be empty
eads@16 208 if ($('#target-block:empty', $target.contents()).length > 0) {
eads@17 209 $('#target-block', $target.contents()).remove();
eads@16 210 } else if (old_id) {
eads@16 211 block.id = old_id;
eads@16 212 } else {
eads@16 213 $block.removeAttr('id');
eads@16 214 }
eads@16 215 }
eads@16 216
eads@16 217 var $inserted = $('#dnd-inserted', $target.contents());
eads@16 218 var inserted = $inserted.get(0);
eads@16 219
eads@16 220 // Look behind in the DOM
eads@16 221 var previous = $inserted.prev().get(0);
eads@16 222
eads@16 223 // If the previous element is also an editor representation, we need to
eads@16 224 // put a dummy paragraph between the elements to prevent editor errors.
eads@16 225 if (previous && $(previous).hasClass('dnd-dropped-wrapper')) {
eads@16 226 $inserted.before('<p><span id="__spacer">_</span></p>');
eads@16 227 c = dom.get('__spacer');
eads@16 228 s.select(c);
eads@16 229 ed.execCommand('Delete', false, null);
eads@16 230 dom.remove(c);
eads@16 231 }
eads@16 232
eads@16 233 // Look ahead in the DOM
eads@16 234 var next = $inserted.next().get(0);
eads@16 235
eads@16 236 // If the next item exists and isn't an editor representation, drop the
eads@16 237 // caret at the beginning of the element, otherwise make a new paragraph
eads@16 238 // to advance the caret to.
eads@16 239 if (next && !$(next).hasClass('dnd-dropped-wrapper')) {
eads@16 240 $(next).prepend('<span id="__caret">_</span>');
eads@16 241 }
eads@16 242 else {
eads@16 243 var after = dom.create('p', {}, '<span id="__caret">_</span>');
eads@16 244 dom.insertAfter(after, 'dnd-inserted');
eads@16 245 }
eads@16 246
eads@16 247 // Clear the ID for the next drop
eads@16 248 $inserted.removeAttr('id');
eads@16 249
eads@16 250 // Force selection to reset the caret
eads@16 251 var c = dom.get('__caret');
eads@16 252 s.select(c);
eads@16 253 ed.execCommand('Delete', false, null);
eads@16 254 dom.remove(c);
eads@4 255
eads@4 256 // Add some classes to the library items
eads@16 257 $(dragged).addClass('dnd-inserted');
eads@16 258 drag_parents = $(dragged).parents('.editor-item');
eads@16 259 drag_parents.addClass('dnd-child-inserted');
eads@4 260 }
eads@4 261 }, settings);
eads@4 262
eads@4 263 $(settings.drop_selector).dnd(settings);
eads@2 264 }