Mercurial > defr > drupal > scald > dnd
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 | |
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 } |