Mercurial > defr > drupal > scald > dnd
diff js/dnd-library.js @ 16:bb68dc3ad56f
Major refactor to provide better TinyMCE support and less configuration options, added new jquery dependency, etc.
author | David Eads <eads@chicagotech.org> |
---|---|
date | Tue, 03 Mar 2009 16:57:39 -0600 |
parents | 7a5f74482ee3 |
children | 1a77f87927dd |
line wrap: on
line diff
--- a/js/dnd-library.js Mon Mar 02 23:22:37 2009 -0600 +++ b/js/dnd-library.js Tue Mar 03 16:57:39 2009 -0600 @@ -1,18 +1,39 @@ +/** + * Drag and Drop Library For Drupal + * + * This builds on the DnD jQuery plugin written to provide drag and drop media + * handling to Rich Text Editors to consume, display, and attach behavior to + * a "media library" provided via JSON and implemented for Drupal running + * the Wysiwyg plugin. + */ + +/** + * Extend jQuery a bit + * + * We add a selector to look for "empty" elements (empty elements in TinyMCE + * often have non-breaking spaces and <br /> tags). + */ +(function($) { + // Custom selectors + $.extend($.expr[":"], { + 'empty' : function(a, i, m) { + var text = $(a).html(); + text.replace(/\u00a0/g,''); // Remove + $('br', $(text)).remove(); // Remove breaks + return !$.trim(text); + } + }); +}) (jQuery); + Drupal.behaviors.dndLibrary = function(context) { - $('.dnd-library-wrapper:not(.dnd-processed)', context).each(function() { + $('.dnd-library-wrapper', context).each(function() { var $this = $(this); // This is a bad hack to lop off '-dnd-library' from the id to get the editor name var editor = this.id.slice(0, -12); - // Attempt to attach library - //Drupal.behaviors.dndLibrary.attach_library(false, Drupal.wysiwyg.instances[editor]); - - // Bind Drag and Drop plugin invocation to wywsiwygAttach event + // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg $('#' + editor).bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library); - - // @TODO track and clear intervals to save memory at the cost of - // more processing? $('#' + editor).bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library); // Ajax pager @@ -30,25 +51,18 @@ }); return false; }); - - $this.addClass('dnd-processed'); }); } +// Dynamically compose a callback based on the editor name Drupal.behaviors.dndLibrary.attach_library = function(e, data) { - var settings = { - renderRepresentation: function(target, drop, representation_id) { - return Drupal.settings.dndEditorRepresentations[representation_id]; - } - } - settings = $.extend(settings, Drupal.settings.dndEnabledLibraries[data.field]); - var editor_fn = 'attach_' + data.editor; if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) { - window.Drupal.behaviors.dndLibrary[editor_fn](data, settings); + window.Drupal.behaviors.dndLibrary[editor_fn](data, Drupal.settings.dndEnabledLibraries[data.field]); } } +// Do garbage collection on detach Drupal.behaviors.dndLibrary.detach_library = function(e, data) { for (t in $(document).data('dnd_timers')) { clearInterval(t); @@ -56,9 +70,20 @@ $(document).removeData('dnd_timers'); } +// Basic textareas +Drupal.behaviors.dndLibrary.attach_none = function(data, settings) { + settings = $.extend({ + targets: $('#'+ data.field), + procressTextAreaDrop: function(target, clicked, representation_id, e, data) { + var snippet = Drupal.settings.dndEditorRepresentations[representation_id]; + $(target).replaceSelection(snippet, true); + } + }, settings); + $(settings.drop_selector).dnd(settings); +} +// Attach TinyMCE Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) { - var tiny_instance = tinyMCE.getInstanceById(data.field); // If the Tiny instance exists, attach directly, otherwise wait until Tiny @@ -76,27 +101,107 @@ } } +// Really attach TinyMCE Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) { + var ed = tiny_instance, dom = ed.dom, s = ed.selection; + settings = $.extend({ targets: $('#'+ data.field +'-wrapper iframe'), - insertAfter: '<p><span id="__caret">_</span></p>', + idSelector: function(element) { + if ($(element).is('img')) { + return $.url.setUrl(element.src).param('dnd_id'); + } + return false; + }, + interval: 100, + processIframeDrop: function(target, dragged, dropped, representation_id) { + var representation = Drupal.settings.dndEditorRepresentations[representation_id]; + var $target = $(target), $dropped = $(dropped), $dragged = $(dragged), block; - // Back out markup to render in place after parent container - preprocessDrop: function(target, drop) { - drop.id = 'DND-TMP-' + $.data(drop); - // Do some native tiny manipulations - return drop; - }, - postprocessDrop: function(target, drop, element) { - // Get our special span, select it, delete it, and hope the caret - // resets correctly. - tiny_instance.selection.select(tiny_instance.dom.get('__caret')); - tiny_instance.execCommand('Delete', false, null); - tiny_instance.dom.remove('__caret'); + // Search through block level parents + $dropped.parents().each(function() { + var $this = $(this); + if ($this.css('display') == 'block') { + block = this; + return false; + } + }); + + // Remove dropped item + $dropped.remove(); + + // Create an element to insert + var insert = dom.create('p', {'class' : 'dnd-dropped-wrapper', 'id' : 'dnd-inserted'}, representation); + + // The no-parent case + if ($(block).is('body')) { + + s.setNode(insert); + } + else { + var old_id = block.id; + block.id = 'target-block'; + $block = $('#target-block', $target.contents()); + + // @TODO is finding the parent broken in safari?? + $block.after('<p class="dnd-dropped-wrapper" id="dnd-inserted">' + representation + '</p>'); + + // The active target block should be empty + if ($('#target-block:empty', $target.contents()).length > 0) { + var c = dom.get('target-block'); + s.select(dom.get('target-block')); + ed.execCommand('Delete', false, null); + dom.remove(c); + } else if (old_id) { + block.id = old_id; + } else { + $block.removeAttr('id'); + } + } + + var $inserted = $('#dnd-inserted', $target.contents()); + var inserted = $inserted.get(0); + + // Look behind in the DOM + var previous = $inserted.prev().get(0); + + // If the previous element is also an editor representation, we need to + // put a dummy paragraph between the elements to prevent editor errors. + if (previous && $(previous).hasClass('dnd-dropped-wrapper')) { + $inserted.before('<p><span id="__spacer">_</span></p>'); + c = dom.get('__spacer'); + s.select(c); + ed.execCommand('Delete', false, null); + dom.remove(c); + } + + // Look ahead in the DOM + var next = $inserted.next().get(0); + + // If the next item exists and isn't an editor representation, drop the + // caret at the beginning of the element, otherwise make a new paragraph + // to advance the caret to. + if (next && !$(next).hasClass('dnd-dropped-wrapper')) { + $(next).prepend('<span id="__caret">_</span>'); + } + else { + var after = dom.create('p', {}, '<span id="__caret">_</span>'); + dom.insertAfter(after, 'dnd-inserted'); + } + + // Clear the ID for the next drop + $inserted.removeAttr('id'); + + // Force selection to reset the caret + var c = dom.get('__caret'); + s.select(c); + ed.execCommand('Delete', false, null); + dom.remove(c); // Add some classes to the library items - $(element).addClass('dnd-inserted'); - $(element).parents('.editor-item').addClass('dnd-child-inserted'); + $(dragged).addClass('dnd-inserted'); + drag_parents = $(dragged).parents('.editor-item'); + drag_parents.addClass('dnd-child-inserted'); } }, settings);