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