annotate js/dnd-library.js @ 52:482aa8971743

Add all the body classes to the RTE iframe
author Franck Deroche <franck@defr.org>
date Tue, 20 Jul 2010 14:20:01 +0200
parents 5cf2b262b9e4
children 4e975bbf1bfe
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) {
franck@51 33 if (!Drupal.settings.dndEnabledLibraries) {
franck@51 34 return;
franck@51 35 }
eads@16 36 $('.dnd-library-wrapper', context).each(function() {
eads@4 37 var $this = $(this);
eads@2 38
eads@4 39 // This is a bad hack to lop off '-dnd-library' from the id to get the editor name
eads@17 40 var $editor = $('#' + this.id.slice(0, -12));
eads@17 41
eads@30 42 // Set up some initial settings for BeautyTips
eads@30 43 var settings = Drupal.settings.dndEnabledLibraries[$editor.get(0).id] = $.extend({
eads@31 44 'btSettings' : {
franck@35 45 'trigger': ['click'],
franck@41 46 'width': 480,
eads@20 47 'spikeLength': 7,
eads@20 48 'spikeGirth': 9,
eads@18 49 'corner-radius' : 3,
eads@20 50 'strokeWidth': 1,
eads@20 51 'fill': '#ffd',
franck@35 52 'strokeStyle': '#555',
franck@35 53 'closeWhenOthersOpen': true
eads@31 54 },
eads@31 55 'libraryHoverIntentSettings' : {
eads@31 56 'interval': 500,
eads@31 57 'timeout' : 0,
eads@31 58 'over': function() {
eads@31 59 var $this = $(this);
eads@31 60 this.btOn();
eads@31 61 // Remove the preview once dragging of any image has commenced
eads@31 62 $('img', $this).bind('drag', function(e) {
eads@31 63 $this.btOff();
eads@31 64 });
eads@31 65 $('img', $this).bind('click', function(e) {
eads@31 66 $this.btOff();
eads@31 67 });
eads@31 68 },
eads@31 69 'out': function() { this.btOff(); }
eads@31 70 }
eads@30 71 }, Drupal.settings.dndEnabledLibraries[$editor.get(0).id]);
eads@24 72
eads@24 73 // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg
eads@24 74 $editor.bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library);
eads@24 75 $editor.bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library);
eads@24 76
eads@30 77 // Set up empty objects to keep track of things
eads@30 78 Drupal.settings.dndEditorRepresentations = {};
eads@30 79 Drupal.settings.dndLibraryPreviews = {};
franck@36 80
franck@36 81 // Clean up the url if needed (this happen in case drupal_add_js is called
franck@36 82 // multiple time for the page)
franck@36 83 if (settings.url instanceof Object) {
franck@36 84 settings.url = settings.url[0];
franck@36 85 }
eads@30 86
eads@31 87 // Initialize the library
defr@47 88 var wrapper = $this.get(0);
defr@47 89 wrapper.library_url = Drupal.settings.basePath + settings.url;
eads@30 90 $.getJSON(Drupal.settings.basePath + settings.url, function(data) {
defr@47 91 Drupal.behaviors.dndLibrary.renderLibrary.call(wrapper, data, $editor);
eads@17 92 });
eads@4 93
eads@4 94 });
eads@4 95 }
eads@2 96
eads@30 97 Drupal.behaviors.dndLibrary.renderLibrary = function(data, editor) {
eads@17 98 $this = $(this);
eads@17 99
eads@30 100 $this.html(data.library);
eads@17 101
eads@30 102 var settings = Drupal.settings.dndEnabledLibraries[editor.get(0).id];
eads@17 103 var params = Drupal.wysiwyg.instances[editor.get(0).id];
eads@30 104
eads@17 105 editor.trigger('wysiwygDetach', params);
eads@17 106 editor.trigger('wysiwygAttach', params);
eads@17 107
eads@17 108 for (editor_id in data.editor_representations) {
eads@17 109 Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id];
eads@17 110 }
eads@17 111 for (preview_id in data.library_previews) {
eads@17 112 Drupal.settings.dndLibraryPreviews[preview_id] = data.library_previews[preview_id];
eads@17 113 }
eads@19 114
eads@30 115 // Add preview behavior to editor items (thanks, BeautyTips!)
eads@30 116 $('.editor-item', $this).each(function () {
eads@31 117 $(this).bt(Drupal.settings.dndLibraryPreviews[this.id], settings.btSettings);
franck@43 118 //$(this).hoverIntent(settings.libraryHoverIntentSettings);
eads@30 119 });
eads@30 120
eads@30 121 // Preload images in editor representations
eads@30 122 var cached = $.data($(editor), 'dnd_preload') || {};
eads@31 123 for (editor_id in Drupal.settings.dndEditorRepresentations) {
eads@30 124 if (!cached[editor_id]) {
eads@30 125 $representation = $(Drupal.settings.dndEditorRepresentations[editor_id].body);
eads@30 126 if ($representation.is('img') && $representation.get(0).src) {
eads@30 127 $representation.attr('src', $representation.get(0).src);
eads@30 128 } else {
eads@30 129 $('img', $representation).each(function() {
eads@31 130 $(this).attr('src', this.src);
eads@30 131 });
eads@30 132 }
eads@30 133 }
eads@30 134 }
eads@31 135 $.data($(editor), 'dnd_preload', cached);
eads@30 136
eads@31 137 $('.pager a', $this).click(function() {
franck@42 138 // At page switching, close all opened BeautyTips.
franck@42 139 $('.editor-item.bt-active').btOff();
defr@47 140 $this.get(0).library_url = this.href;
eads@31 141 $.getJSON(this.href, function(data) {
eads@31 142 Drupal.behaviors.dndLibrary.renderLibrary.call($this.get(0), data, $(editor));
eads@31 143 });
eads@31 144 return false;
eads@31 145 });
eads@31 146 $('.view-filters input[type=submit]', $this).click(function() {
defr@44 147 var submit = $(this);
franck@32 148 $('.view-filters form', $this).ajaxSubmit({
eads@31 149 'url' : Drupal.settings.basePath + settings.url,
eads@31 150 'dataType' : 'json',
eads@31 151 'success' : function(data) {
defr@47 152 var target = submit.parents('div.dnd-library-wrapper').get(0);
defr@47 153 target.library_url = this.url;
defr@44 154 Drupal.behaviors.dndLibrary.renderLibrary.call(target, data, $(editor));
eads@31 155 }
eads@31 156 });
eads@31 157 return false;
eads@31 158 });
defr@46 159 $('.view-filters input[type=reset]', $this).click(function() {
defr@46 160 var reset = $(this);
defr@46 161 $('.view-filters form', $this).ajaxSubmit({
defr@46 162 'url' : Drupal.settings.basePath + settings.url,
defr@46 163 'dataType' : 'json',
defr@46 164 'success' : function(data) {
defr@47 165 var target = reset.parents('div.dnd-library-wrapper').get(0);
defr@47 166 target.library_url = Drupal.dndEnabledLibraries[editor].url;
defr@46 167 Drupal.behaviors.dndLibrary.renderLibrary.call(target, data, $(editor));
defr@46 168 },
defr@46 169 'beforeSubmit': function (data, form, options) {
defr@46 170 // Can't use data = [], otherwise we're creating a new array
defr@46 171 // instead of modifying the existing one.
defr@46 172 data.splice(0, data.length);
defr@46 173 }
defr@46 174 });
defr@46 175 return false;
defr@46 176 });
franck@37 177 Drupal.attachBehaviors($this);
eads@17 178 }
eads@17 179
eads@16 180 // Dynamically compose a callback based on the editor name
eads@4 181 Drupal.behaviors.dndLibrary.attach_library = function(e, data) {
eads@17 182 var settings = $.extend({idSelector: Drupal.behaviors.dndLibrary.idSelector}, Drupal.settings.dndEnabledLibraries[data.field]);
eads@4 183 var editor_fn = 'attach_' + data.editor;
eads@4 184 if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) {
eads@17 185 window.Drupal.behaviors.dndLibrary[editor_fn](data, settings);
eads@2 186 }
eads@2 187 }
eads@2 188
eads@16 189 // Do garbage collection on detach
eads@31 190 Drupal.behaviors.dndLibrary.detach_library = function(e, data) {}
eads@4 191
eads@16 192 // Basic textareas
eads@16 193 Drupal.behaviors.dndLibrary.attach_none = function(data, settings) {
eads@16 194 settings = $.extend({
eads@16 195 targets: $('#'+ data.field),
eads@25 196 processTextAreaClick: function(clicked, representation_id, e, data) {
eads@25 197 var target = this, $target = $(target);
eads@25 198
eads@25 199 // Update element count
eads@25 200 Drupal.behaviors.dndLibrary.countElements.call(target, representation_id);
eads@25 201
franck@38 202 var rep = Drupal.settings.dndEditorRepresentations[representation_id];
franck@38 203 var snippet = '<div class="dnd-drop-wrapper">' + rep.body + '</div>';
franck@38 204 if (rep.meta.legend) {
franck@38 205 snippet += rep.meta.legend;
franck@38 206 }
eads@25 207 $target.replaceSelection(snippet, true);
eads@16 208 }
eads@16 209 }, settings);
eads@16 210 $(settings.drop_selector).dnd(settings);
eads@16 211 }
eads@4 212
eads@16 213 // Attach TinyMCE
eads@2 214 Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) {
eads@4 215 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 216
eads@4 217 // If the Tiny instance exists, attach directly, otherwise wait until Tiny
eads@4 218 // has registered a new instance.
eads@4 219 if (tiny_instance) {
eads@4 220 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4 221 } else {
eads@4 222 var t = setInterval(function() {
eads@4 223 var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4 224 if (tiny_instance) {
eads@4 225 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@24 226 $('#'+ data.field +'-wrapper').trigger('dnd_attach_library');
eads@4 227 clearInterval(t);
eads@2 228 }
eads@4 229 }, 100);
eads@4 230 }
eads@4 231 }
eads@2 232
eads@17 233 Drupal.behaviors.dndLibrary.idSelector = function(element) {
eads@17 234 if ($(element).is('img')) {
eads@17 235 return $.url.setUrl(element.src).param('dnd_id');
eads@17 236 }
eads@17 237 return false;
eads@17 238 }
eads@17 239
eads@16 240 // Really attach TinyMCE
eads@4 241 Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) {
eads@16 242 var ed = tiny_instance, dom = ed.dom, s = ed.selection;
eads@16 243
eads@4 244 settings = $.extend({
eads@4 245 targets: $('#'+ data.field +'-wrapper iframe'),
eads@31 246 processTargets: function(targets) {
eads@31 247 return targets.each(function() {
eads@31 248 var target = this
eads@31 249 // Decrement counter on delete
eads@31 250 $(target).bind('dnd_delete', function(e, data) {
eads@31 251 Drupal.behaviors.dndLibrary.countElements(target, $(data.node).attr('dnd_id'), true);
eads@31 252 });
defr@45 253 $('head', $(this).contents()).append('<style type="text/css">img.drop { display: none; } div.dnd-drop-wrapper {background: #efe; border: 1px #090 solid;}</style>');
franck@52 254 $('body', $(this).contents()).addClass($('body').attr('class'));
eads@31 255 return this;
eads@31 256 });
eads@31 257 },
eads@20 258 processIframeDrop: function(drop, id_selector) {
eads@20 259 var representation_id = id_selector.call(this, drop);
defr@45 260 if (!Drupal.settings.dndEditorRepresentations[representation_id]) return;
eads@24 261 var representation = Drupal.settings.dndEditorRepresentations[representation_id].body;
franck@38 262 var legend = Drupal.settings.dndEditorRepresentations[representation_id].meta.legend;
eads@20 263 var target = this, $target = $(target), $drop = $(drop), block;
eads@15 264
eads@25 265 // Update element count
eads@31 266 Drupal.behaviors.dndLibrary.countElements(target, representation_id);
eads@24 267
eads@16 268 // Search through block level parents
eads@20 269 $drop.parents().each(function() {
eads@16 270 var $this = $(this);
eads@16 271 if ($this.css('display') == 'block') {
eads@16 272 block = this;
eads@16 273 return false;
eads@16 274 }
eads@16 275 });
eads@16 276
eads@16 277 // Remove dropped item
eads@20 278 $drop.remove();
eads@16 279
eads@16 280 // Create an element to insert
franck@38 281 var insert = dom.create('div', {'class' : 'dnd-drop-wrapper', 'id' : 'dnd-inserted'}, representation);
eads@16 282
eads@16 283 // The no-parent case
eads@16 284 if ($(block).is('body')) {
franck@39 285 // Never seem to be hit ?
eads@16 286 s.setNode(insert);
eads@16 287 }
eads@16 288 else {
eads@16 289 var old_id = block.id;
eads@16 290 block.id = 'target-block';
eads@16 291 $block = $('#target-block', $target.contents());
eads@16 292
eads@16 293 // @TODO is finding the parent broken in safari??
franck@38 294 var snip = '<div class="dnd-drop-wrapper" id="dnd-inserted">' + representation + '</div>';
franck@38 295 if (legend) {
franck@38 296 snip += legend;
franck@38 297 }
franck@38 298 $block.after(snip);
eads@16 299
eads@16 300 // The active target block should be empty
eads@31 301 if ($('#target-block:dnd_empty', $target.contents()).length > 0) {
eads@17 302 $('#target-block', $target.contents()).remove();
eads@16 303 } else if (old_id) {
eads@16 304 block.id = old_id;
eads@16 305 } else {
eads@16 306 $block.removeAttr('id');
eads@16 307 }
eads@31 308 }
eads@16 309
eads@16 310 var $inserted = $('#dnd-inserted', $target.contents());
eads@16 311 var inserted = $inserted.get(0);
eads@16 312
eads@16 313 // Look behind in the DOM
eads@16 314 var previous = $inserted.prev().get(0);
eads@16 315
eads@16 316 // If the previous element is also an editor representation, we need to
eads@16 317 // put a dummy paragraph between the elements to prevent editor errors.
eads@31 318 if (previous ) {
eads@31 319 $inserted.before('<p></p>');
eads@16 320 }
eads@16 321
eads@16 322 // Look ahead in the DOM
eads@16 323 var next = $inserted.next().get(0);
eads@16 324
eads@16 325 // If the next item exists and isn't an editor representation, drop the
eads@16 326 // caret at the beginning of the element, otherwise make a new paragraph
eads@16 327 // to advance the caret to.
eads@31 328 if (next && !$(next).hasClass('dnd-drop-wrapper')) {
eads@16 329 $(next).prepend('<span id="__caret">_</span>');
eads@16 330 }
eads@31 331 else if (!$(next).hasClass('dnd-drop-wrapper')) {
eads@16 332 var after = dom.create('p', {}, '<span id="__caret">_</span>');
eads@16 333 dom.insertAfter(after, 'dnd-inserted');
eads@16 334 }
eads@31 335
eads@16 336 // Force selection to reset the caret
eads@16 337 var c = dom.get('__caret');
eads@31 338 if (c) {
eads@31 339 s.select(c);
eads@31 340 ed.execCommand('Delete', false, null);
eads@31 341 dom.remove(c);
eads@31 342 }
eads@31 343
eads@31 344 // Unset id for next drop and add special dnd attribute for counting
eads@31 345 // purposes
eads@31 346 $inserted
eads@31 347 .removeAttr('id')
eads@31 348 .attr('dnd_id', representation_id);
eads@31 349
eads@4 350 }
eads@4 351 }, settings);
eads@4 352
eads@4 353 $(settings.drop_selector).dnd(settings);
eads@2 354 }
eads@25 355
eads@25 356
eads@25 357 // Keep a counter of times a representation ID has been used
eads@31 358 Drupal.behaviors.dndLibrary.countElements = function(target, representation_id, decrement) {
eads@31 359 var counter = $(target).data('dnd_representation_counter');
eads@25 360 if (!counter) {
eads@25 361 counter = {}
eads@25 362 counter[representation_id] = 1;
eads@25 363 } else if (counter && !counter[representation_id]) {
eads@25 364 counter[representation_id] = 1;
eads@25 365 } else {
eads@31 366 counter[representation_id] = counter[representation_id] + ((decrement) ? -1 : 1);
eads@25 367 }
eads@31 368 $(target).data('dnd_representation_counter', counter);
eads@31 369 return counter[representation_id];
eads@25 370 }
defr@47 371
defr@47 372 /**
defr@47 373 * Refresh the library.
defr@47 374 */
defr@47 375 Drupal.dnd = {}
defr@47 376 Drupal.dnd.refreshLibraries = function() {
defr@47 377 var settings = Drupal.settings.dndEnabledLibraries;
defr@47 378 for (editor_id in settings) {
defr@47 379 var elem = $("#" + settings[editor_id].library_id).get(0);
defr@47 380 var $editor = $("#" + editor_id);
defr@47 381 $.getJSON(elem.library_url, function (data) {
defr@47 382 Drupal.behaviors.dndLibrary.renderLibrary.call(elem, data, $editor);
defr@47 383 });
defr@47 384 }
defr@47 385 }