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@16
|
4 * Text Editors (RTEs) that use the embedded iframe + DesignMode method. DnD |
eads@16
|
5 * also provides a simple "clicky" interface for inserting the same markup |
eads@16
|
6 * directly 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@16
|
14 * targets (Required): |
eads@16
|
15 * A jQuery object corresponding to the proper iframe(s) and/or textarea(s) |
eads@16
|
16 * that are allowed drop targets for the current set of elements. |
eads@3
|
17 * |
eads@16
|
18 * idSelector: |
eads@16
|
19 * A callback that parses out the unique ID of an image that is dropped in an |
eads@16
|
20 * iframe. While some browsers (such as Firefox and Internet Explorer) allow |
eads@16
|
21 * markup to be copied when dragging and dropping, Safari (and assumably |
eads@16
|
22 * other webkit based browsers) don't. The upshot is that the safest bet is |
eads@16
|
23 * to parse the element's src URL. Because querystrings seem to drop |
eads@16
|
24 * consistently across |
eads@3
|
25 * |
eads@16
|
26 * processIframeDrop: |
eads@16
|
27 * A callback that defines the mechanism for inserting and rendering the |
eads@16
|
28 * dropped item in an iframe. The typical usage pattern I expect to see is |
eads@16
|
29 * that implementers will listen for their RTE to load and then invoke DnD |
eads@16
|
30 * with a processIframeDrop appropriate to their editor. |
eads@3
|
31 * |
eads@16
|
32 * processTextAreaDrop: |
eads@16
|
33 * A callback that defines the mechanism for inserting and rendering the |
eads@16
|
34 * clicked item in a textarea. The default function just tries to insert |
eads@16
|
35 * some markup at the caret location in the current textarea. |
eads@3
|
36 * |
eads@12
|
37 * interval: |
eads@12
|
38 * How often to check the iframe for a drop, in milliseconds. |
eads@12
|
39 * |
eads@16
|
40 * |
eads@16
|
41 * |
eads@12
|
42 * Usage notes: |
eads@12
|
43 * |
eads@16
|
44 * Due to cross browser flakiness, there are many many limitations on what is |
eads@16
|
45 * possible in terms of dragging and dropping to DesignMode enabled iframes. |
eads@12
|
46 * |
eads@16
|
47 * To make this work, your "droppable" elements must be image tags. No ifs, ands, |
eads@16
|
48 * or buts: image tags have the best cross browser behavior when dragging. |
eads@12
|
49 * |
eads@16
|
50 * When DnD is initialized, it begins timers which periodically check your |
eads@16
|
51 * editor iframe. If an image is dropped in, DnD takes the element and |
eads@16
|
52 * attempts to parse it for a unique ID which you can use to do a lookup for |
eads@16
|
53 * an "editor representation." If an editor representation is found, |
eads@16
|
54 * typically the image is replaced with the representation snippet, but you are |
eads@16
|
55 * free to do whatever you want. |
eads@12
|
56 * |
eads@16
|
57 * Because of browser limitations, the safest way to parse the element is to |
eads@16
|
58 * look at the img's src attribute and use some or all of the image URL. |
eads@16
|
59 * In my experience, the best way to deal with this is simply to parse out |
eads@16
|
60 * generate a query string in the src attribute server side that corresponds |
eads@16
|
61 * to the proper representation ID. |
eads@12
|
62 * |
eads@16
|
63 * If the target is not an iframe but a textarea, DnD provides a very minimal |
eads@16
|
64 * system for clicking what would otherwise be dragged to insert markup into |
eads@16
|
65 * the textarea. |
eads@16
|
66 * |
eads@16
|
67 * DnD is purely a utility library: to make it work for any particular editor, |
eads@16
|
68 * CMS, etc: It is very unlikely it will provide much benefit out of the box. |
eads@16
|
69 * |
eads@16
|
70 * Because DnD spawns so many timers, they are stored in a $.data element |
eads@16
|
71 * attached to the parent document body. Implementers should consider clearing |
eads@16
|
72 * the timers when building systems such as dynamic searches or paging |
eads@16
|
73 * functionality to ensure memory usage and performance remains stable. |
eads@3
|
74 */ |
eads@3
|
75 |
eads@3
|
76 (function($) { |
eads@3
|
77 $.fn.dnd = function(opt) { |
eads@3
|
78 opt = $.extend({}, { |
eads@15
|
79 interval: 250, |
eads@16
|
80 targets: $('iframe, textarea'), |
eads@3
|
81 processTargets: function(targets) { |
eads@3
|
82 return targets.each(function() { |
eads@15
|
83 $('head', $(this).contents()).append('<style type="text/css">img { display: none; } img.dnd-dropped {display: block; }</style>'); |
eads@3
|
84 return this; |
eads@3
|
85 }); |
eads@3
|
86 }, |
eads@3
|
87 idSelector: function(element) { |
eads@12
|
88 if ($(element).is('img')) { |
eads@16
|
89 return element.src; |
eads@3
|
90 } |
eads@16
|
91 return false; |
eads@3
|
92 }, |
eads@16
|
93 processIframeDrop: function(target, dragged, dropped, representation_id) { |
eads@3
|
94 // Keep a counter of how many times this element was used |
eads@3
|
95 var count = $.data(target, representation_id +'_count'); |
eads@3
|
96 if (!count) { |
eads@3
|
97 count = 1; |
eads@3
|
98 } else { |
eads@3
|
99 count++; |
eads@3
|
100 } |
eads@3
|
101 $.data(target, representation_id +'_count', count); |
eads@16
|
102 $(dropped).replaceWith('<p id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</p>'); |
eads@3
|
103 }, |
eads@16
|
104 processTextAreaDrop: function(target, clicked, representation_id, e, data) { |
eads@16
|
105 var snippet = '<div><img src="'+ representation_id +'" /></div>'; |
eads@16
|
106 $(target).replaceSelection(snippet, true); |
eads@16
|
107 e.preventDefault(); |
eads@16
|
108 } |
eads@3
|
109 |
eads@3
|
110 }, opt); |
eads@3
|
111 |
eads@3
|
112 // Initialize plugin |
eads@3
|
113 var targets = opt.processTargets(opt.targets); |
eads@3
|
114 |
eads@3
|
115 // Process! |
eads@3
|
116 return this.each(function() { |
eads@12
|
117 if ($(this).is('img')) { |
eads@16
|
118 var element = this, $element = $(element); |
eads@3
|
119 |
eads@12
|
120 // If we don't have a proper id, bail |
eads@12
|
121 var representation_id = opt.idSelector(element); |
eads@3
|
122 |
eads@12
|
123 if (!representation_id) { |
eads@12
|
124 return this; |
eads@12
|
125 }; |
eads@12
|
126 |
eads@16
|
127 // Add a special class |
eads@16
|
128 $(element).addClass('dnd-processed'); |
eads@12
|
129 |
eads@12
|
130 // We need to differentiate behavior based on the targets... I guess. |
eads@12
|
131 targets.each(function() { |
eads@16
|
132 var target = this, $target = $(target); |
eads@16
|
133 if ($target.is('iframe')) { |
eads@15
|
134 |
eads@16
|
135 // Indicate this element is draggy |
eads@16
|
136 $element.css('cursor', 'move'); |
eads@16
|
137 |
eads@12
|
138 // Watch the iframe for changes |
eads@12
|
139 var t = setInterval(function() { |
eads@16
|
140 $('img:not(.dnd-dropped)', $(target).contents()).each(function() { |
eads@16
|
141 var dropped = this; |
eads@16
|
142 if (opt.idSelector(dropped) == representation_id) { |
eads@16
|
143 opt.processIframeDrop(target, element, dropped, representation_id); |
eads@16
|
144 } |
eads@14
|
145 }); |
eads@12
|
146 }, opt.interval); |
eads@15
|
147 |
eads@16
|
148 // Track current active timers -- developers working with DnD |
eads@16
|
149 // can implement their own garbage collection for specific |
eads@16
|
150 // interactions, such as paging or live search. |
eads@15
|
151 var data = $(document).data('dnd_timers'); |
eads@15
|
152 if (data) { |
eads@15
|
153 data[data.length] = t; |
eads@15
|
154 } else { |
eads@15
|
155 data = new Array(); |
eads@15
|
156 data[0] = t; |
eads@15
|
157 } |
eads@15
|
158 $(document).data('dnd_timers', data); |
eads@15
|
159 |
eads@16
|
160 } else if ($target.is('textarea')) { |
eads@17
|
161 $element.css('cursor', 'pointer'); |
eads@16
|
162 $(element).click(function(e, data) { |
eads@16
|
163 opt.processTextAreaDrop(target, element, representation_id, e, data); |
eads@16
|
164 }); |
eads@12
|
165 } |
eads@12
|
166 }); |
eads@3
|
167 } |
eads@3
|
168 }); |
eads@11
|
169 }; |
eads@3
|
170 })(jQuery); |