eads@3
|
1 /* jQuery Drag and Drop Library for Rich Editors |
eads@3
|
2 * |
eads@12
|
3 * A helper library which provides the ability to drag and drop images to Rich |
eads@12
|
4 * Text Editors (RTEs) that use the embedded iframe + DesignMode method, and |
eads@12
|
5 * provides a simple "clicky" interface for inserting the same markup directly |
eads@12
|
6 * into a textarea. |
eads@3
|
7 * |
eads@3
|
8 * Basic usage: |
eads@3
|
9 * |
eads@12
|
10 * $('img.my-class').dnd({targets: $('#my-iframe')}); |
eads@3
|
11 * |
eads@3
|
12 * Options: |
eads@3
|
13 * |
eads@3
|
14 * targets |
eads@3
|
15 * A jQuery object corresponding to the proper iframe(s) to enable dragging |
eads@3
|
16 * and dropping for. [Required] |
eads@3
|
17 * |
eads@3
|
18 * dropWrapper |
eads@3
|
19 * An html snippet to wrap the entire inserted content with in the editor |
eads@3
|
20 * representation (i.e. '<p class="foo"></p>') |
eads@3
|
21 * |
eads@3
|
22 * insertBefore: |
eads@3
|
23 * Markup to insert before drop (i.e. '<hr />') |
eads@3
|
24 * |
eads@3
|
25 * insertAfter: |
eads@3
|
26 * Markup to insert after drop (i.e. '<div class="clearfix"></div>') |
eads@3
|
27 * |
eads@3
|
28 * processedClass: |
eads@3
|
29 * The class to apply to links and images tagged as droppable. This class |
eads@3
|
30 * should have a style rule in the editor that sets display to 'none' for |
eads@3
|
31 * the best experience. |
eads@3
|
32 * |
eads@3
|
33 * idSelector: |
eads@3
|
34 * A callback that parses a unique id out of a droppable element. B y default |
eads@3
|
35 * this uses the id of the element, but one could parse out an ID based on |
eads@3
|
36 * any part of the URL, interior markup, etc. |
eads@3
|
37 * |
eads@3
|
38 * renderRepresentation: |
eads@3
|
39 * A callback that defines the mechanism for rendering a representation of |
eads@3
|
40 * the content. The default is currently essential useless. |
eads@3
|
41 * |
eads@3
|
42 * preprocessDrop: |
eads@3
|
43 * A callback that preprocesses the dropped snippet in the iframe, before |
eads@3
|
44 * replacing it. By default this uses a little logic to walk up the DOM |
eads@3
|
45 * tree to topmost parent of the place in the source where the item was |
eads@3
|
46 * dropped, and add the dropped element after that parent instead of |
eads@3
|
47 * inside it. |
eads@3
|
48 * |
eads@3
|
49 * postprocessDrop: |
eads@3
|
50 * A callback that postprocesses the iframe. |
eads@3
|
51 * |
eads@12
|
52 * interval: |
eads@12
|
53 * How often to check the iframe for a drop, in milliseconds. |
eads@12
|
54 * |
eads@12
|
55 * Usage notes: |
eads@12
|
56 * |
eads@12
|
57 * This is a very tricky problem and to achieve cross browser (as of writing, |
eads@12
|
58 * IE, Safari, and Firefox) support, severe limitations must be made on the |
eads@12
|
59 * nature of DOM elements that are dropped. |
eads@12
|
60 * |
eads@12
|
61 * Droppable elements must be <img> tags. Internet Explorer will not accept |
eads@12
|
62 * anchors, and Safari strips attributes and additional markup from |
eads@12
|
63 * dropped anchors, images, and snippets. |
eads@12
|
64 * |
eads@12
|
65 * While the idSelector option allows you to parse the dropped element for |
eads@12
|
66 * any attribute that "comes along" with the element when it is dropped in a |
eads@12
|
67 * designmode-enabled iframe, the only safe attribute to scan is the 'src' |
eads@12
|
68 * attribute, and to avoid strange "relativization" of links, the src should |
eads@12
|
69 * always be expressed as an absolute url with a fully qualified domain name. |
eads@12
|
70 * |
eads@12
|
71 * Implementation notes and todos: |
eads@12
|
72 * |
eads@12
|
73 * Currently, there is no garbage collection instituted for the many many |
eads@12
|
74 * timers that are created, so memory usage could become as issue in scenarios |
eads@12
|
75 * where the user does a lot of paging or otherwise winds up invoking |
eads@12
|
76 * drag and drop on large numbers of elements. |
eads@3
|
77 */ |
eads@3
|
78 |
eads@3
|
79 (function($) { |
eads@3
|
80 $.fn.dnd = function(opt) { |
eads@3
|
81 opt = $.extend({}, { |
eads@4
|
82 dropWrapper: '<p class="dnd-dropped"></p>', |
eads@12
|
83 insertBefore: false, |
eads@12
|
84 insertAfter: false, |
eads@3
|
85 processedClass: 'dnd-processed', |
eads@12
|
86 interval: 100, |
eads@3
|
87 |
eads@3
|
88 processTargets: function(targets) { |
eads@3
|
89 return targets.each(function() { |
eads@12
|
90 //$('head', $(this).contents()).append('<style type="text/css">.dnd-processed { display: none; }</style>'); |
eads@12
|
91 //@TODO use jQuery.rules() |
eads@3
|
92 return this; |
eads@3
|
93 }); |
eads@3
|
94 }, |
eads@3
|
95 |
eads@3
|
96 // Must return a string |
eads@3
|
97 idSelector: function(element) { |
eads@12
|
98 if ($(element).is('img')) { |
eads@12
|
99 return $.url.setUrl(element.src).param('dnd_id'); |
eads@3
|
100 } |
eads@3
|
101 }, |
eads@3
|
102 |
eads@3
|
103 // @TODO: Target should be jQuery object |
eads@3
|
104 targets: $('iframe, textarea'), |
eads@3
|
105 |
eads@3
|
106 // Must return a string that DOES NOT share the id of any droppable item |
eads@3
|
107 // living outside the iframe |
eads@3
|
108 renderRepresentation: function(target, drop, representation_id) { |
eads@3
|
109 // Keep a counter of how many times this element was used |
eads@3
|
110 var count = $.data(target, representation_id +'_count'); |
eads@3
|
111 if (!count) { |
eads@3
|
112 count = 1; |
eads@3
|
113 } else { |
eads@3
|
114 count++; |
eads@3
|
115 } |
eads@3
|
116 $.data(target, representation_id +'_count', count); |
eads@3
|
117 return '<span id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</span>'; |
eads@3
|
118 }, |
eads@3
|
119 |
eads@3
|
120 // Back out markup to render in place after parent container |
eads@3
|
121 preprocessDrop: function(target, drop) { |
eads@12
|
122 return drop; |
eads@3
|
123 var old_parent = false; |
eads@3
|
124 var element_id = ''; |
eads@3
|
125 |
eads@3
|
126 var parents = drop.parents(); |
eads@11
|
127 for (var i=0; i < parents.length; i++) { |
eads@3
|
128 if ($(parents[i]).is('body')) { |
eads@3
|
129 element_id = $(drop).get(0).id; |
eads@3
|
130 $(old_parent).after(drop.clone()); |
eads@3
|
131 drop.remove(); |
eads@3
|
132 } |
eads@3
|
133 old_parent = parents[i]; |
eads@3
|
134 } |
eads@3
|
135 return $('#'+ element_id, $(target).contents()); |
eads@3
|
136 }, |
eads@3
|
137 |
eads@4
|
138 postprocessDrop: function(target, drop, element) { |
eads@4
|
139 $(element).addClass('dnd-inserted'); |
eads@4
|
140 } |
eads@3
|
141 |
eads@3
|
142 }, opt); |
eads@3
|
143 |
eads@3
|
144 // Initialize plugin |
eads@3
|
145 var targets = opt.processTargets(opt.targets); |
eads@3
|
146 |
eads@3
|
147 // Process! |
eads@3
|
148 return this.each(function() { |
eads@12
|
149 if ($(this).is('img')) { |
eads@12
|
150 var element = this; |
eads@3
|
151 |
eads@12
|
152 // If we don't have a proper id, bail |
eads@12
|
153 var representation_id = opt.idSelector(element); |
eads@3
|
154 |
eads@12
|
155 if (!representation_id) { |
eads@12
|
156 return this; |
eads@12
|
157 }; |
eads@12
|
158 |
eads@12
|
159 // Add some UI sugar and a special class |
eads@12
|
160 $(element) |
eads@12
|
161 .css('cursor', 'move') |
eads@12
|
162 .addClass(opt.processedClass); |
eads@12
|
163 |
eads@12
|
164 // We need to differentiate behavior based on the targets... I guess. |
eads@12
|
165 targets.each(function() { |
eads@12
|
166 if ($(this).is('iframe')) { |
eads@12
|
167 var target = this; |
eads@12
|
168 var selector = 'img[src='+ element.src +']'; |
eads@12
|
169 |
eads@12
|
170 // Watch the iframe for changes |
eads@12
|
171 var t = setInterval(function() { |
eads@14
|
172 $('img', $(target).contents()).each(function() { |
eads@14
|
173 if (opt.idSelector(this) == representation_id) { |
eads@14
|
174 var drop = opt.preprocessDrop(target, $(this)); // Must return a jquery object |
eads@14
|
175 var representation = opt.renderRepresentation(target, drop, representation_id); |
eads@14
|
176 if (representation) { |
eads@14
|
177 if (opt.dropWrapper) { |
eads@14
|
178 drop.wrap(opt.dropWrapper); |
eads@14
|
179 } |
eads@14
|
180 if (opt.insertBefore) { |
eads@14
|
181 drop.before(opt.insertBefore); |
eads@14
|
182 } |
eads@14
|
183 if (opt.insertAfter) { |
eads@14
|
184 drop.after(opt.insertAfter); |
eads@14
|
185 } |
eads@14
|
186 drop.replaceWith(representation); |
eads@14
|
187 opt.postprocessDrop(target, drop, element); |
eads@12
|
188 } |
eads@12
|
189 } |
eads@14
|
190 }); |
eads@12
|
191 }, opt.interval); |
eads@12
|
192 // @TODO track the timer with $.data() so we can clear it? |
eads@12
|
193 } else if ($(this).is('textarea')) { |
eads@12
|
194 //console.log('@TODO handle textareas via.... regexp?'); |
eads@12
|
195 } |
eads@12
|
196 }); |
eads@3
|
197 } |
eads@3
|
198 }); |
eads@11
|
199 }; |
eads@3
|
200 })(jQuery); |