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