annotate js/jquery.draganddrop.js @ 24:4f58fa0a9a6d

Cleaned up some library behavior, changed editor representation cache to track a title and a body for representations, to allow a little more dynamic composition.
author David Eads <eads@chicagotech.org>
date Wed, 11 Mar 2009 01:47:57 -0500
parents 89fe0aca43d4
children e71df38143d1
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@24 47 * processedClass:
eads@20 48 * A class to add to draggable items processed by DnD.
eads@20 49 *
eads@24 50 * droppedClass:
eads@24 51 * A class to add to images that are dropped (by default, this is used to
eads@24 52 * ensure that a successful drop manifests the editor representation only
eads@24 53 * once.)
eads@24 54 *
eads@12 55 * interval:
eads@12 56 * How often to check the iframe for a drop, in milliseconds.
eads@12 57 *
eads@12 58 * Usage notes:
eads@12 59 *
eads@16 60 * Due to cross browser flakiness, there are many many limitations on what is
eads@16 61 * possible in terms of dragging and dropping to DesignMode enabled iframes.
eads@12 62 *
eads@16 63 * To make this work, your "droppable" elements must be image tags. No ifs, ands,
eads@16 64 * or buts: image tags have the best cross browser behavior when dragging.
eads@12 65 *
eads@20 66 * When DnD is invoked, it begins a timer which periodically checks your
eads@16 67 * editor iframe. If an image is dropped in, DnD takes the element and
eads@16 68 * attempts to parse it for a unique ID which you can use to do a lookup for
eads@16 69 * an "editor representation." If an editor representation is found,
eads@16 70 * typically the image is replaced with the representation snippet, but you are
eads@16 71 * free to do whatever you want.
eads@12 72 *
eads@16 73 * Because of browser limitations, the safest way to parse the element is to
eads@16 74 * look at the img's src attribute and use some or all of the image URL.
eads@16 75 * In my experience, the best way to deal with this is simply to parse out
eads@16 76 * generate a query string in the src attribute server side that corresponds
eads@16 77 * to the proper representation ID.
eads@12 78 *
eads@16 79 * If the target is not an iframe but a textarea, DnD provides a very minimal
eads@16 80 * system for clicking what would otherwise be dragged to insert markup into
eads@16 81 * the textarea.
eads@16 82 *
eads@16 83 * Because DnD spawns so many timers, they are stored in a $.data element
eads@16 84 * attached to the parent document body. Implementers should consider clearing
eads@16 85 * the timers when building systems such as dynamic searches or paging
eads@16 86 * functionality to ensure memory usage and performance remains stable.
eads@3 87 */
eads@3 88
eads@3 89 (function($) {
eads@3 90 $.fn.dnd = function(opt) {
eads@3 91 opt = $.extend({}, {
eads@15 92 interval: 250,
eads@16 93 targets: $('iframe, textarea'),
eads@3 94 processTargets: function(targets) {
eads@3 95 return targets.each(function() {
eads@15 96 $('head', $(this).contents()).append('<style type="text/css">img { display: none; } img.dnd-dropped {display: block; }</style>');
eads@3 97 return this;
eads@3 98 });
eads@3 99 },
eads@3 100 idSelector: function(element) {
eads@12 101 if ($(element).is('img')) {
eads@16 102 return element.src;
eads@3 103 }
eads@16 104 return false;
eads@3 105 },
eads@20 106 processIframeDrop: function(drop, id_selector) {
eads@20 107 var representation_id = opt.idSelector(drop);
eads@24 108 $(drop).replaceWith(representation_id).wrap('<p class="'+ opt.droppedClass +'"></p>');
eads@3 109 },
eads@20 110 processTextAreaClick: function(target, clicked, representation_id) {
eads@16 111 var snippet = '<div><img src="'+ representation_id +'" /></div>';
eads@16 112 $(target).replaceSelection(snippet, true);
eads@16 113 e.preventDefault();
eads@20 114 },
eads@20 115 processIframeClick: function (target, clicked, representation_id) { return true; },
eads@20 116
eads@20 117 iframeTargetClass: 'dnd-iframe-target',
eads@20 118 textareaTargetClass: 'dnd-textarea-target',
eads@24 119 processedClass: 'dnd-processed',
eads@24 120 droppedClass: 'dnd-dropped'
eads@3 121
eads@3 122 }, opt);
eads@3 123
eads@3 124 // Initialize plugin
eads@3 125 var targets = opt.processTargets(opt.targets);
eads@3 126
eads@20 127 // Watch iframes for changes
eads@20 128 $(targets).filter('iframe').each(function() {
eads@20 129 var target = this;
eads@20 130 var t = setInterval(function() {
eads@24 131 $('img:not(.'+ opt.droppedClass +')', $(target).contents()).each(function() {
eads@20 132 opt.processIframeDrop.call(target, this, opt.idSelector);
eads@24 133 var data = {'drop': this, 'representation_id': opt.idSelector(this)};
eads@24 134
eads@24 135 // Trigger event in container window
eads@24 136 $(target).trigger('dnd_drop', data);
eads@24 137
eads@24 138 // Trigger event in iframe
eads@24 139 $(this).trigger('dnd_drop', data);
eads@20 140 });
eads@20 141 }, opt.interval);
eads@20 142
eads@20 143 // Track current active timers -- developers working with DnD
eads@20 144 // can implement their own garbage collection for specific
eads@20 145 // interactions, such as paging or live search.
eads@20 146 var data = $(document).data('dnd_timers');
eads@20 147 if (!data) { data = {}; }
eads@20 148 data[target.id] = t;
eads@20 149 $(document).data('dnd_timers', data);
eads@20 150 });
eads@20 151
eads@20 152 // Process each draggable element
eads@3 153 return this.each(function() {
eads@12 154 if ($(this).is('img')) {
eads@16 155 var element = this, $element = $(element);
eads@12 156 var representation_id = opt.idSelector(element);
eads@3 157
eads@12 158 if (!representation_id) {
eads@12 159 return this;
eads@12 160 };
eads@12 161
eads@16 162 // Add a special class
eads@20 163 $(element).addClass(opt.processedClass);
eads@12 164
eads@20 165 // We need to differentiate behavior based on the targets
eads@12 166 targets.each(function() {
eads@16 167 var target = this, $target = $(target);
eads@16 168 if ($target.is('iframe')) {
eads@20 169 $(element).addClass(opt.iframeTargetClass);
eads@20 170 $(element).click(function() {
eads@20 171 opt.processIframeClick.call(target, element, representation_id);
eads@20 172 });
eads@16 173 } else if ($target.is('textarea')) {
eads@20 174 $(element).addClass(opt.textareaTargetClass);
eads@20 175 $(element).click(function() {
eads@20 176 opt.processTextAreaClick.call(target, element, representation_id);
eads@16 177 });
eads@12 178 }
eads@12 179 });
eads@3 180 }
eads@3 181 });
eads@11 182 };
eads@3 183 })(jQuery);