eads@16: /**
eads@16: * Drag and Drop Library For Drupal
eads@16: *
eads@16: * This builds on the DnD jQuery plugin written to provide drag and drop media
eads@16: * handling to Rich Text Editors to consume, display, and attach behavior to
eads@16: * a "media library" provided via JSON and implemented for Drupal running
eads@16: * the Wysiwyg plugin.
eads@16: */
eads@16:
eads@16: /**
eads@16: * Extend jQuery a bit
eads@16: *
eads@16: * We add a selector to look for "empty" elements (empty elements in TinyMCE
eads@16: * often have non-breaking spaces and
tags).
eads@16: */
eads@16: (function($) {
eads@16: // Custom selectors
eads@16: $.extend($.expr[":"], {
eads@16: 'empty' : function(a, i, m) {
eads@16: var text = $(a).html();
eads@16: text.replace(/\u00a0/g,''); // Remove
eads@16: $('br', $(text)).remove(); // Remove breaks
eads@16: return !$.trim(text);
eads@16: }
eads@16: });
eads@16: }) (jQuery);
eads@16:
eads@2: Drupal.behaviors.dndLibrary = function(context) {
eads@16: $('.dnd-library-wrapper', context).each(function() {
eads@4: var $this = $(this);
eads@2:
eads@4: // This is a bad hack to lop off '-dnd-library' from the id to get the editor name
eads@4: var editor = this.id.slice(0, -12);
eads@2:
eads@16: // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg
eads@4: $('#' + editor).bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library);
eads@4: $('#' + editor).bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library);
eads@4:
eads@4: // Ajax pager
eads@4: $('.pager a', $this).click(function(e, data) {
eads@2: $.getJSON(this.href, function(data) {
eads@4: $('.header', $this).html(data.header);
eads@4: $('.library', $this).html(data.library);
eads@4: //$('.footer', $this).html(data.footer);
eads@4: for (editor_id in data.editor_representations) {
eads@4: Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id];
eads@2: }
eads@4: var params = Drupal.wysiwyg.instances[editor];
eads@15: $('#' + editor).trigger('wysiwygDetach', params);
eads@4: $('#' + editor).trigger('wysiwygAttach', params);
eads@2: });
eads@2: return false;
eads@2: });
eads@4: });
eads@4: }
eads@2:
eads@16: // Dynamically compose a callback based on the editor name
eads@4: Drupal.behaviors.dndLibrary.attach_library = function(e, data) {
eads@4: var editor_fn = 'attach_' + data.editor;
eads@4: if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) {
eads@16: window.Drupal.behaviors.dndLibrary[editor_fn](data, Drupal.settings.dndEnabledLibraries[data.field]);
eads@2: }
eads@2: }
eads@2:
eads@16: // Do garbage collection on detach
eads@4: Drupal.behaviors.dndLibrary.detach_library = function(e, data) {
eads@15: for (t in $(document).data('dnd_timers')) {
eads@15: clearInterval(t);
eads@15: }
eads@15: $(document).removeData('dnd_timers');
eads@4: }
eads@4:
eads@16: // Basic textareas
eads@16: Drupal.behaviors.dndLibrary.attach_none = function(data, settings) {
eads@16: settings = $.extend({
eads@16: targets: $('#'+ data.field),
eads@16: procressTextAreaDrop: function(target, clicked, representation_id, e, data) {
eads@16: var snippet = Drupal.settings.dndEditorRepresentations[representation_id];
eads@16: $(target).replaceSelection(snippet, true);
eads@16: }
eads@16: }, settings);
eads@16: $(settings.drop_selector).dnd(settings);
eads@16: }
eads@4:
eads@16: // Attach TinyMCE
eads@2: Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) {
eads@4: var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4:
eads@4: // If the Tiny instance exists, attach directly, otherwise wait until Tiny
eads@4: // has registered a new instance.
eads@4: if (tiny_instance) {
eads@4: Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4: } else {
eads@4: var t = setInterval(function() {
eads@4: var tiny_instance = tinyMCE.getInstanceById(data.field);
eads@4: if (tiny_instance) {
eads@4: Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance);
eads@4: clearInterval(t);
eads@2: }
eads@4: }, 100);
eads@4: }
eads@4: }
eads@2:
eads@16: // Really attach TinyMCE
eads@4: Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) {
eads@16: var ed = tiny_instance, dom = ed.dom, s = ed.selection;
eads@16:
eads@4: settings = $.extend({
eads@4: targets: $('#'+ data.field +'-wrapper iframe'),
eads@16: idSelector: function(element) {
eads@16: if ($(element).is('img')) {
eads@16: return $.url.setUrl(element.src).param('dnd_id');
eads@16: }
eads@16: return false;
eads@16: },
eads@16: interval: 100,
eads@16: processIframeDrop: function(target, dragged, dropped, representation_id) {
eads@16: var representation = Drupal.settings.dndEditorRepresentations[representation_id];
eads@16: var $target = $(target), $dropped = $(dropped), $dragged = $(dragged), block;
eads@15:
eads@16: // Search through block level parents
eads@16: $dropped.parents().each(function() {
eads@16: var $this = $(this);
eads@16: if ($this.css('display') == 'block') {
eads@16: block = this;
eads@16: return false;
eads@16: }
eads@16: });
eads@16:
eads@16: // Remove dropped item
eads@16: $dropped.remove();
eads@16:
eads@16: // Create an element to insert
eads@16: var insert = dom.create('p', {'class' : 'dnd-dropped-wrapper', 'id' : 'dnd-inserted'}, representation);
eads@16:
eads@16: // The no-parent case
eads@16: if ($(block).is('body')) {
eads@16:
eads@16: s.setNode(insert);
eads@16: }
eads@16: else {
eads@16: var old_id = block.id;
eads@16: block.id = 'target-block';
eads@16: $block = $('#target-block', $target.contents());
eads@16:
eads@16: // @TODO is finding the parent broken in safari??
eads@16: $block.after('
' + representation + '
'); eads@16: eads@16: // The active target block should be empty eads@16: if ($('#target-block:empty', $target.contents()).length > 0) { eads@16: var c = dom.get('target-block'); eads@16: s.select(dom.get('target-block')); eads@16: ed.execCommand('Delete', false, null); eads@16: dom.remove(c); eads@16: } else if (old_id) { eads@16: block.id = old_id; eads@16: } else { eads@16: $block.removeAttr('id'); eads@16: } eads@16: } eads@16: eads@16: var $inserted = $('#dnd-inserted', $target.contents()); eads@16: var inserted = $inserted.get(0); eads@16: eads@16: // Look behind in the DOM eads@16: var previous = $inserted.prev().get(0); eads@16: eads@16: // If the previous element is also an editor representation, we need to eads@16: // put a dummy paragraph between the elements to prevent editor errors. eads@16: if (previous && $(previous).hasClass('dnd-dropped-wrapper')) { eads@16: $inserted.before('_
'); eads@16: c = dom.get('__spacer'); eads@16: s.select(c); eads@16: ed.execCommand('Delete', false, null); eads@16: dom.remove(c); eads@16: } eads@16: eads@16: // Look ahead in the DOM eads@16: var next = $inserted.next().get(0); eads@16: eads@16: // If the next item exists and isn't an editor representation, drop the eads@16: // caret at the beginning of the element, otherwise make a new paragraph eads@16: // to advance the caret to. eads@16: if (next && !$(next).hasClass('dnd-dropped-wrapper')) { eads@16: $(next).prepend('_'); eads@16: } eads@16: else { eads@16: var after = dom.create('p', {}, '_'); eads@16: dom.insertAfter(after, 'dnd-inserted'); eads@16: } eads@16: eads@16: // Clear the ID for the next drop eads@16: $inserted.removeAttr('id'); eads@16: eads@16: // Force selection to reset the caret eads@16: var c = dom.get('__caret'); eads@16: s.select(c); eads@16: ed.execCommand('Delete', false, null); eads@16: dom.remove(c); eads@4: eads@4: // Add some classes to the library items eads@16: $(dragged).addClass('dnd-inserted'); eads@16: drag_parents = $(dragged).parents('.editor-item'); eads@16: drag_parents.addClass('dnd-child-inserted'); eads@4: } eads@4: }, settings); eads@4: eads@4: $(settings.drop_selector).dnd(settings); eads@2: }