eads@16
|
1 /** |
eads@16
|
2 * Drag and Drop Library For Drupal |
eads@16
|
3 * |
eads@16
|
4 * This builds on the DnD jQuery plugin written to provide drag and drop media |
eads@16
|
5 * handling to Rich Text Editors to consume, display, and attach behavior to |
eads@16
|
6 * a "media library" provided via JSON and implemented for Drupal running |
eads@16
|
7 * the Wysiwyg plugin. |
eads@16
|
8 */ |
eads@16
|
9 |
eads@16
|
10 /** |
eads@16
|
11 * Extend jQuery a bit |
eads@16
|
12 * |
eads@16
|
13 * We add a selector to look for "empty" elements (empty elements in TinyMCE |
eads@17
|
14 * often have non-breaking spaces and <br /> tags). An exception is required |
eads@17
|
15 * to make this work in IE. |
eads@16
|
16 */ |
eads@16
|
17 (function($) { |
eads@16
|
18 // Custom selectors |
eads@16
|
19 $.extend($.expr[":"], { |
eads@16
|
20 'empty' : function(a, i, m) { |
eads@17
|
21 return !$(a).filter(function(i) { |
eads@17
|
22 return !$(this).is('br'); |
eads@17
|
23 }).length && !$.trim(a.textContent || a.innerText||$(a).text()||""); |
eads@16
|
24 } |
eads@16
|
25 }); |
eads@16
|
26 }) (jQuery); |
eads@16
|
27 |
eads@2
|
28 Drupal.behaviors.dndLibrary = function(context) { |
eads@16
|
29 $('.dnd-library-wrapper', context).each(function() { |
eads@4
|
30 var $this = $(this); |
eads@2
|
31 |
eads@4
|
32 // This is a bad hack to lop off '-dnd-library' from the id to get the editor name |
eads@17
|
33 var $editor = $('#' + this.id.slice(0, -12)); |
eads@17
|
34 |
eads@2
|
35 |
eads@16
|
36 // Bind Drag and Drop plugin invocation to events emanating from Wysiwyg |
eads@17
|
37 $editor.bind('wysiwygAttach', Drupal.behaviors.dndLibrary.attach_library); |
eads@17
|
38 $editor.bind('wysiwygDetach', Drupal.behaviors.dndLibrary.detach_library); |
eads@17
|
39 |
eads@17
|
40 // Add basic hover behavior to editor items |
eads@17
|
41 $('.editor-item', context).hover(function() { |
eads@17
|
42 var $this = $(this); |
eads@17
|
43 var p = $('#edit-body-wrapper').position(); |
eads@17
|
44 var preview = $(Drupal.settings.dndLibraryPreviews[this.id]).css({ |
eads@17
|
45 'position' : 'absolute', |
eads@17
|
46 'top' : p.top + 150, |
eads@17
|
47 'left' : p.left + $('#edit-body-wrapper').width() + 150, |
eads@17
|
48 'display' : 'none', |
eads@17
|
49 'width' : '300px', |
eads@17
|
50 'background-color' : '#ddd', |
eads@17
|
51 'border' : '3px solid #999', |
eads@17
|
52 'padding' : '4px', |
eads@17
|
53 'z-index' : 10 |
eads@17
|
54 }); |
eads@17
|
55 $('body').prepend(preview); |
eads@17
|
56 preview.fadeIn('slow'); |
eads@17
|
57 }, function() { |
eads@17
|
58 $('#' + this.id.replace(/test/,'preview')).fadeOut('fast', function() { $(this).remove(); }); |
eads@17
|
59 }); |
eads@4
|
60 |
eads@4
|
61 // Ajax pager |
eads@17
|
62 $('.pager a', $this).click(function() { |
eads@2
|
63 $.getJSON(this.href, function(data) { |
eads@17
|
64 Drupal.behaviors.dndLibrary.refreshLibrary.call($this.get(0), data, $editor); |
eads@2
|
65 }); |
eads@17
|
66 |
eads@17
|
67 // Reattach behaviors |
eads@17
|
68 Drupal.behaviors.dndLibrary(); |
eads@17
|
69 |
eads@2
|
70 return false; |
eads@2
|
71 }); |
eads@17
|
72 |
eads@17
|
73 // Preload images in editor representations |
eads@17
|
74 var cached = $.data($editor, 'dnd_preload') || {}; |
eads@17
|
75 for (editor_id in Drupal.settings.dndEditorRepresentations) { |
eads@17
|
76 if (!cached[editor_id]) { |
eads@17
|
77 $representation = $(Drupal.settings.dndEditorRepresentations[editor_id]); |
eads@17
|
78 if ($representation.is('img') && $representation.get(0).src) { |
eads@17
|
79 $representation.attr('src', $representation.get(0).src); |
eads@17
|
80 } else { |
eads@17
|
81 $('img', $representation).each(function() { |
eads@17
|
82 this.attr('src', this.src); |
eads@17
|
83 }); |
eads@17
|
84 } |
eads@17
|
85 } |
eads@17
|
86 } |
eads@17
|
87 $.data($editor, 'dnd_preload', cached); |
eads@4
|
88 }); |
eads@4
|
89 } |
eads@2
|
90 |
eads@17
|
91 Drupal.behaviors.dndLibrary.refreshLibrary = function(data, editor) { |
eads@17
|
92 $this = $(this); |
eads@17
|
93 |
eads@17
|
94 $('.header', $this).html(data.header); |
eads@17
|
95 $('.library', $this).html(data.library); |
eads@17
|
96 $('.footer', $this).html(data.footer); |
eads@17
|
97 |
eads@17
|
98 var params = Drupal.wysiwyg.instances[editor.get(0).id]; |
eads@17
|
99 editor.trigger('wysiwygDetach', params); |
eads@17
|
100 editor.trigger('wysiwygAttach', params); |
eads@17
|
101 |
eads@17
|
102 for (editor_id in data.editor_representations) { |
eads@17
|
103 Drupal.settings.dndEditorRepresentations[editor_id] = data.editor_representations[editor_id]; |
eads@17
|
104 } |
eads@17
|
105 for (preview_id in data.library_previews) { |
eads@17
|
106 Drupal.settings.dndLibraryPreviews[preview_id] = data.library_previews[preview_id]; |
eads@17
|
107 } |
eads@17
|
108 } |
eads@17
|
109 |
eads@17
|
110 |
eads@16
|
111 // Dynamically compose a callback based on the editor name |
eads@4
|
112 Drupal.behaviors.dndLibrary.attach_library = function(e, data) { |
eads@17
|
113 var settings = $.extend({idSelector: Drupal.behaviors.dndLibrary.idSelector}, Drupal.settings.dndEnabledLibraries[data.field]); |
eads@4
|
114 var editor_fn = 'attach_' + data.editor; |
eads@4
|
115 if ($.isFunction(window.Drupal.behaviors.dndLibrary[editor_fn])) { |
eads@17
|
116 window.Drupal.behaviors.dndLibrary[editor_fn](data, settings); |
eads@2
|
117 } |
eads@2
|
118 } |
eads@2
|
119 |
eads@16
|
120 // Do garbage collection on detach |
eads@4
|
121 Drupal.behaviors.dndLibrary.detach_library = function(e, data) { |
eads@15
|
122 for (t in $(document).data('dnd_timers')) { |
eads@15
|
123 clearInterval(t); |
eads@15
|
124 } |
eads@15
|
125 $(document).removeData('dnd_timers'); |
eads@4
|
126 } |
eads@4
|
127 |
eads@16
|
128 // Basic textareas |
eads@16
|
129 Drupal.behaviors.dndLibrary.attach_none = function(data, settings) { |
eads@16
|
130 settings = $.extend({ |
eads@16
|
131 targets: $('#'+ data.field), |
eads@17
|
132 processTextAreaDrop: function(target, clicked, representation_id, e, data) { |
eads@17
|
133 var snippet = '<p class="dnd-dropped-wrapper">' + Drupal.settings.dndEditorRepresentations[representation_id] + '</p>'; |
eads@16
|
134 $(target).replaceSelection(snippet, true); |
eads@16
|
135 } |
eads@16
|
136 }, settings); |
eads@16
|
137 $(settings.drop_selector).dnd(settings); |
eads@16
|
138 } |
eads@4
|
139 |
eads@16
|
140 // Attach TinyMCE |
eads@2
|
141 Drupal.behaviors.dndLibrary.attach_tinymce = function(data, settings) { |
eads@4
|
142 var tiny_instance = tinyMCE.getInstanceById(data.field); |
eads@4
|
143 |
eads@4
|
144 // If the Tiny instance exists, attach directly, otherwise wait until Tiny |
eads@4
|
145 // has registered a new instance. |
eads@4
|
146 if (tiny_instance) { |
eads@4
|
147 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance); |
eads@4
|
148 } else { |
eads@4
|
149 var t = setInterval(function() { |
eads@4
|
150 var tiny_instance = tinyMCE.getInstanceById(data.field); |
eads@4
|
151 if (tiny_instance) { |
eads@4
|
152 Drupal.behaviors.dndLibrary._attach_tinymce(data, settings, tiny_instance); |
eads@4
|
153 clearInterval(t); |
eads@2
|
154 } |
eads@4
|
155 }, 100); |
eads@4
|
156 } |
eads@4
|
157 } |
eads@2
|
158 |
eads@17
|
159 Drupal.behaviors.dndLibrary.idSelector = function(element) { |
eads@17
|
160 if ($(element).is('img')) { |
eads@17
|
161 return $.url.setUrl(element.src).param('dnd_id'); |
eads@17
|
162 } |
eads@17
|
163 return false; |
eads@17
|
164 } |
eads@17
|
165 |
eads@16
|
166 // Really attach TinyMCE |
eads@4
|
167 Drupal.behaviors.dndLibrary._attach_tinymce = function(data, settings, tiny_instance) { |
eads@16
|
168 var ed = tiny_instance, dom = ed.dom, s = ed.selection; |
eads@16
|
169 |
eads@4
|
170 settings = $.extend({ |
eads@4
|
171 targets: $('#'+ data.field +'-wrapper iframe'), |
eads@16
|
172 interval: 100, |
eads@16
|
173 processIframeDrop: function(target, dragged, dropped, representation_id) { |
eads@16
|
174 var representation = Drupal.settings.dndEditorRepresentations[representation_id]; |
eads@16
|
175 var $target = $(target), $dropped = $(dropped), $dragged = $(dragged), block; |
eads@15
|
176 |
eads@16
|
177 // Search through block level parents |
eads@16
|
178 $dropped.parents().each(function() { |
eads@16
|
179 var $this = $(this); |
eads@16
|
180 if ($this.css('display') == 'block') { |
eads@16
|
181 block = this; |
eads@16
|
182 return false; |
eads@16
|
183 } |
eads@16
|
184 }); |
eads@16
|
185 |
eads@16
|
186 // Remove dropped item |
eads@16
|
187 $dropped.remove(); |
eads@16
|
188 |
eads@16
|
189 // Create an element to insert |
eads@16
|
190 var insert = dom.create('p', {'class' : 'dnd-dropped-wrapper', 'id' : 'dnd-inserted'}, representation); |
eads@16
|
191 |
eads@16
|
192 // The no-parent case |
eads@16
|
193 if ($(block).is('body')) { |
eads@16
|
194 s.setNode(insert); |
eads@16
|
195 } |
eads@16
|
196 else { |
eads@16
|
197 var old_id = block.id; |
eads@16
|
198 block.id = 'target-block'; |
eads@16
|
199 $block = $('#target-block', $target.contents()); |
eads@16
|
200 |
eads@16
|
201 // @TODO is finding the parent broken in safari?? |
eads@16
|
202 $block.after('<p class="dnd-dropped-wrapper" id="dnd-inserted">' + representation + '</p>'); |
eads@16
|
203 |
eads@16
|
204 // The active target block should be empty |
eads@16
|
205 if ($('#target-block:empty', $target.contents()).length > 0) { |
eads@17
|
206 $('#target-block', $target.contents()).remove(); |
eads@16
|
207 } else if (old_id) { |
eads@16
|
208 block.id = old_id; |
eads@16
|
209 } else { |
eads@16
|
210 $block.removeAttr('id'); |
eads@16
|
211 } |
eads@16
|
212 } |
eads@16
|
213 |
eads@16
|
214 var $inserted = $('#dnd-inserted', $target.contents()); |
eads@16
|
215 var inserted = $inserted.get(0); |
eads@16
|
216 |
eads@16
|
217 // Look behind in the DOM |
eads@16
|
218 var previous = $inserted.prev().get(0); |
eads@16
|
219 |
eads@16
|
220 // If the previous element is also an editor representation, we need to |
eads@16
|
221 // put a dummy paragraph between the elements to prevent editor errors. |
eads@16
|
222 if (previous && $(previous).hasClass('dnd-dropped-wrapper')) { |
eads@16
|
223 $inserted.before('<p><span id="__spacer">_</span></p>'); |
eads@16
|
224 c = dom.get('__spacer'); |
eads@16
|
225 s.select(c); |
eads@16
|
226 ed.execCommand('Delete', false, null); |
eads@16
|
227 dom.remove(c); |
eads@16
|
228 } |
eads@16
|
229 |
eads@16
|
230 // Look ahead in the DOM |
eads@16
|
231 var next = $inserted.next().get(0); |
eads@16
|
232 |
eads@16
|
233 // If the next item exists and isn't an editor representation, drop the |
eads@16
|
234 // caret at the beginning of the element, otherwise make a new paragraph |
eads@16
|
235 // to advance the caret to. |
eads@16
|
236 if (next && !$(next).hasClass('dnd-dropped-wrapper')) { |
eads@16
|
237 $(next).prepend('<span id="__caret">_</span>'); |
eads@16
|
238 } |
eads@16
|
239 else { |
eads@16
|
240 var after = dom.create('p', {}, '<span id="__caret">_</span>'); |
eads@16
|
241 dom.insertAfter(after, 'dnd-inserted'); |
eads@16
|
242 } |
eads@16
|
243 |
eads@16
|
244 // Clear the ID for the next drop |
eads@16
|
245 $inserted.removeAttr('id'); |
eads@16
|
246 |
eads@16
|
247 // Force selection to reset the caret |
eads@16
|
248 var c = dom.get('__caret'); |
eads@16
|
249 s.select(c); |
eads@16
|
250 ed.execCommand('Delete', false, null); |
eads@16
|
251 dom.remove(c); |
eads@4
|
252 |
eads@4
|
253 // Add some classes to the library items |
eads@16
|
254 $(dragged).addClass('dnd-inserted'); |
eads@16
|
255 drag_parents = $(dragged).parents('.editor-item'); |
eads@16
|
256 drag_parents.addClass('dnd-child-inserted'); |
eads@4
|
257 } |
eads@4
|
258 }, settings); |
eads@4
|
259 |
eads@4
|
260 $(settings.drop_selector).dnd(settings); |
eads@2
|
261 } |