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