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@17: * often have non-breaking spaces and
tags). An exception is required eads@17: * to make this work in IE. eads@16: */ eads@16: (function($) { eads@16: // Custom selectors eads@16: $.extend($.expr[":"], { eads@31: 'dnd_empty' : function(a, i, m) { eads@17: return !$(a).filter(function(i) { eads@17: return !$(this).is('br'); eads@30: }).length && !$.trim(a.textContent || a.innerText||$(a).text() || ""); eads@16: } eads@16: }); eads@16: }) (jQuery); eads@16: eads@30: /** eads@30: * Initialize and load drag and drop library and pass off rendering and eads@30: * behavior attachment. eads@30: */ 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@17: var $editor = $('#' + this.id.slice(0, -12)); eads@17: eads@30: // Set up some initial settings for BeautyTips eads@30: var settings = Drupal.settings.dndEnabledLibraries[$editor.get(0).id] = $.extend({ eads@31: 'btSettings' : { franck@35: 'trigger': ['click'], eads@21: 'width': 375, eads@20: 'spikeLength': 7, eads@20: 'spikeGirth': 9, eads@18: 'corner-radius' : 3, eads@20: 'strokeWidth': 1, eads@20: 'fill': '#ffd', franck@35: 'strokeStyle': '#555', franck@35: 'closeWhenOthersOpen': true eads@31: }, eads@31: 'libraryHoverIntentSettings' : { eads@31: 'interval': 500, eads@31: 'timeout' : 0, eads@31: 'over': function() { eads@31: var $this = $(this); eads@31: this.btOn(); eads@31: // Remove the preview once dragging of any image has commenced eads@31: $('img', $this).bind('drag', function(e) { eads@31: $this.btOff(); eads@31: }); eads@31: $('img', $this).bind('click', function(e) { eads@31: $this.btOff(); eads@31: }); eads@31: }, eads@31: 'out': function() { this.btOff(); } eads@31: } eads@30: }, Drupal.settings.dndEnabledLibraries[$editor.get(0).id]); eads@24: eads@24: // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg eads@24: $editor.bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library); eads@24: $editor.bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library); eads@24: eads@30: // Set up empty objects to keep track of things eads@30: Drupal.settings.dndEditorRepresentations = {}; eads@30: Drupal.settings.dndLibraryPreviews = {}; eads@30: eads@31: // Initialize the library eads@30: $.getJSON(Drupal.settings.basePath + settings.url, function(data) { eads@30: Drupal.behaviors.dndLibrary.renderLibrary.call($this.get(0), data, $editor); eads@17: }); eads@4: eads@4: }); eads@4: } eads@2: eads@30: Drupal.behaviors.dndLibrary.renderLibrary = function(data, editor) { eads@17: $this = $(this); eads@17: eads@30: $this.html(data.library); eads@17: eads@30: var settings = Drupal.settings.dndEnabledLibraries[editor.get(0).id]; eads@17: var params = Drupal.wysiwyg.instances[editor.get(0).id]; eads@30: eads@17: editor.trigger('wysiwygDetach', params); eads@17: editor.trigger('wysiwygAttach', params); eads@17: eads@17: for (editor_id in data.editor_representations) { eads@17: Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id]; eads@17: } eads@17: for (preview_id in data.library_previews) { eads@17: Drupal.settings.dndLibraryPreviews[preview_id] = data.library_previews[preview_id]; eads@17: } eads@19: eads@30: // Add preview behavior to editor items (thanks, BeautyTips!) eads@30: $('.editor-item', $this).each(function () { eads@31: $(this).bt(Drupal.settings.dndLibraryPreviews[this.id], settings.btSettings); eads@31: $(this).hoverIntent(settings.libraryHoverIntentSettings); eads@30: }); eads@30: eads@30: // Preload images in editor representations eads@30: var cached = $.data($(editor), 'dnd_preload') || {}; eads@31: for (editor_id in Drupal.settings.dndEditorRepresentations) { eads@30: if (!cached[editor_id]) { eads@30: $representation = $(Drupal.settings.dndEditorRepresentations[editor_id].body); eads@30: if ($representation.is('img') && $representation.get(0).src) { eads@30: $representation.attr('src', $representation.get(0).src); eads@30: } else { eads@30: $('img', $representation).each(function() { eads@31: $(this).attr('src', this.src); eads@30: }); eads@30: } eads@30: } eads@30: } eads@31: $.data($(editor), 'dnd_preload', cached); eads@30: eads@31: $('.pager a', $this).click(function() { eads@31: $.getJSON(this.href, function(data) { eads@31: Drupal.behaviors.dndLibrary.renderLibrary.call($this.get(0), data, $(editor)); eads@31: }); eads@31: return false; eads@31: }); eads@31: $('.view-filters input[type=submit]', $this).click(function() { franck@32: $('.view-filters form', $this).ajaxSubmit({ eads@31: 'url' : Drupal.settings.basePath + settings.url, eads@31: 'dataType' : 'json', eads@31: 'success' : function(data) { eads@31: Drupal.behaviors.dndLibrary.renderLibrary.call($this.get(0), data, $(editor)); eads@31: } eads@31: }); eads@31: return false; eads@31: }); eads@17: } eads@17: eads@16: // Dynamically compose a callback based on the editor name eads@4: Drupal.behaviors.dndLibrary.attach_library = function(e, data) { eads@17: var settings = $.extend({idSelector: Drupal.behaviors.dndLibrary.idSelector}, Drupal.settings.dndEnabledLibraries[data.field]); eads@4: var editor_fn = 'attach_' + data.editor; eads@4: if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) { eads@17: window.Drupal.behaviors.dndLibrary[editor_fn](data, settings); eads@2: } eads@2: } eads@2: eads@16: // Do garbage collection on detach eads@31: Drupal.behaviors.dndLibrary.detach_library = function(e, data) {} 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@25: processTextAreaClick: function(clicked, representation_id, e, data) { eads@25: var target = this, $target = $(target); eads@25: eads@25: // Update element count eads@25: Drupal.behaviors.dndLibrary.countElements.call(target, representation_id); eads@25: eads@31: var snippet = '

' + Drupal.settings.dndEditorRepresentations[representation_id].body + '

'; eads@25: $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@24: $('#'+ data.field +'-wrapper').trigger('dnd_attach_library'); eads@4: clearInterval(t); eads@2: } eads@4: }, 100); eads@4: } eads@4: } eads@2: eads@17: Drupal.behaviors.dndLibrary.idSelector = function(element) { eads@17: if ($(element).is('img')) { eads@17: return $.url.setUrl(element.src).param('dnd_id'); eads@17: } eads@17: return false; eads@17: } eads@17: 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@31: processTargets: function(targets) { eads@31: return targets.each(function() { eads@31: var target = this eads@31: // Decrement counter on delete eads@31: $(target).bind('dnd_delete', function(e, data) { eads@31: Drupal.behaviors.dndLibrary.countElements(target, $(data.node).attr('dnd_id'), true); eads@31: }); eads@31: $('head', $(this).contents()).append(''); eads@31: return this; eads@31: }); eads@31: }, eads@20: processIframeDrop: function(drop, id_selector) { eads@20: var representation_id = id_selector.call(this, drop); eads@24: var representation = Drupal.settings.dndEditorRepresentations[representation_id].body; eads@20: var target = this, $target = $(target), $drop = $(drop), block; eads@15: eads@25: // Update element count eads@31: Drupal.behaviors.dndLibrary.countElements(target, representation_id); eads@24: eads@16: // Search through block level parents eads@20: $drop.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@20: $drop.remove(); eads@16: eads@16: // Create an element to insert eads@31: var insert = dom.create('p', {'class' : 'dnd-drop-wrapper', 'id' : 'dnd-inserted'}, representation); eads@16: eads@16: // The no-parent case eads@16: if ($(block).is('body')) { 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@31: $block.after('

' + representation + '

'); eads@16: eads@16: // The active target block should be empty eads@31: if ($('#target-block:dnd_empty', $target.contents()).length > 0) { eads@17: $('#target-block', $target.contents()).remove(); eads@16: } else if (old_id) { eads@16: block.id = old_id; eads@16: } else { eads@16: $block.removeAttr('id'); eads@16: } eads@31: } 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@31: if (previous ) { eads@31: $inserted.before('

'); 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@31: if (next && !$(next).hasClass('dnd-drop-wrapper')) { eads@16: $(next).prepend('_'); eads@16: } eads@31: else if (!$(next).hasClass('dnd-drop-wrapper')) { eads@16: var after = dom.create('p', {}, '_'); eads@16: dom.insertAfter(after, 'dnd-inserted'); eads@16: } eads@31: eads@16: // Force selection to reset the caret eads@16: var c = dom.get('__caret'); eads@31: if (c) { eads@31: s.select(c); eads@31: ed.execCommand('Delete', false, null); eads@31: dom.remove(c); eads@31: } eads@31: eads@31: // Unset id for next drop and add special dnd attribute for counting eads@31: // purposes eads@31: $inserted eads@31: .removeAttr('id') eads@31: .attr('dnd_id', representation_id); eads@31: eads@4: } eads@4: }, settings); eads@4: eads@4: $(settings.drop_selector).dnd(settings); eads@2: } eads@25: eads@25: eads@25: // Keep a counter of times a representation ID has been used eads@31: Drupal.behaviors.dndLibrary.countElements = function(target, representation_id, decrement) { eads@31: var counter = $(target).data('dnd_representation_counter'); eads@25: if (!counter) { eads@25: counter = {} eads@25: counter[representation_id] = 1; eads@25: } else if (counter && !counter[representation_id]) { eads@25: counter[representation_id] = 1; eads@25: } else { eads@31: counter[representation_id] = counter[representation_id] + ((decrement) ? -1 : 1); eads@25: } eads@31: $(target).data('dnd_representation_counter', counter); eads@31: return counter[representation_id]; eads@25: }