comparison 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
comparison
equal deleted inserted replaced
15:7a5f74482ee3 16:bb68dc3ad56f
1 /**
2 * Drag and Drop Library For Drupal
3 *
4 * This builds on the DnD jQuery plugin written to provide drag and drop media
5 * handling to Rich Text Editors to consume, display, and attach behavior to
6 * a "media library" provided via JSON and implemented for Drupal running
7 * the Wysiwyg plugin.
8 */
9
10 /**
11 * Extend jQuery a bit
12 *
13 * We add a selector to look for "empty" elements (empty elements in TinyMCE
14 * often have non-breaking spaces and <br /> tags).
15 */
16 (function($) {
17 // Custom selectors
18 $.extend($.expr[":"], {
19 'empty' : function(a, i, m) {
20 var text = $(a).html();
21 text.replace(/\u00a0/g,''); // Remove &nbsp;
22 $('br', $(text)).remove(); // Remove breaks
23 return !$.trim(text);
24 }
25 });
26 }) (jQuery);
27
1 Drupal.behaviors.dndLibrary = function(context) { 28 Drupal.behaviors.dndLibrary = function(context) {
2 $('.dnd-library-wrapper:not(.dnd-processed)', context).each(function() { 29 $('.dnd-library-wrapper', context).each(function() {
3 var $this = $(this); 30 var $this = $(this);
4 31
5 // This is a bad hack to lop off '-dnd-library' from the id to get the editor name 32 // This is a bad hack to lop off '-dnd-library' from the id to get the editor name
6 var editor = this.id.slice(0, -12); 33 var editor = this.id.slice(0, -12);
7 34
8 // Attempt to attach library 35 // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg
9 //Drupal.behaviors.dndLibrary.attach_library(false, Drupal.wysiwyg.instances[editor]);
10
11 // Bind Drag and Drop plugin invocation to wywsiwygAttach event
12 $('#' + editor).bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library); 36 $('#' + editor).bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library);
13
14 // @TODO track and clear intervals to save memory at the cost of
15 // more processing?
16 $('#' + editor).bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library); 37 $('#' + editor).bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library);
17 38
18 // Ajax pager 39 // Ajax pager
19 $('.pager a', $this).click(function(e, data) { 40 $('.pager a', $this).click(function(e, data) {
20 $.getJSON(this.href, function(data) { 41 $.getJSON(this.href, function(data) {
28 $('#' + editor).trigger('wysiwygDetach', params); 49 $('#' + editor).trigger('wysiwygDetach', params);
29 $('#' + editor).trigger('wysiwygAttach', params); 50 $('#' + editor).trigger('wysiwygAttach', params);
30 }); 51 });
31 return false; 52 return false;
32 }); 53 });
33
34 $this.addClass('dnd-processed');
35 }); 54 });
36 } 55 }
37 56
57 // Dynamically compose a callback based on the editor name
38 Drupal.behaviors.dndLibrary.attach_library = function(e, data) { 58 Drupal.behaviors.dndLibrary.attach_library = function(e, data) {
39 var settings = {
40 renderRepresentation: function(target, drop, representation_id) {
41 return Drupal.settings.dndEditorRepresentations[representation_id];
42 }
43 }
44 settings = $.extend(settings, Drupal.settings.dndEnabledLibraries[data.field]);
45
46 var editor_fn = 'attach_' + data.editor; 59 var editor_fn = 'attach_' + data.editor;
47 if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) { 60 if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) {
48 window.Drupal.behaviors.dndLibrary[editor_fn](data, settings); 61 window.Drupal.behaviors.dndLibrary[editor_fn](data, Drupal.settings.dndEnabledLibraries[data.field]);
49 } 62 }
50 } 63 }
51 64
65 // Do garbage collection on detach
52 Drupal.behaviors.dndLibrary.detach_library = function(e, data) { 66 Drupal.behaviors.dndLibrary.detach_library = function(e, data) {
53 for (t in $(document).data('dnd_timers')) { 67 for (t in $(document).data('dnd_timers')) {
54 clearInterval(t); 68 clearInterval(t);
55 } 69 }
56 $(document).removeData('dnd_timers'); 70 $(document).removeData('dnd_timers');
57 } 71 }
58 72
59 73 // Basic textareas
74 Drupal.behaviors.dndLibrary.attach_none = function(data, settings) {
75 settings = $.extend({
76 targets: $('#'+ data.field),
77 procressTextAreaDrop: function(target, clicked, representation_id, e, data) {
78 var snippet = Drupal.settings.dndEditorRepresentations[representation_id];
79 $(target).replaceSelection(snippet, true);
80 }
81 }, settings);
82 $(settings.drop_selector).dnd(settings);
83 }
84
85 // Attach TinyMCE
60 Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) { 86 Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) {
61
62 var tiny_instance = tinyMCE.getInstanceById(data.field); 87 var tiny_instance = tinyMCE.getInstanceById(data.field);
63 88
64 // If the Tiny instance exists, attach directly, otherwise wait until Tiny 89 // If the Tiny instance exists, attach directly, otherwise wait until Tiny
65 // has registered a new instance. 90 // has registered a new instance.
66 if (tiny_instance) { 91 if (tiny_instance) {
74 } 99 }
75 }, 100); 100 }, 100);
76 } 101 }
77 } 102 }
78 103
104 // Really attach TinyMCE
79 Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) { 105 Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) {
106 var ed = tiny_instance, dom = ed.dom, s = ed.selection;
107
80 settings = $.extend({ 108 settings = $.extend({
81 targets: $('#'+ data.field +'-wrapper iframe'), 109 targets: $('#'+ data.field +'-wrapper iframe'),
82 insertAfter: '<p><span id="__caret">_</span></p>', 110 idSelector: function(element) {
83 111 if ($(element).is('img')) {
84 // Back out markup to render in place after parent container 112 return $.url.setUrl(element.src).param('dnd_id');
85 preprocessDrop: function(target, drop) { 113 }
86 drop.id = 'DND-TMP-' + $.data(drop); 114 return false;
87 // Do some native tiny manipulations 115 },
88 return drop; 116 interval: 100,
89 }, 117 processIframeDrop: function(target, dragged, dropped, representation_id) {
90 postprocessDrop: function(target, drop, element) { 118 var representation = Drupal.settings.dndEditorRepresentations[representation_id];
91 // Get our special span, select it, delete it, and hope the caret 119 var $target = $(target), $dropped = $(dropped), $dragged = $(dragged), block;
92 // resets correctly. 120
93 tiny_instance.selection.select(tiny_instance.dom.get('__caret')); 121 // Search through block level parents
94 tiny_instance.execCommand('Delete', false, null); 122 $dropped.parents().each(function() {
95 tiny_instance.dom.remove('__caret'); 123 var $this = $(this);
124 if ($this.css('display') == 'block') {
125 block = this;
126 return false;
127 }
128 });
129
130 // Remove dropped item
131 $dropped.remove();
132
133 // Create an element to insert
134 var insert = dom.create('p', {'class' : 'dnd-dropped-wrapper', 'id' : 'dnd-inserted'}, representation);
135
136 // The no-parent case
137 if ($(block).is('body')) {
138
139 s.setNode(insert);
140 }
141 else {
142 var old_id = block.id;
143 block.id = 'target-block';
144 $block = $('#target-block', $target.contents());
145
146 // @TODO is finding the parent broken in safari??
147 $block.after('<p class="dnd-dropped-wrapper" id="dnd-inserted">' + representation + '</p>');
148
149 // The active target block should be empty
150 if ($('#target-block:empty', $target.contents()).length > 0) {
151 var c = dom.get('target-block');
152 s.select(dom.get('target-block'));
153 ed.execCommand('Delete', false, null);
154 dom.remove(c);
155 } else if (old_id) {
156 block.id = old_id;
157 } else {
158 $block.removeAttr('id');
159 }
160 }
161
162 var $inserted = $('#dnd-inserted', $target.contents());
163 var inserted = $inserted.get(0);
164
165 // Look behind in the DOM
166 var previous = $inserted.prev().get(0);
167
168 // If the previous element is also an editor representation, we need to
169 // put a dummy paragraph between the elements to prevent editor errors.
170 if (previous && $(previous).hasClass('dnd-dropped-wrapper')) {
171 $inserted.before('<p><span id="__spacer">_</span></p>');
172 c = dom.get('__spacer');
173 s.select(c);
174 ed.execCommand('Delete', false, null);
175 dom.remove(c);
176 }
177
178 // Look ahead in the DOM
179 var next = $inserted.next().get(0);
180
181 // If the next item exists and isn't an editor representation, drop the
182 // caret at the beginning of the element, otherwise make a new paragraph
183 // to advance the caret to.
184 if (next && !$(next).hasClass('dnd-dropped-wrapper')) {
185 $(next).prepend('<span id="__caret">_</span>');
186 }
187 else {
188 var after = dom.create('p', {}, '<span id="__caret">_</span>');
189 dom.insertAfter(after, 'dnd-inserted');
190 }
191
192 // Clear the ID for the next drop
193 $inserted.removeAttr('id');
194
195 // Force selection to reset the caret
196 var c = dom.get('__caret');
197 s.select(c);
198 ed.execCommand('Delete', false, null);
199 dom.remove(c);
96 200
97 // Add some classes to the library items 201 // Add some classes to the library items
98 $(element).addClass('dnd-inserted'); 202 $(dragged).addClass('dnd-inserted');
99 $(element).parents('.editor-item').addClass('dnd-child-inserted'); 203 drag_parents = $(dragged).parents('.editor-item');
204 drag_parents.addClass('dnd-child-inserted');
100 } 205 }
101 }, settings); 206 }, settings);
102 207
103 $(settings.drop_selector).dnd(settings); 208 $(settings.drop_selector).dnd(settings);
104 } 209 }