annotate js/dnd-library.js @ 17:1a77f87927dd

Fixed :empty custom selector behavior in IE, a little refactoring, etc.
author David Eads <eads@chicagotech.org>
date Wed, 04 Mar 2009 13:29:31 -0600
parents bb68dc3ad56f
children 0d557e6e73f7
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@17 40 // Add basic hover behavior to editor items
eads@17 41 $('.editor-item', context).hover(function() {
eads@17 42 var $this = $(this);
eads@17 43 var p = $('#edit-body-wrapper').position();
eads@17 44 var preview = $(Drupal.settings.dndLibraryPreviews[this.id]).css({
eads@17 45 'position' : 'absolute',
eads@17 46 'top' : p.top + 150,
eads@17 47 'left' : p.left + $('#edit-body-wrapper').width() + 150,
eads@17 48 'display' : 'none',
eads@17 49 'width' : '300px',
eads@17 50 'background-color' : '#ddd',
eads@17 51 'border' : '3px solid #999',
eads@17 52 'padding' : '4px',
eads@17 53 'z-index' : 10
eads@17 54 });
eads@17 55 $('body').prepend(preview);
eads@17 56 preview.fadeIn('slow');
eads@17 57 }, function() {
eads@17 58 $('#' + this.id.replace(/test/,'preview')).fadeOut('fast', function() { $(this).remove(); });
eads@17 59 });
eads@4 60
eads@4 61 // Ajax pager
eads@17 62 $('.pager a', $this).click(function() {
eads@2 63 $.getJSON(this.href, function(data) {
eads@17 64 Drupal.behaviors.dndLibrary.refreshLibrary.call($this.get(0), data, $editor);
eads@2 65 });
eads@17 66
eads@17 67 // Reattach behaviors
eads@17 68 Drupal.behaviors.dndLibrary();
eads@17 69
eads@2 70 return false;
eads@2 71 });
eads@17 72
eads@17 73 // Preload images in editor representations
eads@17 74 var cached = $.data($editor, 'dnd_preload') || {};
eads@17 75 for (editor_id in Drupal.settings.dndEditorRepresentations) {
eads@17 76 if (!cached[editor_id]) {
eads@17 77 $representation = $(Drupal.settings.dndEditorRepresentations[editor_id]);
eads@17 78 if ($representation.is('img') && $representation.get(0).src) {
eads@17 79 $representation.attr('src', $representation.get(0).src);
eads@17 80 } else {
eads@17 81 $('img', $representation).each(function() {
eads@17 82 this.attr('src', this.src);
eads@17 83 });
eads@17 84 }
eads@17 85 }
eads@17 86 }
eads@17 87 $.data($editor, 'dnd_preload', cached);
eads@4 88 });
eads@4 89 }
eads@2 90
eads@17 91 Drupal.behaviors.dndLibrary.refreshLibrary = function(data, editor) {
eads@17 92 $this = $(this);
eads@17 93
eads@17 94 $('.header', $this).html(data.header);
eads@17 95 $('.library', $this).html(data.library);
eads@17 96 $('.footer', $this).html(data.footer);
eads@17 97
eads@17 98 var params = Drupal.wysiwyg.instances[editor.get(0).id];
eads@17 99 editor.trigger('wysiwygDetach', params);
eads@17 100 editor.trigger('wysiwygAttach', params);
eads@17 101
eads@17 102 for (editor_id in data.editor_representations) {
eads@17 103 Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id];
eads@17 104 }
eads@17 105 for (preview_id in data.library_previews) {
eads@17 106 Drupal.settings.dndLibraryPreviews[preview_id] = data.library_previews[preview_id];
eads@17 107 }
eads@17 108 }
eads@17 109
eads@17 110
eads@16 111 // Dynamically compose a callback based on the editor name
eads@4 112 Drupal.behaviors.dndLibrary.attach_library = function(e, data) {
eads@17 113 var settings = $.extend({idSelector: Drupal.behaviors.dndLibrary.idSelector}, Drupal.settings.dndEnabledLibraries[data.field]);
eads@4 114 var editor_fn = 'attach_' + data.editor;
eads@4 115 if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) {
eads@17 116 window.Drupal.behaviors.dndLibrary[editor_fn](data, settings);
eads@2 117 }
eads@2 118 }
eads@2 119
eads@16 120 // Do garbage collection on detach
eads@4 121 Drupal.behaviors.dndLibrary.detach_library = function(e, data) {
eads@15 122 for (t in $(document).data('dnd_timers')) {
eads@15 123 clearInterval(t);
eads@15 124 }
eads@15 125 $(document).removeData('dnd_timers');
eads@4 126 }
eads@4 127
eads@16 128 // Basic textareas
eads@16 129 Drupal.behaviors.dndLibrary.attach_none = function(data, settings) {
eads@16 130 settings = $.extend({
eads@16 131 targets: $('#'+ data.field),
eads@17 132 processTextAreaDrop: function(target, clicked, representation_id, e, data) {
eads@17 133 var snippet = '<p class="dnd-dropped-wrapper">' + Drupal.settings.dndEditorRepresentations[representation_id] + '</p>';
eads@16 134 $(target).replaceSelection(snippet, true);
eads@16 135 }
eads@16 136 }, settings);
eads@16 137 $(settings.drop_selector).dnd(settings);
eads@16 138 }
eads@4 139
eads@16 140 // Attach TinyMCE
eads@2 141 Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) {
eads@4 142 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 143
eads@4 144 // If the Tiny instance exists, attach directly, otherwise wait until Tiny
eads@4 145 // has registered a new instance.
eads@4 146 if (tiny_instance) {
eads@4 147 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4 148 } else {
eads@4 149 var t = setInterval(function() {
eads@4 150 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 151 if (tiny_instance) {
eads@4 152 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4 153 clearInterval(t);
eads@2 154 }
eads@4 155 }, 100);
eads@4 156 }
eads@4 157 }
eads@2 158
eads@17 159 Drupal.behaviors.dndLibrary.idSelector = function(element) {
eads@17 160 if ($(element).is('img')) {
eads@17 161 return $.url.setUrl(element.src).param('dnd_id');
eads@17 162 }
eads@17 163 return false;
eads@17 164 }
eads@17 165
eads@16 166 // Really attach TinyMCE
eads@4 167 Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) {
eads@16 168 var ed = tiny_instance, dom = ed.dom, s = ed.selection;
eads@16 169
eads@4 170 settings = $.extend({
eads@4 171 targets: $('#'+ data.field +'-wrapper iframe'),
eads@16 172 interval: 100,
eads@16 173 processIframeDrop: function(target, dragged, dropped, representation_id) {
eads@16 174 var representation = Drupal.settings.dndEditorRepresentations[representation_id];
eads@16 175 var $target = $(target), $dropped = $(dropped), $dragged = $(dragged), block;
eads@15 176
eads@16 177 // Search through block level parents
eads@16 178 $dropped.parents().each(function() {
eads@16 179 var $this = $(this);
eads@16 180 if ($this.css('display') == 'block') {
eads@16 181 block = this;
eads@16 182 return false;
eads@16 183 }
eads@16 184 });
eads@16 185
eads@16 186 // Remove dropped item
eads@16 187 $dropped.remove();
eads@16 188
eads@16 189 // Create an element to insert
eads@16 190 var insert = dom.create('p', {'class' : 'dnd-dropped-wrapper', 'id' : 'dnd-inserted'}, representation);
eads@16 191
eads@16 192 // The no-parent case
eads@16 193 if ($(block).is('body')) {
eads@16 194 s.setNode(insert);
eads@16 195 }
eads@16 196 else {
eads@16 197 var old_id = block.id;
eads@16 198 block.id = 'target-block';
eads@16 199 $block = $('#target-block', $target.contents());
eads@16 200
eads@16 201 // @TODO is finding the parent broken in safari??
eads@16 202 $block.after('<p class="dnd-dropped-wrapper" id="dnd-inserted">' + representation + '</p>');
eads@16 203
eads@16 204 // The active target block should be empty
eads@16 205 if ($('#target-block:empty', $target.contents()).length > 0) {
eads@17 206 $('#target-block', $target.contents()).remove();
eads@16 207 } else if (old_id) {
eads@16 208 block.id = old_id;
eads@16 209 } else {
eads@16 210 $block.removeAttr('id');
eads@16 211 }
eads@16 212 }
eads@16 213
eads@16 214 var $inserted = $('#dnd-inserted', $target.contents());
eads@16 215 var inserted = $inserted.get(0);
eads@16 216
eads@16 217 // Look behind in the DOM
eads@16 218 var previous = $inserted.prev().get(0);
eads@16 219
eads@16 220 // If the previous element is also an editor representation, we need to
eads@16 221 // put a dummy paragraph between the elements to prevent editor errors.
eads@16 222 if (previous && $(previous).hasClass('dnd-dropped-wrapper')) {
eads@16 223 $inserted.before('<p><span id="__spacer">_</span></p>');
eads@16 224 c = dom.get('__spacer');
eads@16 225 s.select(c);
eads@16 226 ed.execCommand('Delete', false, null);
eads@16 227 dom.remove(c);
eads@16 228 }
eads@16 229
eads@16 230 // Look ahead in the DOM
eads@16 231 var next = $inserted.next().get(0);
eads@16 232
eads@16 233 // If the next item exists and isn't an editor representation, drop the
eads@16 234 // caret at the beginning of the element, otherwise make a new paragraph
eads@16 235 // to advance the caret to.
eads@16 236 if (next && !$(next).hasClass('dnd-dropped-wrapper')) {
eads@16 237 $(next).prepend('<span id="__caret">_</span>');
eads@16 238 }
eads@16 239 else {
eads@16 240 var after = dom.create('p', {}, '<span id="__caret">_</span>');
eads@16 241 dom.insertAfter(after, 'dnd-inserted');
eads@16 242 }
eads@16 243
eads@16 244 // Clear the ID for the next drop
eads@16 245 $inserted.removeAttr('id');
eads@16 246
eads@16 247 // Force selection to reset the caret
eads@16 248 var c = dom.get('__caret');
eads@16 249 s.select(c);
eads@16 250 ed.execCommand('Delete', false, null);
eads@16 251 dom.remove(c);
eads@4 252
eads@4 253 // Add some classes to the library items
eads@16 254 $(dragged).addClass('dnd-inserted');
eads@16 255 drag_parents = $(dragged).parents('.editor-item');
eads@16 256 drag_parents.addClass('dnd-child-inserted');
eads@4 257 }
eads@4 258 }, settings);
eads@4 259
eads@4 260 $(settings.drop_selector).dnd(settings);
eads@2 261 }