annotate js/dnd-library.js @ 31:767ebf925654

Added forcecontainer tinymce plugin, lots and lots and lots of refactorizing.
author David Eads <eads@chicagotech.org>
date Thu, 19 Mar 2009 15:58:36 -0500
parents 2d49adbd8992
children ee520ba7d98b
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@31 20 'dnd_empty' : function(a, i, m) {
eads@17 21 return !$(a).filter(function(i) {
eads@17 22 return !$(this).is('br');
eads@30 23 }).length && !$.trim(a.textContent || a.innerText||$(a).text() || "");
eads@16 24 }
eads@16 25 });
eads@16 26 }) (jQuery);
eads@16 27
eads@30 28 /**
eads@30 29 * Initialize and load drag and drop library and pass off rendering and
eads@30 30 * behavior attachment.
eads@30 31 */
eads@2 32 Drupal.behaviors.dndLibrary = function(context) {
eads@16 33 $('.dnd-library-wrapper', context).each(function() {
eads@4 34 var $this = $(this);
eads@2 35
eads@4 36 // This is a bad hack to lop off '-dnd-library' from the id to get the editor name
eads@17 37 var $editor = $('#' + this.id.slice(0, -12));
eads@17 38
eads@30 39 // Set up some initial settings for BeautyTips
eads@30 40 var settings = Drupal.settings.dndEnabledLibraries[$editor.get(0).id] = $.extend({
eads@31 41 'btSettings' : {
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@31 50 },
eads@31 51 'libraryHoverIntentSettings' : {
eads@31 52 'interval': 500,
eads@31 53 'timeout' : 0,
eads@31 54 'over': function() {
eads@31 55 var $this = $(this);
eads@31 56 this.btOn();
eads@31 57 // Remove the preview once dragging of any image has commenced
eads@31 58 $('img', $this).bind('drag', function(e) {
eads@31 59 $this.btOff();
eads@31 60 });
eads@31 61 $('img', $this).bind('click', function(e) {
eads@31 62 $this.btOff();
eads@31 63 });
eads@31 64 },
eads@31 65 'out': function() { this.btOff(); }
eads@31 66 }
eads@30 67 }, Drupal.settings.dndEnabledLibraries[$editor.get(0).id]);
eads@24 68
eads@24 69 // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg
eads@24 70 $editor.bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library);
eads@24 71 $editor.bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library);
eads@24 72
eads@30 73 // Set up empty objects to keep track of things
eads@30 74 Drupal.settings.dndEditorRepresentations = {};
eads@30 75 Drupal.settings.dndLibraryPreviews = {};
eads@30 76
eads@31 77 // Initialize the library
eads@30 78 $.getJSON(Drupal.settings.basePath + settings.url, function(data) {
eads@30 79 Drupal.behaviors.dndLibrary.renderLibrary.call($this.get(0), data, $editor);
eads@17 80 });
eads@4 81
eads@4 82 });
eads@4 83 }
eads@2 84
eads@30 85 Drupal.behaviors.dndLibrary.renderLibrary = function(data, editor) {
eads@17 86 $this = $(this);
eads@17 87
eads@30 88 $this.html(data.library);
eads@17 89
eads@30 90 var settings = Drupal.settings.dndEnabledLibraries[editor.get(0).id];
eads@17 91 var params = Drupal.wysiwyg.instances[editor.get(0).id];
eads@30 92
eads@17 93 editor.trigger('wysiwygDetach', params);
eads@17 94 editor.trigger('wysiwygAttach', params);
eads@17 95
eads@17 96 for (editor_id in data.editor_representations) {
eads@17 97 Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id];
eads@17 98 }
eads@17 99 for (preview_id in data.library_previews) {
eads@17 100 Drupal.settings.dndLibraryPreviews[preview_id] = data.library_previews[preview_id];
eads@17 101 }
eads@19 102
eads@30 103 // Add preview behavior to editor items (thanks, BeautyTips!)
eads@30 104 $('.editor-item', $this).each(function () {
eads@31 105 $(this).bt(Drupal.settings.dndLibraryPreviews[this.id], settings.btSettings);
eads@31 106 $(this).hoverIntent(settings.libraryHoverIntentSettings);
eads@30 107 });
eads@30 108
eads@30 109 // Preload images in editor representations
eads@30 110 var cached = $.data($(editor), 'dnd_preload') || {};
eads@31 111 for (editor_id in Drupal.settings.dndEditorRepresentations) {
eads@30 112 if (!cached[editor_id]) {
eads@30 113 $representation = $(Drupal.settings.dndEditorRepresentations[editor_id].body);
eads@30 114 if ($representation.is('img') && $representation.get(0).src) {
eads@30 115 $representation.attr('src', $representation.get(0).src);
eads@30 116 } else {
eads@30 117 $('img', $representation).each(function() {
eads@31 118 $(this).attr('src', this.src);
eads@30 119 });
eads@30 120 }
eads@30 121 }
eads@30 122 }
eads@31 123 $.data($(editor), 'dnd_preload', cached);
eads@30 124
eads@31 125 $('.pager a', $this).click(function() {
eads@31 126 $.getJSON(this.href, function(data) {
eads@31 127 Drupal.behaviors.dndLibrary.renderLibrary.call($this.get(0), data, $(editor));
eads@31 128 });
eads@31 129 return false;
eads@31 130 });
eads@31 131 $('.view-filters input[type=submit]', $this).click(function() {
eads@31 132 $(this).ajaxSubmit({
eads@31 133 'url' : Drupal.settings.basePath + settings.url,
eads@31 134 'dataType' : 'json',
eads@31 135 'success' : function(data) {
eads@31 136 Drupal.behaviors.dndLibrary.renderLibrary.call($this.get(0), data, $(editor));
eads@31 137 }
eads@31 138 });
eads@31 139 return false;
eads@31 140 });
eads@17 141 }
eads@17 142
eads@16 143 // Dynamically compose a callback based on the editor name
eads@4 144 Drupal.behaviors.dndLibrary.attach_library = function(e, data) {
eads@17 145 var settings = $.extend({idSelector: Drupal.behaviors.dndLibrary.idSelector}, Drupal.settings.dndEnabledLibraries[data.field]);
eads@4 146 var editor_fn = 'attach_' + data.editor;
eads@4 147 if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) {
eads@17 148 window.Drupal.behaviors.dndLibrary[editor_fn](data, settings);
eads@2 149 }
eads@2 150 }
eads@2 151
eads@16 152 // Do garbage collection on detach
eads@31 153 Drupal.behaviors.dndLibrary.detach_library = function(e, data) {}
eads@4 154
eads@16 155 // Basic textareas
eads@16 156 Drupal.behaviors.dndLibrary.attach_none = function(data, settings) {
eads@16 157 settings = $.extend({
eads@16 158 targets: $('#'+ data.field),
eads@25 159 processTextAreaClick: function(clicked, representation_id, e, data) {
eads@25 160 var target = this, $target = $(target);
eads@25 161
eads@25 162 // Update element count
eads@25 163 Drupal.behaviors.dndLibrary.countElements.call(target, representation_id);
eads@25 164
eads@31 165 var snippet = '<p class="dnd-drop-wrapper">' + Drupal.settings.dndEditorRepresentations[representation_id].body + '</p>';
eads@25 166 $target.replaceSelection(snippet, true);
eads@16 167 }
eads@16 168 }, settings);
eads@16 169 $(settings.drop_selector).dnd(settings);
eads@16 170 }
eads@4 171
eads@16 172 // Attach TinyMCE
eads@2 173 Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) {
eads@4 174 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 175
eads@4 176 // If the Tiny instance exists, attach directly, otherwise wait until Tiny
eads@4 177 // has registered a new instance.
eads@4 178 if (tiny_instance) {
eads@4 179 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4 180 } else {
eads@4 181 var t = setInterval(function() {
eads@4 182 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 183 if (tiny_instance) {
eads@4 184 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@24 185 $('#'+ data.field +'-wrapper').trigger('dnd_attach_library');
eads@4 186 clearInterval(t);
eads@2 187 }
eads@4 188 }, 100);
eads@4 189 }
eads@4 190 }
eads@2 191
eads@17 192 Drupal.behaviors.dndLibrary.idSelector = function(element) {
eads@17 193 if ($(element).is('img')) {
eads@17 194 return $.url.setUrl(element.src).param('dnd_id');
eads@17 195 }
eads@17 196 return false;
eads@17 197 }
eads@17 198
eads@16 199 // Really attach TinyMCE
eads@4 200 Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) {
eads@16 201 var ed = tiny_instance, dom = ed.dom, s = ed.selection;
eads@16 202
eads@4 203 settings = $.extend({
eads@4 204 targets: $('#'+ data.field +'-wrapper iframe'),
eads@31 205 processTargets: function(targets) {
eads@31 206 return targets.each(function() {
eads@31 207 var target = this
eads@31 208 // Decrement counter on delete
eads@31 209 $(target).bind('dnd_delete', function(e, data) {
eads@31 210 Drupal.behaviors.dndLibrary.countElements(target, $(data.node).attr('dnd_id'), true);
eads@31 211 });
eads@31 212 $('head', $(this).contents()).append('<style type="text/css">img { display: none; } img.dnd-dropped {display: block; }</style>');
eads@31 213 return this;
eads@31 214 });
eads@31 215 },
eads@20 216 processIframeDrop: function(drop, id_selector) {
eads@20 217 var representation_id = id_selector.call(this, drop);
eads@24 218 var representation = Drupal.settings.dndEditorRepresentations[representation_id].body;
eads@20 219 var target = this, $target = $(target), $drop = $(drop), block;
eads@15 220
eads@25 221 // Update element count
eads@31 222 Drupal.behaviors.dndLibrary.countElements(target, representation_id);
eads@24 223
eads@16 224 // Search through block level parents
eads@20 225 $drop.parents().each(function() {
eads@16 226 var $this = $(this);
eads@16 227 if ($this.css('display') == 'block') {
eads@16 228 block = this;
eads@16 229 return false;
eads@16 230 }
eads@16 231 });
eads@16 232
eads@16 233 // Remove dropped item
eads@20 234 $drop.remove();
eads@16 235
eads@16 236 // Create an element to insert
eads@31 237 var insert = dom.create('p', {'class' : 'dnd-drop-wrapper', 'id' : 'dnd-inserted'}, representation);
eads@16 238
eads@16 239 // The no-parent case
eads@16 240 if ($(block).is('body')) {
eads@16 241 s.setNode(insert);
eads@16 242 }
eads@16 243 else {
eads@16 244 var old_id = block.id;
eads@16 245 block.id = 'target-block';
eads@16 246 $block = $('#target-block', $target.contents());
eads@16 247
eads@16 248 // @TODO is finding the parent broken in safari??
eads@31 249 $block.after('<p class="dnd-drop-wrapper" id="dnd-inserted">' + representation + '</p>');
eads@16 250
eads@16 251 // The active target block should be empty
eads@31 252 if ($('#target-block:dnd_empty', $target.contents()).length > 0) {
eads@17 253 $('#target-block', $target.contents()).remove();
eads@16 254 } else if (old_id) {
eads@16 255 block.id = old_id;
eads@16 256 } else {
eads@16 257 $block.removeAttr('id');
eads@16 258 }
eads@31 259 }
eads@16 260
eads@16 261 var $inserted = $('#dnd-inserted', $target.contents());
eads@16 262 var inserted = $inserted.get(0);
eads@16 263
eads@16 264 // Look behind in the DOM
eads@16 265 var previous = $inserted.prev().get(0);
eads@16 266
eads@16 267 // If the previous element is also an editor representation, we need to
eads@16 268 // put a dummy paragraph between the elements to prevent editor errors.
eads@31 269 if (previous ) {
eads@31 270 $inserted.before('<p></p>');
eads@16 271 }
eads@16 272
eads@16 273 // Look ahead in the DOM
eads@16 274 var next = $inserted.next().get(0);
eads@16 275
eads@16 276 // If the next item exists and isn't an editor representation, drop the
eads@16 277 // caret at the beginning of the element, otherwise make a new paragraph
eads@16 278 // to advance the caret to.
eads@31 279 if (next && !$(next).hasClass('dnd-drop-wrapper')) {
eads@16 280 $(next).prepend('<span id="__caret">_</span>');
eads@16 281 }
eads@31 282 else if (!$(next).hasClass('dnd-drop-wrapper')) {
eads@16 283 var after = dom.create('p', {}, '<span id="__caret">_</span>');
eads@16 284 dom.insertAfter(after, 'dnd-inserted');
eads@16 285 }
eads@31 286
eads@16 287 // Force selection to reset the caret
eads@16 288 var c = dom.get('__caret');
eads@31 289 if (c) {
eads@31 290 s.select(c);
eads@31 291 ed.execCommand('Delete', false, null);
eads@31 292 dom.remove(c);
eads@31 293 }
eads@31 294
eads@31 295 // Unset id for next drop and add special dnd attribute for counting
eads@31 296 // purposes
eads@31 297 $inserted
eads@31 298 .removeAttr('id')
eads@31 299 .attr('dnd_id', representation_id);
eads@31 300
eads@4 301 }
eads@4 302 }, settings);
eads@4 303
eads@4 304 $(settings.drop_selector).dnd(settings);
eads@2 305 }
eads@25 306
eads@25 307
eads@25 308 // Keep a counter of times a representation ID has been used
eads@31 309 Drupal.behaviors.dndLibrary.countElements = function(target, representation_id, decrement) {
eads@31 310 var counter = $(target).data('dnd_representation_counter');
eads@25 311 if (!counter) {
eads@25 312 counter = {}
eads@25 313 counter[representation_id] = 1;
eads@25 314 } else if (counter && !counter[representation_id]) {
eads@25 315 counter[representation_id] = 1;
eads@25 316 } else {
eads@31 317 counter[representation_id] = counter[representation_id] + ((decrement) ? -1 : 1);
eads@25 318 }
eads@31 319 $(target).data('dnd_representation_counter', counter);
eads@31 320 return counter[representation_id];
eads@25 321 }