viewerDragDropMixin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import defaultValue from '../../Core/defaultValue.js';
  2. import defined from '../../Core/defined.js';
  3. import defineProperties from '../../Core/defineProperties.js';
  4. import DeveloperError from '../../Core/DeveloperError.js';
  5. import Event from '../../Core/Event.js';
  6. import wrapFunction from '../../Core/wrapFunction.js';
  7. import CzmlDataSource from '../../DataSources/CzmlDataSource.js';
  8. import GeoJsonDataSource from '../../DataSources/GeoJsonDataSource.js';
  9. import KmlDataSource from '../../DataSources/KmlDataSource.js';
  10. import getElement from '../getElement.js';
  11. /**
  12. * A mixin which adds default drag and drop support for CZML files to the Viewer widget.
  13. * Rather than being called directly, this function is normally passed as
  14. * a parameter to {@link Viewer#extend}, as shown in the example below.
  15. * @exports viewerDragDropMixin
  16. * @namespace
  17. * @param {Viewer} viewer The viewer instance.
  18. * @param {Object} [options] Object with the following properties:
  19. * @param {Element|String} [options.dropTarget=viewer.container] The DOM element which will serve as the drop target.
  20. * @param {Boolean} [options.clearOnDrop=true] When true, dropping files will clear all existing data sources first, when false, new data sources will be loaded after the existing ones.
  21. * @param {Boolean} [options.flyToOnDrop=true] When true, dropping files will fly to the data source once it is loaded.
  22. * @param {Boolean} [options.clampToGround=true] When true, datasources are clamped to the ground.
  23. * @param {DefaultProxy} [options.proxy] The proxy to be used for KML network links.
  24. *
  25. * @exception {DeveloperError} Element with id <options.dropTarget> does not exist in the document.
  26. * @exception {DeveloperError} dropTarget is already defined by another mixin.
  27. * @exception {DeveloperError} dropEnabled is already defined by another mixin.
  28. * @exception {DeveloperError} dropError is already defined by another mixin.
  29. * @exception {DeveloperError} clearOnDrop is already defined by another mixin.
  30. *
  31. * @example
  32. * // Add basic drag and drop support and pop up an alert window on error.
  33. * var viewer = new Cesium.Viewer('cesiumContainer');
  34. * viewer.extend(Cesium.viewerDragDropMixin);
  35. * viewer.dropError.addEventListener(function(viewerArg, source, error) {
  36. * window.alert('Error processing ' + source + ':' + error);
  37. * });
  38. */
  39. function viewerDragDropMixin(viewer, options) {
  40. //>>includeStart('debug', pragmas.debug);
  41. if (!defined(viewer)) {
  42. throw new DeveloperError('viewer is required.');
  43. }
  44. if (viewer.hasOwnProperty('dropTarget')) {
  45. throw new DeveloperError('dropTarget is already defined by another mixin.');
  46. }
  47. if (viewer.hasOwnProperty('dropEnabled')) {
  48. throw new DeveloperError('dropEnabled is already defined by another mixin.');
  49. }
  50. if (viewer.hasOwnProperty('dropError')) {
  51. throw new DeveloperError('dropError is already defined by another mixin.');
  52. }
  53. if (viewer.hasOwnProperty('clearOnDrop')) {
  54. throw new DeveloperError('clearOnDrop is already defined by another mixin.');
  55. }
  56. if (viewer.hasOwnProperty('flyToOnDrop')) {
  57. throw new DeveloperError('flyToOnDrop is already defined by another mixin.');
  58. }
  59. //>>includeEnd('debug');
  60. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  61. //Local variables to be closed over by defineProperties.
  62. var dropEnabled = true;
  63. var flyToOnDrop = defaultValue(options.flyToOnDrop, true);
  64. var dropError = new Event();
  65. var clearOnDrop = defaultValue(options.clearOnDrop, true);
  66. var dropTarget = defaultValue(options.dropTarget, viewer.container);
  67. var clampToGround = defaultValue(options.clampToGround, true);
  68. var proxy = options.proxy;
  69. dropTarget = getElement(dropTarget);
  70. defineProperties(viewer, {
  71. /**
  72. * Gets or sets the element to serve as the drop target.
  73. * @memberof viewerDragDropMixin.prototype
  74. * @type {Element}
  75. */
  76. dropTarget : {
  77. //TODO See https://github.com/AnalyticalGraphicsInc/cesium/issues/832
  78. get : function() {
  79. return dropTarget;
  80. },
  81. set : function(value) {
  82. //>>includeStart('debug', pragmas.debug);
  83. if (!defined(value)) {
  84. throw new DeveloperError('value is required.');
  85. }
  86. //>>includeEnd('debug');
  87. unsubscribe(dropTarget, handleDrop);
  88. dropTarget = value;
  89. subscribe(dropTarget, handleDrop);
  90. }
  91. },
  92. /**
  93. * Gets or sets a value indicating if drag and drop support is enabled.
  94. * @memberof viewerDragDropMixin.prototype
  95. * @type {Element}
  96. */
  97. dropEnabled : {
  98. get : function() {
  99. return dropEnabled;
  100. },
  101. set : function(value) {
  102. if (value !== dropEnabled) {
  103. if (value) {
  104. subscribe(dropTarget, handleDrop);
  105. } else {
  106. unsubscribe(dropTarget, handleDrop);
  107. }
  108. dropEnabled = value;
  109. }
  110. }
  111. },
  112. /**
  113. * Gets the event that will be raised when an error is encountered during drop processing.
  114. * @memberof viewerDragDropMixin.prototype
  115. * @type {Event}
  116. */
  117. dropError : {
  118. get : function() {
  119. return dropError;
  120. }
  121. },
  122. /**
  123. * Gets or sets a value indicating if existing data sources should be cleared before adding the newly dropped sources.
  124. * @memberof viewerDragDropMixin.prototype
  125. * @type {Boolean}
  126. */
  127. clearOnDrop : {
  128. get : function() {
  129. return clearOnDrop;
  130. },
  131. set : function(value) {
  132. clearOnDrop = value;
  133. }
  134. },
  135. /**
  136. * Gets or sets a value indicating if the camera should fly to the data source after it is loaded.
  137. * @memberof viewerDragDropMixin.prototype
  138. * @type {Boolean}
  139. */
  140. flyToOnDrop : {
  141. get : function() {
  142. return flyToOnDrop;
  143. },
  144. set : function(value) {
  145. flyToOnDrop = value;
  146. }
  147. },
  148. /**
  149. * Gets or sets the proxy to be used for KML.
  150. * @memberof viewerDragDropMixin.prototype
  151. * @type {DefaultProxy}
  152. */
  153. proxy : {
  154. get : function() {
  155. return proxy;
  156. },
  157. set : function(value) {
  158. proxy = value;
  159. }
  160. },
  161. /**
  162. * Gets or sets a value indicating if the datasources should be clamped to the ground
  163. * @memberof viewerDragDropMixin.prototype
  164. * @type {Boolean}
  165. */
  166. clampToGround : {
  167. get : function() {
  168. return clampToGround;
  169. },
  170. set : function(value) {
  171. clampToGround = value;
  172. }
  173. }
  174. });
  175. function handleDrop(event) {
  176. stop(event);
  177. if (clearOnDrop) {
  178. viewer.entities.removeAll();
  179. viewer.dataSources.removeAll();
  180. }
  181. var files = event.dataTransfer.files;
  182. var length = files.length;
  183. for (var i = 0; i < length; i++) {
  184. var file = files[i];
  185. var reader = new FileReader();
  186. reader.onload = createOnLoadCallback(viewer, file, proxy, clampToGround);
  187. reader.onerror = createDropErrorCallback(viewer, file);
  188. reader.readAsText(file);
  189. }
  190. }
  191. //Enable drop by default;
  192. subscribe(dropTarget, handleDrop);
  193. //Wrap the destroy function to make sure all events are unsubscribed from
  194. viewer.destroy = wrapFunction(viewer, viewer.destroy, function() {
  195. viewer.dropEnabled = false;
  196. });
  197. //Specs need access to handleDrop
  198. viewer._handleDrop = handleDrop;
  199. }
  200. function stop(event) {
  201. event.stopPropagation();
  202. event.preventDefault();
  203. }
  204. function unsubscribe(dropTarget, handleDrop) {
  205. var currentTarget = dropTarget;
  206. if (defined(currentTarget)) {
  207. currentTarget.removeEventListener('drop', handleDrop, false);
  208. currentTarget.removeEventListener('dragenter', stop, false);
  209. currentTarget.removeEventListener('dragover', stop, false);
  210. currentTarget.removeEventListener('dragexit', stop, false);
  211. }
  212. }
  213. function subscribe(dropTarget, handleDrop) {
  214. dropTarget.addEventListener('drop', handleDrop, false);
  215. dropTarget.addEventListener('dragenter', stop, false);
  216. dropTarget.addEventListener('dragover', stop, false);
  217. dropTarget.addEventListener('dragexit', stop, false);
  218. }
  219. function createOnLoadCallback(viewer, file, proxy, clampToGround) {
  220. var scene = viewer.scene;
  221. return function(evt) {
  222. var fileName = file.name;
  223. try {
  224. var loadPromise;
  225. if (/\.czml$/i.test(fileName)) {
  226. loadPromise = CzmlDataSource.load(JSON.parse(evt.target.result), {
  227. sourceUri : fileName
  228. });
  229. } else if (/\.geojson$/i.test(fileName) || /\.json$/i.test(fileName) || /\.topojson$/i.test(fileName)) {
  230. loadPromise = GeoJsonDataSource.load(JSON.parse(evt.target.result), {
  231. sourceUri : fileName,
  232. clampToGround : clampToGround
  233. });
  234. } else if (/\.(kml|kmz)$/i.test(fileName)) {
  235. loadPromise = KmlDataSource.load(file, {
  236. sourceUri : fileName,
  237. proxy : proxy,
  238. camera : scene.camera,
  239. canvas : scene.canvas,
  240. clampToGround: clampToGround
  241. });
  242. } else {
  243. viewer.dropError.raiseEvent(viewer, fileName, 'Unrecognized file: ' + fileName);
  244. return;
  245. }
  246. if (defined(loadPromise)) {
  247. viewer.dataSources.add(loadPromise).then(function(dataSource) {
  248. if (viewer.flyToOnDrop) {
  249. viewer.flyTo(dataSource);
  250. }
  251. }).otherwise(function(error) {
  252. viewer.dropError.raiseEvent(viewer, fileName, error);
  253. });
  254. }
  255. } catch (error) {
  256. viewer.dropError.raiseEvent(viewer, fileName, error);
  257. }
  258. };
  259. }
  260. function createDropErrorCallback(viewer, file) {
  261. return function(evt) {
  262. viewer.dropError.raiseEvent(viewer, file.name, evt.target.error);
  263. };
  264. }
  265. export default viewerDragDropMixin;