annotate js/dnd-library.js @ 21:69db20fdbac2

Cleanup and refactoring.
author David Eads <eads@chicagotech.org>
date Tue, 10 Mar 2009 13:46:49 -0500
parents 89fe0aca43d4
children 4f58fa0a9a6d
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@16 35 // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg
eads@17 36 $editor.bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library);
eads@17 37 $editor.bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library);
eads@17 38
eads@20 39 // Add preview behavior to editor items (thanks, BeautyTips!)
eads@18 40 $('.editor-item', context).each(function () {
eads@18 41 $(this).bt(Drupal.settings.dndLibraryPreviews[this.id], {
eads@20 42 'trigger': 'none',
eads@21 43 'width': 375,
eads@20 44 'spikeLength': 7,
eads@20 45 'spikeGirth': 9,
eads@18 46 'corner-radius' : 3,
eads@20 47 'strokeWidth': 1,
eads@20 48 'fill': '#ffd',
eads@18 49 'strokeStyle': '#555'
eads@17 50 });
eads@20 51 $(this).hoverIntent({
eads@20 52 'interval': 500,
eads@20 53 'timeout' : 0,
eads@20 54 'over': function() {
eads@20 55 var $this = $(this);
eads@20 56 this.btOn();
eads@20 57 // Remove the preview once dragging of any image has commenced
eads@20 58 $('img', $this).bind('drag', function(e) {
eads@20 59 $this.btOff();
eads@20 60 });
eads@20 61 $('img', $this).bind('click', function(e) {
eads@20 62 $this.btOff();
eads@20 63 });
eads@20 64 },
eads@20 65 'out': function() { this.btOff(); }
eads@18 66 });
eads@17 67 });
eads@4 68
eads@20 69 // Simple AJAX pager
eads@17 70 $('.pager a', $this).click(function() {
eads@2 71 $.getJSON(this.href, function(data) {
eads@17 72 Drupal.behaviors.dndLibrary.refreshLibrary.call($this.get(0), data, $editor);
eads@2 73 });
eads@17 74 Drupal.behaviors.dndLibrary();
eads@2 75 return false;
eads@2 76 });
eads@17 77
eads@21 78 $('.view-filters input[type=submit]', $this).click(function() {
eads@20 79 $(this).ajaxSubmit({
eads@21 80 'url' : Drupal.settings.basePath + Drupal.settings.dndEnabledLibraries[$editor.get(0).id].url,
eads@20 81 'dataType': 'json',
eads@20 82 'success': function(responsetext, statustext) {
eads@21 83 Drupal.behaviors.dndLibrary.refreshLibrary.call($this.get(0), responsetext, $editor);
eads@21 84 Drupal.behaviors.dndLibrary();
eads@20 85 }
eads@20 86 });
eads@20 87 return false;
eads@20 88 });
eads@20 89
eads@17 90 // Preload images in editor representations
eads@17 91 var cached = $.data($editor, 'dnd_preload') || {};
eads@17 92 for (editor_id in Drupal.settings.dndEditorRepresentations) {
eads@17 93 if (!cached[editor_id]) {
eads@17 94 $representation = $(Drupal.settings.dndEditorRepresentations[editor_id]);
eads@17 95 if ($representation.is('img') && $representation.get(0).src) {
eads@17 96 $representation.attr('src', $representation.get(0).src);
eads@17 97 } else {
eads@17 98 $('img', $representation).each(function() {
eads@17 99 this.attr('src', this.src);
eads@17 100 });
eads@17 101 }
eads@17 102 }
eads@17 103 }
eads@17 104 $.data($editor, 'dnd_preload', cached);
eads@4 105 });
eads@4 106 }
eads@2 107
eads@17 108 Drupal.behaviors.dndLibrary.refreshLibrary = function(data, editor) {
eads@17 109 $this = $(this);
eads@17 110
eads@17 111 $('.header', $this).html(data.header);
eads@17 112 $('.library', $this).html(data.library);
eads@17 113 $('.footer', $this).html(data.footer);
eads@17 114
eads@17 115 var params = Drupal.wysiwyg.instances[editor.get(0).id];
eads@17 116 editor.trigger('wysiwygDetach', params);
eads@17 117 editor.trigger('wysiwygAttach', params);
eads@17 118
eads@17 119 for (editor_id in data.editor_representations) {
eads@17 120 Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id];
eads@17 121 }
eads@17 122 for (preview_id in data.library_previews) {
eads@17 123 Drupal.settings.dndLibraryPreviews[preview_id] = data.library_previews[preview_id];
eads@17 124 }
eads@19 125
eads@19 126 // Reattach behaviors
eads@19 127 Drupal.behaviors.dndLibrary();
eads@17 128 }
eads@17 129
eads@17 130
eads@16 131 // Dynamically compose a callback based on the editor name
eads@4 132 Drupal.behaviors.dndLibrary.attach_library = function(e, data) {
eads@17 133 var settings = $.extend({idSelector: Drupal.behaviors.dndLibrary.idSelector}, Drupal.settings.dndEnabledLibraries[data.field]);
eads@4 134 var editor_fn = 'attach_' + data.editor;
eads@4 135 if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) {
eads@17 136 window.Drupal.behaviors.dndLibrary[editor_fn](data, settings);
eads@2 137 }
eads@2 138 }
eads@2 139
eads@16 140 // Do garbage collection on detach
eads@4 141 Drupal.behaviors.dndLibrary.detach_library = function(e, data) {
eads@15 142 for (t in $(document).data('dnd_timers')) {
eads@15 143 clearInterval(t);
eads@15 144 }
eads@15 145 $(document).removeData('dnd_timers');
eads@4 146 }
eads@4 147
eads@16 148 // Basic textareas
eads@16 149 Drupal.behaviors.dndLibrary.attach_none = function(data, settings) {
eads@16 150 settings = $.extend({
eads@16 151 targets: $('#'+ data.field),
eads@20 152 processTextAreaClick: function(target, clicked, representation_id, e, data) {
eads@17 153 var snippet = '<p class="dnd-dropped-wrapper">' + Drupal.settings.dndEditorRepresentations[representation_id] + '</p>';
eads@16 154 $(target).replaceSelection(snippet, true);
eads@16 155 }
eads@16 156 }, settings);
eads@16 157 $(settings.drop_selector).dnd(settings);
eads@16 158 }
eads@4 159
eads@16 160 // Attach TinyMCE
eads@2 161 Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) {
eads@4 162 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 163
eads@4 164 // If the Tiny instance exists, attach directly, otherwise wait until Tiny
eads@4 165 // has registered a new instance.
eads@4 166 if (tiny_instance) {
eads@4 167 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4 168 } else {
eads@4 169 var t = setInterval(function() {
eads@4 170 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 171 if (tiny_instance) {
eads@4 172 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4 173 clearInterval(t);
eads@2 174 }
eads@4 175 }, 100);
eads@4 176 }
eads@4 177 }
eads@2 178
eads@17 179 Drupal.behaviors.dndLibrary.idSelector = function(element) {
eads@17 180 if ($(element).is('img')) {
eads@17 181 return $.url.setUrl(element.src).param('dnd_id');
eads@17 182 }
eads@17 183 return false;
eads@17 184 }
eads@17 185
eads@16 186 // Really attach TinyMCE
eads@4 187 Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) {
eads@16 188 var ed = tiny_instance, dom = ed.dom, s = ed.selection;
eads@16 189
eads@4 190 settings = $.extend({
eads@4 191 targets: $('#'+ data.field +'-wrapper iframe'),
eads@20 192 processIframeDrop: function(drop, id_selector) {
eads@20 193 var representation_id = id_selector.call(this, drop);
eads@16 194 var representation = Drupal.settings.dndEditorRepresentations[representation_id];
eads@20 195 var target = this, $target = $(target), $drop = $(drop), block;
eads@15 196
eads@16 197 // Search through block level parents
eads@20 198 $drop.parents().each(function() {
eads@16 199 var $this = $(this);
eads@16 200 if ($this.css('display') == 'block') {
eads@16 201 block = this;
eads@16 202 return false;
eads@16 203 }
eads@16 204 });
eads@16 205
eads@16 206 // Remove dropped item
eads@20 207 $drop.remove();
eads@16 208
eads@16 209 // Create an element to insert
eads@16 210 var insert = dom.create('p', {'class' : 'dnd-dropped-wrapper', 'id' : 'dnd-inserted'}, representation);
eads@16 211
eads@16 212 // The no-parent case
eads@16 213 if ($(block).is('body')) {
eads@16 214 s.setNode(insert);
eads@16 215 }
eads@16 216 else {
eads@16 217 var old_id = block.id;
eads@16 218 block.id = 'target-block';
eads@16 219 $block = $('#target-block', $target.contents());
eads@16 220
eads@16 221 // @TODO is finding the parent broken in safari??
eads@16 222 $block.after('<p class="dnd-dropped-wrapper" id="dnd-inserted">' + representation + '</p>');
eads@16 223
eads@16 224 // The active target block should be empty
eads@16 225 if ($('#target-block:empty', $target.contents()).length > 0) {
eads@17 226 $('#target-block', $target.contents()).remove();
eads@16 227 } else if (old_id) {
eads@16 228 block.id = old_id;
eads@16 229 } else {
eads@16 230 $block.removeAttr('id');
eads@16 231 }
eads@16 232 }
eads@16 233
eads@16 234 var $inserted = $('#dnd-inserted', $target.contents());
eads@16 235 var inserted = $inserted.get(0);
eads@16 236
eads@16 237 // Look behind in the DOM
eads@16 238 var previous = $inserted.prev().get(0);
eads@16 239
eads@16 240 // If the previous element is also an editor representation, we need to
eads@16 241 // put a dummy paragraph between the elements to prevent editor errors.
eads@16 242 if (previous && $(previous).hasClass('dnd-dropped-wrapper')) {
eads@16 243 $inserted.before('<p><span id="__spacer">_</span></p>');
eads@16 244 c = dom.get('__spacer');
eads@16 245 s.select(c);
eads@16 246 ed.execCommand('Delete', false, null);
eads@16 247 dom.remove(c);
eads@16 248 }
eads@16 249
eads@16 250 // Look ahead in the DOM
eads@16 251 var next = $inserted.next().get(0);
eads@16 252
eads@16 253 // If the next item exists and isn't an editor representation, drop the
eads@16 254 // caret at the beginning of the element, otherwise make a new paragraph
eads@16 255 // to advance the caret to.
eads@20 256 if (next && !$(next).hasClass('dnd-droped-wrapper')) {
eads@16 257 $(next).prepend('<span id="__caret">_</span>');
eads@16 258 }
eads@16 259 else {
eads@16 260 var after = dom.create('p', {}, '<span id="__caret">_</span>');
eads@16 261 dom.insertAfter(after, 'dnd-inserted');
eads@16 262 }
eads@16 263
eads@16 264 // Clear the ID for the next drop
eads@16 265 $inserted.removeAttr('id');
eads@16 266
eads@16 267 // Force selection to reset the caret
eads@16 268 var c = dom.get('__caret');
eads@16 269 s.select(c);
eads@16 270 ed.execCommand('Delete', false, null);
eads@16 271 dom.remove(c);
eads@4 272 }
eads@4 273 }, settings);
eads@4 274
eads@4 275 $(settings.drop_selector).dnd(settings);
eads@2 276 }