annotate js/dnd-library.js @ 47:cbfe386cb51b

Add a function to refresh opened libraries. The source URL of each libraries is now tracked, which allows for auto refreshing the libraries based on various events. The obvious use case is to refresh the library when an atom has been added to Scald, for example via a Popups dialog.
author Franck Deroche <defr@ows.fr>
date Mon, 15 Feb 2010 14:08:04 +0000
parents 2ba96288fbea
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 }