annotate js/dnd-library.js @ 27:9a92410be362

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