annotate js/dnd-library.js @ 35:abc9d39cfbe9

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