Mercurial > defr > drupal > scald > dnd
view js/dnd-library.js @ 47:cbfe386cb51b
Add a function to refresh opened libraries.
The source URL of each libraries is now tracked, which allows for auto
refreshing the libraries based on various events. The obvious use
case is to refresh the library when an atom has been added to Scald,
for example via a Popups dialog.
author | Franck Deroche <defr@ows.fr> |
---|---|
date | Mon, 15 Feb 2010 14:08:04 +0000 |
parents | 2ba96288fbea |
children | 5cf2b262b9e4 |
line wrap: on
line source
/** * 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). An exception is required * to make this work in IE. */ (function($) { // Custom selectors $.extend($.expr[":"], { 'dnd_empty' : function(a, i, m) { return !$(a).filter(function(i) { return !$(this).is('br'); }).length && !$.trim(a.textContent || a.innerText||$(a).text() || ""); } }); }) (jQuery); /** * Initialize and load drag and drop library and pass off rendering and * behavior attachment. */ Drupal.behaviors.dndLibrary = function(context) { $('.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)); // Set up some initial settings for BeautyTips var settings = Drupal.settings.dndEnabledLibraries[$editor.get(0).id] = $.extend({ 'btSettings' : { 'trigger': ['click'], 'width': 480, 'spikeLength': 7, 'spikeGirth': 9, 'corner-radius' : 3, 'strokeWidth': 1, 'fill': '#ffd', 'strokeStyle': '#555', 'closeWhenOthersOpen': true }, 'libraryHoverIntentSettings' : { 'interval': 500, 'timeout' : 0, 'over': function() { var $this = $(this); this.btOn(); // Remove the preview once dragging of any image has commenced $('img', $this).bind('drag', function(e) { $this.btOff(); }); $('img', $this).bind('click', function(e) { $this.btOff(); }); }, 'out': function() { this.btOff(); } } }, Drupal.settings.dndEnabledLibraries[$editor.get(0).id]); // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg $editor.bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library); $editor.bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library); // Set up empty objects to keep track of things Drupal.settings.dndEditorRepresentations = {}; Drupal.settings.dndLibraryPreviews = {}; // Clean up the url if needed (this happen in case drupal_add_js is called // multiple time for the page) if (settings.url instanceof Object) { settings.url = settings.url[0]; } // Initialize the library var wrapper = $this.get(0); wrapper.library_url = Drupal.settings.basePath + settings.url; $.getJSON(Drupal.settings.basePath + settings.url, function(data) { Drupal.behaviors.dndLibrary.renderLibrary.call(wrapper, data, $editor); }); }); } Drupal.behaviors.dndLibrary.renderLibrary = function(data, editor) { $this = $(this); $this.html(data.library); var settings = Drupal.settings.dndEnabledLibraries[editor.get(0).id]; var params = Drupal.wysiwyg.instances[editor.get(0).id]; editor.trigger('wysiwygDetach', params); editor.trigger('wysiwygAttach', params); for (editor_id in data.editor_representations) { Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id]; } for (preview_id in data.library_previews) { Drupal.settings.dndLibraryPreviews[preview_id] = data.library_previews[preview_id]; } // Add preview behavior to editor items (thanks, BeautyTips!) $('.editor-item', $this).each(function () { $(this).bt(Drupal.settings.dndLibraryPreviews[this.id], settings.btSettings); //$(this).hoverIntent(settings.libraryHoverIntentSettings); }); // Preload images in editor representations var cached = $.data($(editor), 'dnd_preload') || {}; for (editor_id in Drupal.settings.dndEditorRepresentations) { if (!cached[editor_id]) { $representation = $(Drupal.settings.dndEditorRepresentations[editor_id].body); if ($representation.is('img') && $representation.get(0).src) { $representation.attr('src', $representation.get(0).src); } else { $('img', $representation).each(function() { $(this).attr('src', this.src); }); } } } $.data($(editor), 'dnd_preload', cached); $('.pager a', $this).click(function() { // At page switching, close all opened BeautyTips. $('.editor-item.bt-active').btOff(); $this.get(0).library_url = this.href; $.getJSON(this.href, function(data) { Drupal.behaviors.dndLibrary.renderLibrary.call($this.get(0), data, $(editor)); }); return false; }); $('.view-filters input[type=submit]', $this).click(function() { var submit = $(this); $('.view-filters form', $this).ajaxSubmit({ 'url' : Drupal.settings.basePath + settings.url, 'dataType' : 'json', 'success' : function(data) { var target = submit.parents('div.dnd-library-wrapper').get(0); target.library_url = this.url; Drupal.behaviors.dndLibrary.renderLibrary.call(target, data, $(editor)); } }); return false; }); $('.view-filters input[type=reset]', $this).click(function() { var reset = $(this); $('.view-filters form', $this).ajaxSubmit({ 'url' : Drupal.settings.basePath + settings.url, 'dataType' : 'json', 'success' : function(data) { var target = reset.parents('div.dnd-library-wrapper').get(0); target.library_url = Drupal.dndEnabledLibraries[editor].url; Drupal.behaviors.dndLibrary.renderLibrary.call(target, data, $(editor)); }, 'beforeSubmit': function (data, form, options) { // Can't use data = [], otherwise we're creating a new array // instead of modifying the existing one. data.splice(0, data.length); } }); return false; }); Drupal.attachBehaviors($this); } // Dynamically compose a callback based on the editor name Drupal.behaviors.dndLibrary.attach_library = function(e, data) { var settings = $.extend({idSelector: Drupal.behaviors.dndLibrary.idSelector}, 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); } } // Do garbage collection on detach Drupal.behaviors.dndLibrary.detach_library = function(e, data) {} // Basic textareas Drupal.behaviors.dndLibrary.attach_none = function(data, settings) { settings = $.extend({ targets: $('#'+ data.field), processTextAreaClick: function(clicked, representation_id, e, data) { var target = this, $target = $(target); // Update element count Drupal.behaviors.dndLibrary.countElements.call(target, representation_id); var rep = Drupal.settings.dndEditorRepresentations[representation_id]; var snippet = '<div class="dnd-drop-wrapper">' + rep.body + '</div>'; if (rep.meta.legend) { snippet += rep.meta.legend; } $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 // has registered a new instance. if (tiny_instance) { Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance); } else { var t = setInterval(function() { var tiny_instance = tinyMCE.getInstanceById(data.field); if (tiny_instance) { Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance); $('#'+ data.field +'-wrapper').trigger('dnd_attach_library'); clearInterval(t); } }, 100); } } Drupal.behaviors.dndLibrary.idSelector = function(element) { if ($(element).is('img')) { return $.url.setUrl(element.src).param('dnd_id'); } return false; } // 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'), processTargets: function(targets) { return targets.each(function() { var target = this // Decrement counter on delete $(target).bind('dnd_delete', function(e, data) { Drupal.behaviors.dndLibrary.countElements(target, $(data.node).attr('dnd_id'), true); }); $('head', $(this).contents()).append('<style type="text/css">img.drop { display: none; } div.dnd-drop-wrapper {background: #efe; border: 1px #090 solid;}</style>'); return this; }); }, processIframeDrop: function(drop, id_selector) { var representation_id = id_selector.call(this, drop); if (!Drupal.settings.dndEditorRepresentations[representation_id]) return; var representation = Drupal.settings.dndEditorRepresentations[representation_id].body; var legend = Drupal.settings.dndEditorRepresentations[representation_id].meta.legend; var target = this, $target = $(target), $drop = $(drop), block; // Update element count Drupal.behaviors.dndLibrary.countElements(target, representation_id); // Search through block level parents $drop.parents().each(function() { var $this = $(this); if ($this.css('display') == 'block') { block = this; return false; } }); // Remove dropped item $drop.remove(); // Create an element to insert var insert = dom.create('div', {'class' : 'dnd-drop-wrapper', 'id' : 'dnd-inserted'}, representation); // The no-parent case if ($(block).is('body')) { // Never seem to be hit ? 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?? var snip = '<div class="dnd-drop-wrapper" id="dnd-inserted">' + representation + '</div>'; if (legend) { snip += legend; } $block.after(snip); // The active target block should be empty if ($('#target-block:dnd_empty', $target.contents()).length > 0) { $('#target-block', $target.contents()).remove(); } 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 ) { $inserted.before('<p></p>'); } // 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-drop-wrapper')) { $(next).prepend('<span id="__caret">_</span>'); } else if (!$(next).hasClass('dnd-drop-wrapper')) { var after = dom.create('p', {}, '<span id="__caret">_</span>'); dom.insertAfter(after, 'dnd-inserted'); } // Force selection to reset the caret var c = dom.get('__caret'); if (c) { s.select(c); ed.execCommand('Delete', false, null); dom.remove(c); } // Unset id for next drop and add special dnd attribute for counting // purposes $inserted .removeAttr('id') .attr('dnd_id', representation_id); } }, settings); $(settings.drop_selector).dnd(settings); } // Keep a counter of times a representation ID has been used Drupal.behaviors.dndLibrary.countElements = function(target, representation_id, decrement) { var counter = $(target).data('dnd_representation_counter'); if (!counter) { counter = {} counter[representation_id] = 1; } else if (counter && !counter[representation_id]) { counter[representation_id] = 1; } else { counter[representation_id] = counter[representation_id] + ((decrement) ? -1 : 1); } $(target).data('dnd_representation_counter', counter); return counter[representation_id]; } /** * Refresh the library. */ Drupal.dnd = {} Drupal.dnd.refreshLibraries = function() { var settings = Drupal.settings.dndEnabledLibraries; for (editor_id in settings) { var elem = $("#" + settings[editor_id].library_id).get(0); var $editor = $("#" + editor_id); $.getJSON(elem.library_url, function (data) { Drupal.behaviors.dndLibrary.renderLibrary.call(elem, data, $editor); }); } }