annotate js/dnd-library.js @ 49:af6627970bc4

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