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@15
|
34 * A callback that parses a unique id out of a droppable element. By 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@15
|
82 interval: 250, |
eads@15
|
83 dropWrapper: '<p class="dnd-dropped-wrapper"></p>', |
eads@12
|
84 insertBefore: false, |
eads@12
|
85 insertAfter: false, |
eads@3
|
86 processedClass: 'dnd-processed', |
eads@15
|
87 ignoreClass: 'dnd-dropped', |
eads@3
|
88 processTargets: function(targets) { |
eads@3
|
89 return targets.each(function() { |
eads@15
|
90 $('head', $(this).contents()).append('<style type="text/css">img { display: none; } img.dnd-dropped {display: block; }</style>'); |
eads@3
|
91 return this; |
eads@3
|
92 }); |
eads@3
|
93 }, |
eads@3
|
94 |
eads@3
|
95 // Must return a string |
eads@3
|
96 idSelector: function(element) { |
eads@12
|
97 if ($(element).is('img')) { |
eads@12
|
98 return $.url.setUrl(element.src).param('dnd_id'); |
eads@3
|
99 } |
eads@3
|
100 }, |
eads@3
|
101 |
eads@3
|
102 // @TODO: Target should be jQuery object |
eads@3
|
103 targets: $('iframe, textarea'), |
eads@3
|
104 |
eads@3
|
105 // Must return a string that DOES NOT share the id of any droppable item |
eads@3
|
106 // living outside the iframe |
eads@3
|
107 renderRepresentation: function(target, drop, representation_id) { |
eads@3
|
108 // Keep a counter of how many times this element was used |
eads@3
|
109 var count = $.data(target, representation_id +'_count'); |
eads@3
|
110 if (!count) { |
eads@3
|
111 count = 1; |
eads@3
|
112 } else { |
eads@3
|
113 count++; |
eads@3
|
114 } |
eads@3
|
115 $.data(target, representation_id +'_count', count); |
eads@3
|
116 return '<span id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</span>'; |
eads@3
|
117 }, |
eads@3
|
118 |
eads@15
|
119 preprocessDrop: function(target, drop) { return drop; }, |
eads@4
|
120 postprocessDrop: function(target, drop, element) { |
eads@4
|
121 $(element).addClass('dnd-inserted'); |
eads@4
|
122 } |
eads@3
|
123 |
eads@3
|
124 }, opt); |
eads@3
|
125 |
eads@3
|
126 // Initialize plugin |
eads@3
|
127 var targets = opt.processTargets(opt.targets); |
eads@3
|
128 |
eads@3
|
129 // Process! |
eads@3
|
130 return this.each(function() { |
eads@12
|
131 if ($(this).is('img')) { |
eads@12
|
132 var element = this; |
eads@3
|
133 |
eads@12
|
134 // If we don't have a proper id, bail |
eads@12
|
135 var representation_id = opt.idSelector(element); |
eads@3
|
136 |
eads@12
|
137 if (!representation_id) { |
eads@12
|
138 return this; |
eads@12
|
139 }; |
eads@12
|
140 |
eads@12
|
141 // Add some UI sugar and a special class |
eads@12
|
142 $(element) |
eads@12
|
143 .css('cursor', 'move') |
eads@12
|
144 .addClass(opt.processedClass); |
eads@12
|
145 |
eads@12
|
146 // We need to differentiate behavior based on the targets... I guess. |
eads@12
|
147 targets.each(function() { |
eads@12
|
148 if ($(this).is('iframe')) { |
eads@12
|
149 var target = this; |
eads@15
|
150 |
eads@15
|
151 var selector = 'img:not(.' + opt.ignoreClass + ')'; |
eads@12
|
152 |
eads@12
|
153 // Watch the iframe for changes |
eads@12
|
154 var t = setInterval(function() { |
eads@15
|
155 $(selector, $(target).contents()).each(function() { |
eads@14
|
156 if (opt.idSelector(this) == representation_id) { |
eads@14
|
157 var drop = opt.preprocessDrop(target, $(this)); // Must return a jquery object |
eads@14
|
158 var representation = opt.renderRepresentation(target, drop, representation_id); |
eads@14
|
159 if (representation) { |
eads@15
|
160 // Breaks IE 7! |
eads@15
|
161 /*if (opt.dropWrapper) { |
eads@14
|
162 drop.wrap(opt.dropWrapper); |
eads@15
|
163 }*/ |
eads@14
|
164 if (opt.insertBefore) { |
eads@14
|
165 drop.before(opt.insertBefore); |
eads@14
|
166 } |
eads@14
|
167 if (opt.insertAfter) { |
eads@14
|
168 drop.after(opt.insertAfter); |
eads@14
|
169 } |
eads@14
|
170 drop.replaceWith(representation); |
eads@14
|
171 opt.postprocessDrop(target, drop, element); |
eads@12
|
172 } |
eads@15
|
173 } |
eads@14
|
174 }); |
eads@12
|
175 }, opt.interval); |
eads@15
|
176 |
eads@15
|
177 // Track current active timers -- this means you can implement |
eads@15
|
178 // your own garbage collection for specific interactions, such |
eads@15
|
179 // as paging. |
eads@15
|
180 var data = $(document).data('dnd_timers'); |
eads@15
|
181 if (data) { |
eads@15
|
182 data[data.length] = t; |
eads@15
|
183 } else { |
eads@15
|
184 data = new Array(); |
eads@15
|
185 data[0] = t; |
eads@15
|
186 } |
eads@15
|
187 $(document).data('dnd_timers', data); |
eads@15
|
188 |
eads@12
|
189 } else if ($(this).is('textarea')) { |
eads@12
|
190 //console.log('@TODO handle textareas via.... regexp?'); |
eads@12
|
191 } |
eads@12
|
192 }); |
eads@3
|
193 } |
eads@3
|
194 }); |
eads@11
|
195 }; |
eads@3
|
196 })(jQuery); |