/*global define*/
define([
'../../Core/defaultValue',
'../../Core/defined',
'../../Core/defineProperties',
'../../Core/DeveloperError',
'../../Core/Event',
'../../Core/wrapFunction',
'../../DataSources/CzmlDataSource',
'../../DataSources/GeoJsonDataSource',
'../../DataSources/KmlDataSource',
'../../Scene/GroundPrimitive',
'../getElement'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
wrapFunction,
CzmlDataSource,
GeoJsonDataSource,
KmlDataSource,
GroundPrimitive,
getElement) {
'use strict';
/**
* A mixin which adds default drag and drop support for CZML files to the Viewer widget.
* Rather than being called directly, this function is normally passed as
* a parameter to {@link Viewer#extend}, as shown in the example below.
* @exports viewerDragDropMixin
*
* @param {Viewer} viewer The viewer instance.
* @param {Object} [options] Object with the following properties:
* @param {Element|String} [options.dropTarget=viewer.container] The DOM element which will serve as the drop target.
* @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.
* @param {Boolean} [options.flyToOnDrop=true] When true, dropping files will fly to the data source once it is loaded.
* @param {Boolean} [options.clampToGround=true] When true, datasources are clamped to the ground.
* @param {DefaultProxy} [options.proxy] The proxy to be used for KML network links.
*
* @exception {DeveloperError} Element with id <options.dropTarget> does not exist in the document.
* @exception {DeveloperError} dropTarget is already defined by another mixin.
* @exception {DeveloperError} dropEnabled is already defined by another mixin.
* @exception {DeveloperError} dropError is already defined by another mixin.
* @exception {DeveloperError} clearOnDrop is already defined by another mixin.
*
* @example
* // Add basic drag and drop support and pop up an alert window on error.
* var viewer = new Cesium.Viewer('cesiumContainer');
* viewer.extend(Cesium.viewerDragDropMixin);
* viewer.dropError.addEventListener(function(viewerArg, source, error) {
* window.alert('Error processing ' + source + ':' + error);
* });
*/
function viewerDragDropMixin(viewer, options) {
//>>includeStart('debug', pragmas.debug);
if (!defined(viewer)) {
throw new DeveloperError('viewer is required.');
}
if (viewer.hasOwnProperty('dropTarget')) {
throw new DeveloperError('dropTarget is already defined by another mixin.');
}
if (viewer.hasOwnProperty('dropEnabled')) {
throw new DeveloperError('dropEnabled is already defined by another mixin.');
}
if (viewer.hasOwnProperty('dropError')) {
throw new DeveloperError('dropError is already defined by another mixin.');
}
if (viewer.hasOwnProperty('clearOnDrop')) {
throw new DeveloperError('clearOnDrop is already defined by another mixin.');
}
if (viewer.hasOwnProperty('flyToOnDrop')) {
throw new DeveloperError('flyToOnDrop is already defined by another mixin.');
}
//>>includeEnd('debug');
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//Local variables to be closed over by defineProperties.
var dropEnabled = true;
var flyToOnDrop = defaultValue(options.flyToOnDrop, true);
var dropError = new Event();
var clearOnDrop = defaultValue(options.clearOnDrop, true);
var dropTarget = defaultValue(options.dropTarget, viewer.container);
var clampToGround = defaultValue(options.clampToGround, true);
var proxy = options.proxy;
dropTarget = getElement(dropTarget);
defineProperties(viewer, {
/**
* Gets or sets the element to serve as the drop target.
* @memberof viewerDragDropMixin.prototype
* @type {Element}
*/
dropTarget : {
//TODO See https://github.com/AnalyticalGraphicsInc/cesium/issues/832
get : function() {
return dropTarget;
},
set : function(value) {
//>>includeStart('debug', pragmas.debug);
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
//>>includeEnd('debug');
unsubscribe(dropTarget, handleDrop);
dropTarget = value;
subscribe(dropTarget, handleDrop);
}
},
/**
* Gets or sets a value indicating if drag and drop support is enabled.
* @memberof viewerDragDropMixin.prototype
* @type {Element}
*/
dropEnabled : {
get : function() {
return dropEnabled;
},
set : function(value) {
if (value !== dropEnabled) {
if (value) {
subscribe(dropTarget, handleDrop);
} else {
unsubscribe(dropTarget, handleDrop);
}
dropEnabled = value;
}
}
},
/**
* Gets the event that will be raised when an error is encountered during drop processing.
* @memberof viewerDragDropMixin.prototype
* @type {Event}
*/
dropError : {
get : function() {
return dropError;
}
},
/**
* Gets or sets a value indicating if existing data sources should be cleared before adding the newly dropped sources.
* @memberof viewerDragDropMixin.prototype
* @type {Boolean}
*/
clearOnDrop : {
get : function() {
return clearOnDrop;
},
set : function(value) {
clearOnDrop = value;
}
},
/**
* Gets or sets a value indicating if the camera should fly to the data source after it is loaded.
* @memberof viewerDragDropMixin.prototype
* @type {Boolean}
*/
flyToOnDrop : {
get : function() {
return flyToOnDrop;
},
set : function(value) {
flyToOnDrop = value;
}
},
/**
* Gets or sets the proxy to be used for KML.
* @memberof viewerDragDropMixin.prototype
* @type {DefaultProxy}
*/
proxy : {
get : function() {
return proxy;
},
set : function(value) {
proxy = value;
}
},
/**
* Gets or sets a value indicating if the datasources should be clamped to the ground
* @memberof viewerDragDropMixin.prototype
* @type {Boolean}
*/
clampToGround : {
get : function() {
return clampToGround;
},
set : function(value) {
clampToGround = value;
}
}
});
function handleDrop(event) {
stop(event);
if (clearOnDrop) {
viewer.entities.removeAll();
viewer.dataSources.removeAll();
}
var files = event.dataTransfer.files;
var length = files.length;
for (var i = 0; i < length; i++) {
var file = files[i];
var reader = new FileReader();
reader.onload = createOnLoadCallback(viewer, file, proxy, clampToGround);
reader.onerror = createDropErrorCallback(viewer, file);
reader.readAsText(file);
}
}
//Enable drop by default;
subscribe(dropTarget, handleDrop);
//Wrap the destroy function to make sure all events are unsubscribed from
viewer.destroy = wrapFunction(viewer, viewer.destroy, function() {
viewer.dropEnabled = false;
});
//Specs need access to handleDrop
viewer._handleDrop = handleDrop;
}
function stop(event) {
event.stopPropagation();
event.preventDefault();
}
function unsubscribe(dropTarget, handleDrop) {
var currentTarget = dropTarget;
if (defined(currentTarget)) {
currentTarget.removeEventListener('drop', handleDrop, false);
currentTarget.removeEventListener('dragenter', stop, false);
currentTarget.removeEventListener('dragover', stop, false);
currentTarget.removeEventListener('dragexit', stop, false);
}
}
function subscribe(dropTarget, handleDrop) {
dropTarget.addEventListener('drop', handleDrop, false);
dropTarget.addEventListener('dragenter', stop, false);
dropTarget.addEventListener('dragover', stop, false);
dropTarget.addEventListener('dragexit', stop, false);
}
function createOnLoadCallback(viewer, file, proxy, clampToGround) {
var scene = viewer.scene;
return function(evt) {
var fileName = file.name;
try {
var loadPromise;
if (/\.czml$/i.test(fileName)) {
loadPromise = CzmlDataSource.load(JSON.parse(evt.target.result), {
sourceUri : fileName
});
} else if (/\.geojson$/i.test(fileName) || /\.json$/i.test(fileName) || /\.topojson$/i.test(fileName)) {
loadPromise = GeoJsonDataSource.load(JSON.parse(evt.target.result), {
sourceUri : fileName,
clampToGround : clampToGround
});
} else if (/\.(kml|kmz)$/i.test(fileName)) {
loadPromise = KmlDataSource.load(file, {
sourceUri : fileName,
proxy : proxy,
camera : scene.camera,
canvas : scene.canvas
});
} else {
viewer.dropError.raiseEvent(viewer, fileName, 'Unrecognized file: ' + fileName);
return;
}
if (defined(loadPromise)) {
viewer.dataSources.add(loadPromise).then(function(dataSource) {
if (viewer.flyToOnDrop) {
viewer.flyTo(dataSource);
}
}).otherwise(function(error) {
viewer.dropError.raiseEvent(viewer, fileName, error);
});
}
} catch (error) {
viewer.dropError.raiseEvent(viewer, fileName, error);
}
};
}
function createDropErrorCallback(viewer, file) {
return function(evt) {
viewer.dropError.raiseEvent(viewer, file.name, evt.target.error);
};
}
return viewerDragDropMixin;
});