Mercurial > defr > drupal > scald > dnd
comparison js/dnd.js @ 3:5df0783706f7
Reorganized layout of module and repo a bit.
author | David Eads <eads@chicagotech.org> |
---|---|
date | Tue, 17 Feb 2009 15:46:36 -0600 |
parents | |
children | c2eb995212bf |
comparison
equal
deleted
inserted
replaced
2:5a44c430b7ac | 3:5df0783706f7 |
---|---|
1 /* jQuery Drag and Drop Library for Rich Editors | |
2 * | |
3 * This library exists to provide a jQuery plugin that manages dragging | |
4 * and dropping of page assets into textareas and rich-text editors which use | |
5 * the iframe + designMode method. | |
6 * | |
7 * This plugin has a rather elaborate set of considerations based on common | |
8 * configurations of rich text editors and serious disparity in how browsers | |
9 * handle dragging and dropping assets into an iframe. | |
10 * | |
11 * The plugin scans an iframe with an embedded document with designMode enabled | |
12 * and tries to detect content that was injected by dragging and dropping | |
13 * within the browser. If it detects an injection of content, it attempts to | |
14 * conjure up an "editor representation" of the content based on the markup | |
15 * passed in. | |
16 * | |
17 * This works because links and images drop with their full HTML syntax in-tact | |
18 * across most browsers and platforms. This means we can set a timer on the | |
19 * iframe that scans for the insertion of the markup and replace it with | |
20 * another HTML snippet, as well as triggering actions inside the editor itself | |
21 * (this will perhaps be handled by a set of editor-specific plugins). | |
22 * | |
23 * Because of the mechanism of operation, it is expected that the iframe | |
24 * contain CSS which hides the markup dropped in to create the illusion of | |
25 * seamlessly dropping in the "editor representation" of the dropped item. | |
26 * | |
27 * It is expected that the implementer will be parsing the input text for | |
28 * the proper markup on the server side and making some decisions about how | |
29 * to parse and handle that markup on load and save. | |
30 * | |
31 * Of special interest is graceful degradation. There is no ideal graceful | |
32 * degradation path at this time. Every mainstream browser except IE 6 and | |
33 * IE 7 drop links and images into textareas with their href and src URIs, | |
34 * respectively, including querystrings. That means that the image or link | |
35 * url can be used for parsing, or a querystring included. But it won't work | |
36 * in Internet Explorer, and probably will require a server-side browser check | |
37 * in full blown implementations of the library system. | |
38 * | |
39 * Basic usage: | |
40 * | |
41 * $('a.my-class').dnd({targets: $('#my-iframe')}); | |
42 * | |
43 * Options: | |
44 * | |
45 * targets | |
46 * A jQuery object corresponding to the proper iframe(s) to enable dragging | |
47 * and dropping for. [Required] | |
48 * | |
49 * dropWrapper | |
50 * An html snippet to wrap the entire inserted content with in the editor | |
51 * representation (i.e. '<p class="foo"></p>') | |
52 * | |
53 * insertBefore: | |
54 * Markup to insert before drop (i.e. '<hr />') | |
55 * | |
56 * insertAfter: | |
57 * Markup to insert after drop (i.e. '<div class="clearfix"></div>') | |
58 * | |
59 * processedClass: | |
60 * The class to apply to links and images tagged as droppable. This class | |
61 * should have a style rule in the editor that sets display to 'none' for | |
62 * the best experience. | |
63 * | |
64 * idSelector: | |
65 * A callback that parses a unique id out of a droppable element. B y default | |
66 * this uses the id of the element, but one could parse out an ID based on | |
67 * any part of the URL, interior markup, etc. | |
68 * | |
69 * renderRepresentation: | |
70 * A callback that defines the mechanism for rendering a representation of | |
71 * the content. The default is currently essential useless. | |
72 * | |
73 * preprocessDrop: | |
74 * A callback that preprocesses the dropped snippet in the iframe, before | |
75 * replacing it. By default this uses a little logic to walk up the DOM | |
76 * tree to topmost parent of the place in the source where the item was | |
77 * dropped, and add the dropped element after that parent instead of | |
78 * inside it. | |
79 * | |
80 * postprocessDrop: | |
81 * A callback that postprocesses the iframe. | |
82 * | |
83 */ | |
84 | |
85 (function($) { | |
86 $.fn.dnd = function(opt) { | |
87 opt = $.extend({}, { | |
88 dropWrapper: '<p class="dnd-inserted"></p>', | |
89 insertBefore: '', | |
90 insertAfter: '', | |
91 processedClass: 'dnd-processed', | |
92 | |
93 processTargets: function(targets) { | |
94 return targets.each(function() { | |
95 $('head', $(this).contents()).append('<style type="text/css">.dnd-processed { display: none; }</style>'); | |
96 return this; | |
97 }); | |
98 }, | |
99 | |
100 // Must return a string | |
101 idSelector: function(element) { | |
102 if (!element.id) { | |
103 // @TODO sanitize output here | |
104 if ($(element).is('a')) { | |
105 return element.href; | |
106 } | |
107 if ($(element).is('img')) { | |
108 return element.src; | |
109 } | |
110 } | |
111 return element.id; | |
112 }, | |
113 | |
114 // @TODO: Target should be jQuery object | |
115 targets: $('iframe, textarea'), | |
116 | |
117 // Must return a string that DOES NOT share the id of any droppable item | |
118 // living outside the iframe | |
119 renderRepresentation: function(target, drop, representation_id) { | |
120 // Keep a counter of how many times this element was used | |
121 var count = $.data(target, representation_id +'_count'); | |
122 if (!count) { | |
123 count = 1; | |
124 } else { | |
125 count++; | |
126 } | |
127 $.data(target, representation_id +'_count', count); | |
128 return '<span id="dnd-' + representation_id +'-'+ count +'">' + representation_id + '</span>'; | |
129 }, | |
130 | |
131 // Back out markup to render in place after parent container | |
132 preprocessDrop: function(target, drop) { | |
133 var old_parent = false; | |
134 var element_id = ''; | |
135 | |
136 var parents = drop.parents(); | |
137 for (i=0; i < parents.length; i++) { | |
138 if ($(parents[i]).is('body')) { | |
139 element_id = $(drop).get(0).id; | |
140 $(old_parent).after(drop.clone()); | |
141 drop.remove(); | |
142 } | |
143 old_parent = parents[i]; | |
144 } | |
145 return $('#'+ element_id, $(target).contents()); | |
146 }, | |
147 | |
148 postprocessDrop: function(target, drop) { return; } | |
149 | |
150 }, opt); | |
151 | |
152 // Initialize plugin | |
153 var targets = opt.processTargets(opt.targets); | |
154 | |
155 // Process! | |
156 return this.each(function() { | |
157 | |
158 var element = this; | |
159 var representation_id = opt.idSelector(element); | |
160 | |
161 if (!element.id) { | |
162 element.id = element.tagName.toLowerCase() + '-' + representation_id; | |
163 } | |
164 | |
165 // Add some UI sugar and a special class | |
166 $(element) | |
167 .css('cursor', 'move') | |
168 .addClass(opt.processedClass); | |
169 | |
170 // We need to differentiate behavior based on the targets... I guess. | |
171 targets.each(function() { | |
172 if ($(this).is('iframe')) { | |
173 var target = this; | |
174 | |
175 // Watch the iframe for changes | |
176 t = setInterval(function() { | |
177 var match = $('#' + element.id, $(target).contents()); | |
178 if (match.length > 0) { | |
179 drop = opt.preprocessDrop(target, match); // Must return a jquery object | |
180 drop | |
181 .before(opt.insertBefore) | |
182 .after(opt.insertAfter) | |
183 .wrap(opt.dropWrapper) | |
184 .replaceWith(opt.renderRepresentation(target, drop, representation_id)); | |
185 opt.postprocessDrop(target, drop); | |
186 } | |
187 }, 100); | |
188 // @TODO track the timer with $.data() so we can clear it? | |
189 } else if ($(this).is('textarea')) { | |
190 console.log('@TODO handle textareas via.... regexp?'); | |
191 } | |
192 }); | |
193 }); | |
194 } | |
195 })(jQuery); |