annotate js/jquery.draganddrop.js @ 47:cbfe386cb51b

Add a function to refresh opened libraries. The source URL of each libraries is now tracked, which allows for auto refreshing the libraries based on various events. The obvious use case is to refresh the library when an atom has been added to Scald, for example via a Popups dialog.
author Franck Deroche <defr@ows.fr>
date Mon, 15 Feb 2010 14:08:04 +0000
parents e71df38143d1
children f817d2a5cc0a
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@25 165 // Data for custom event
eads@25 166 var event_data = {'drop': element, 'representation_id': representation_id};
eads@25 167
eads@20 168 // We need to differentiate behavior based on the targets
eads@12 169 targets.each(function() {
eads@16 170 var target = this, $target = $(target);
eads@16 171 if ($target.is('iframe')) {
eads@20 172 $(element).addClass(opt.iframeTargetClass);
eads@20 173 $(element).click(function() {
eads@20 174 opt.processIframeClick.call(target, element, representation_id);
eads@25 175 $(target).trigger('dnd_drop', event_data);
eads@20 176 });
eads@16 177 } else if ($target.is('textarea')) {
eads@20 178 $(element).addClass(opt.textareaTargetClass);
eads@20 179 $(element).click(function() {
eads@20 180 opt.processTextAreaClick.call(target, element, representation_id);
eads@25 181 $(target).trigger('dnd_drop', event_data);
eads@16 182 });
eads@12 183 }
eads@12 184 });
eads@3 185 }
eads@3 186 });
eads@11 187 };
eads@3 188 })(jQuery);