devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
708 lines (620 loc) • 21.4 kB
JavaScript
"use strict";
var $ = require("../core/renderer"),
window = require("../core/utils/window").getWindow(),
eventsEngine = require("../events/core/events_engine"),
Promise = require("../core/polyfills/promise"),
fromPromise = require("../core/utils/deferred").fromPromise,
registerComponent = require("../core/component_registrator"),
errors = require("./widget/ui.errors"),
devices = require("../core/devices"),
Widget = require("./widget/ui.widget"),
inflector = require("../core/utils/inflector"),
each = require("../core/utils/iterator").each,
extend = require("../core/utils/extend").extend,
inArray = require("../core/utils/array").inArray,
isNumeric = require("../core/utils/type").isNumeric,
eventUtils = require("../events/utils"),
pointerEvents = require("../events/pointer"),
config = require("../core/config"),
wrapToArray = require("../core/utils/array").wrapToArray;
// NOTE external urls must have protocol explicitly specified (because inside Cordova package the protocol is "file:")
var PROVIDERS = {
googleStatic: require("./map/provider.google_static"),
google: require("./map/provider.dynamic.google"),
bing: require("./map/provider.dynamic.bing")
};
var MAP_CLASS = "dx-map",
MAP_CONTAINER_CLASS = "dx-map-container",
MAP_SHIELD_CLASS = "dx-map-shield",
NATIVE_CLICK_CLASS = "dx-native-click";
/**
* @name dxmap
* @publicName dxMap
* @inherits Widget
* @module ui/map
* @export default
*/
var Map = Widget.inherit({
_getDefaultOptions: function _getDefaultOptions() {
return extend(this.callBase(), {
/**
* @name dxMapOptions.bounds
* @publicName bounds
* @type object
* @hidden
*/
bounds: {
/**
* @name dxMapOptions.bounds.northEast
* @publicName northEast
* @type object|string|Array<object>
* @default null
* @hidden
*/
/**
* @name dxMapOptions.bounds.northEast.lat
* @publicName lat
* @type number
* @hidden
*/
/**
* @name dxMapOptions.bounds.northEast.lng
* @publicName lng
* @type number
* @hidden
*/
northEast: null,
/**
* @name dxMapOptions.bounds.southWest
* @publicName southWest
* @type object|string|Array<object>
* @default null
* @hidden
*/
/**
* @name dxMapOptions.bounds.southWest.lat
* @publicName lat
* @type number
* @hidden
*/
/**
* @name dxMapOptions.bounds.southWest.lng
* @publicName lng
* @type number
* @hidden
*/
southWest: null
},
/**
* @pseudo MapLocationType
* @type Object|string|Array<number>
*/
/**
* @name MapLocation
* @publicName MapLocation
* @hidden
*/
/**
* @name MapLocation.lat
* @publicName lat
* @type number
* @default 0
*/
/**
* @name MapLocation.lng
* @publicName lng
* @type number
* @default 0
*/
/**
* @name dxMapOptions.center
* @publicName center
* @extends MapLocationType
* @inherits MapLocation
*/
center: {
lat: 0,
lng: 0
},
/**
* @name dxMapOptions.zoom
* @publicName zoom
* @type number
* @default 1
*/
zoom: 1,
/**
* @name dxMapOptions.width
* @publicName width
* @inheritdoc
* @default 300
*/
width: 300,
/**
* @name dxMapOptions.height
* @publicName height
* @inheritdoc
* @default 300
*/
height: 300,
/**
* @name dxMapOptions.type
* @publicName type
* @type Enums.GeoMapType
* @default "roadmap"
*/
type: "roadmap",
/**
* @name dxMapOptions.provider
* @publicName provider
* @type Enums.GeoMapProvider
* @default "google"
*/
provider: "google",
/**
* @name dxMapOptions.autoAdjust
* @publicName autoAdjust
* @type boolean
* @default true
*/
autoAdjust: true,
/**
* @name dxMapOptions.markers
* @publicName markers
* @type Array<Object>
*/
/**
* @name dxMapOptions.markers.location
* @publicName location
* @extends MapLocationType
* @inherits MapLocation
*/
/**
* @name dxMapOptions.markers.tooltip
* @publicName tooltip
* @type string|object
*/
/**
* @name dxMapOptions.markers.tooltip.text
* @publicName text
* @type string
*/
/**
* @name dxMapOptions.markers.tooltip.isShown
* @publicName isShown
* @type boolean
* @default false
*/
/**
* @name dxMapOptions.markers.onClick
* @publicName onClick
* @type function
*/
/**
* @name dxMapOptions.markers.iconSrc
* @publicName iconSrc
* @type string
*/
markers: [],
/**
* @name dxMapOptions.markerIconSrc
* @publicName markerIconSrc
* @type string
*/
markerIconSrc: null,
/**
* @name dxMapOptions.onMarkerAdded
* @publicName onMarkerAdded
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 options:object
* @type_function_param1_field5 originalMarker:object
* @action
*/
onMarkerAdded: null,
/**
* @name dxMapOptions.onMarkerRemoved
* @publicName onMarkerRemoved
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 options:object
* @action
*/
onMarkerRemoved: null,
/**
* @name dxMapOptions.routes
* @publicName routes
* @type Array<Object>
*/
/**
* @name dxMapOptions.routes.locations
* @publicName locations
* @extends MapLocationType
* @inherits MapLocation
* @type Array<Object>
*/
/**
* @name dxMapOptions.routes.mode
* @publicName mode
* @type Enums.GeoMapRouteMode
* @default 'driving'
*/
/**
* @name dxMapOptions.routes.color
* @publicName color
* @type string
* @default '#0000FF'
*/
/**
* @name dxMapOptions.routes.weight
* @publicName weight
* @type number
* @default 5
*/
/**
* @name dxMapOptions.routes.opacity
* @publicName opacity
* @type number
* @default 0.5
*/
routes: [],
/**
* @name dxMapOptions.onRouteAdded
* @publicName onRouteAdded
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 options:object
* @type_function_param1_field5 originalRoute:object
* @action
*/
onRouteAdded: null,
/**
* @name dxMapOptions.onRouteRemoved
* @publicName onRouteRemoved
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 options:object
* @action
*/
onRouteRemoved: null,
/**
* @name dxMapOptions.key
* @publicName key
* @type string|object
* @default ""
*/
key: {
/**
* @name dxMapOptions.key.bing
* @publicName bing
* @type string
* @default ""
*/
bing: "",
/**
* @name dxMapOptions.key.google
* @publicName google
* @type string
* @default ""
*/
google: "",
/**
* @name dxMapOptions.key.googleStatic
* @publicName googleStatic
* @type string
* @default ""
*/
googleStatic: ""
},
/**
* @name dxMapOptions.controls
* @publicName controls
* @default false
* @type boolean
*/
controls: false,
/**
* @name dxMapOptions.onReady
* @publicName onReady
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 originalMap:object
* @action
*/
onReady: null,
/**
* @name dxMapOptions.onContentReady
* @publicName onContentReady
* @hidden true
* @action
*/
// for internal use only
onUpdated: null,
/**
* @name dxMapOptions.onClick
* @publicName onClick
* @type function(e)|string
* @extends Action
* @type_function_param1 e:object
* @type_function_param1_field4 location:object
* @type_function_param1_field5 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field6 event:event
* @action
*/
onClick: null
});
},
_defaultOptionsRules: function _defaultOptionsRules() {
return this.callBase().concat([{
device: function device() {
return devices.real().deviceType === "desktop" && !devices.isSimulator();
},
options: {
/**
* @name dxMapOptions.focusStateEnabled
* @publicName focusStateEnabled
* @type boolean
* @default true @for desktop
* @inheritdoc
*/
focusStateEnabled: true
}
}]);
},
_init: function _init() {
this.callBase();
this.$element().addClass(MAP_CLASS).addClass(NATIVE_CLICK_CLASS);
this._lastAsyncAction = Promise.resolve();
this._checkOption("provider");
this._checkOption("markers");
this._checkOption("routes");
this._initContainer();
this._grabEvents();
this._rendered = {};
},
_checkOption: function _checkOption(option) {
var value = this.option(option);
if (option === "markers" && !Array.isArray(value)) {
throw errors.Error("E1022");
}
if (option === "routes" && !Array.isArray(value)) {
throw errors.Error("E1023");
}
},
_initContainer: function _initContainer() {
this._$container = $("<div>").addClass(MAP_CONTAINER_CLASS);
this.$element().append(this._$container);
},
_grabEvents: function _grabEvents() {
var eventName = eventUtils.addNamespace(pointerEvents.down, this.NAME);
eventsEngine.on(this.$element(), eventName, this._cancelEvent.bind(this));
},
_cancelEvent: function _cancelEvent(e) {
var cancelByProvider = this._provider && this._provider.isEventsCanceled() && !this.option("disabled");
if (!config.designMode && cancelByProvider) {
e.stopPropagation();
}
},
_saveRendered: function _saveRendered(option) {
var value = this.option(option);
this._rendered[option] = value.slice();
},
_render: function _render() {
this.callBase();
this._renderShield();
this._saveRendered("markers");
this._saveRendered("routes");
this._provider = new PROVIDERS[this.option("provider")](this, this._$container);
this._queueAsyncAction("render", this._rendered.markers, this._rendered.routes);
},
_renderShield: function _renderShield() {
var $shield,
DevExpress = window.DevExpress;
if (DevExpress && DevExpress.designMode || this.option("disabled")) {
$shield = $("<div>").addClass(MAP_SHIELD_CLASS);
this.$element().append($shield);
} else {
$shield = this.$element().find("." + MAP_SHIELD_CLASS);
$shield.remove();
}
},
_clean: function _clean() {
this._cleanFocusState();
if (this._provider) {
this._provider.clean();
}
this._provider = null;
this._lastAsyncAction = Promise.resolve();
this.setOptionSilent("bounds", { northEast: null, southWest: null });
delete this._suppressAsyncAction;
},
_optionChanged: function _optionChanged(args) {
var name = args.name;
if (this._cancelOptionChange) {
return;
}
var changeBag = this._optionChangeBag;
this._optionChangeBag = null;
switch (name) {
case "disabled":
this._renderShield();
this.callBase(args);
break;
case "width":
case "height":
this.callBase(args);
this._dimensionChanged();
break;
case "provider":
this._suppressAsyncAction = true;
this._invalidate();
break;
case "key":
errors.log("W1001");
break;
case "bounds":
this._queueAsyncAction("updateBounds");
break;
case "center":
this._queueAsyncAction("updateCenter");
break;
case "zoom":
this._queueAsyncAction("updateZoom");
break;
case "type":
this._queueAsyncAction("updateMapType");
break;
case "controls":
this._queueAsyncAction("updateControls", this._rendered.markers, this._rendered.routes);
break;
case "autoAdjust":
this._queueAsyncAction("adjustViewport");
break;
case "markers":
case "routes":
this._checkOption(name);
var prevValue = this._rendered[name];
this._saveRendered(name);
this._queueAsyncAction("update" + inflector.titleize(name), changeBag ? changeBag.removed : prevValue, changeBag ? changeBag.added : this._rendered[name]).then(function (result) {
if (changeBag) {
changeBag.resolve(result);
}
});
break;
case "markerIconSrc":
this._queueAsyncAction("updateMarkers", this._rendered.markers, this._rendered.markers);
break;
case "onReady":
case "onUpdated":
case "onMarkerAdded":
case "onMarkerRemoved":
case "onRouteAdded":
case "onRouteRemoved":
case "onClick":
break;
default:
this.callBase.apply(this, arguments);
}
},
_visibilityChanged: function _visibilityChanged(visible) {
if (visible) {
this._dimensionChanged();
}
},
_dimensionChanged: function _dimensionChanged() {
this._queueAsyncAction("updateDimensions");
},
_queueAsyncAction: function _queueAsyncAction(name) {
var options = [].slice.call(arguments).slice(1),
isActionSuppressed = this._suppressAsyncAction;
this._lastAsyncAction = this._lastAsyncAction.then(function () {
if (!this._provider || isActionSuppressed) {
///#DEBUG
this._asyncActionSuppressed = true;
///#ENDDEBUG
return Promise.resolve();
}
return this._provider[name].apply(this._provider, options).then(function (result) {
result = wrapToArray(result);
var mapRefreshed = result[0];
if (mapRefreshed) {
this._triggerReadyAction();
}
///#DEBUG
if (!mapRefreshed && name !== "clean") {
this._triggerUpdateAction();
}
///#ENDDEBUG
return result[1];
}.bind(this));
}.bind(this));
return this._lastAsyncAction;
},
_triggerReadyAction: function _triggerReadyAction() {
this._createActionByOption("onReady")({ originalMap: this._provider.map() });
},
_triggerUpdateAction: function _triggerUpdateAction() {
this._createActionByOption("onUpdated")();
},
// TODO: move this ability to component?
setOptionSilent: function setOptionSilent(name, value) {
this._cancelOptionChange = true;
this.option(name, value);
this._cancelOptionChange = false;
},
/**
* @name dxmapmethods.addmarker
* @publicName addMarker(markerOptions)
* @param1 markerOptions:Object|Array<Object>
* @return Promise<Object>
*/
addMarker: function addMarker(marker) {
return this._addFunction("markers", marker);
},
/**
* @name dxmapmethods.removemarker
* @publicName removeMarker(marker)
* @param1 marker:Object|number|Array<Object>
* @return Promise<void>
*/
removeMarker: function removeMarker(marker) {
return this._removeFunction("markers", marker);
},
/**
* @name dxmapmethods.addroute
* @publicName addRoute(routeOptions)
* @param1 options:object|Array<Object>
* @return Promise<Object>
*/
addRoute: function addRoute(route) {
return this._addFunction("routes", route);
},
/**
* @name dxmapmethods.removeroute
* @publicName removeRoute(route)
* @param1 route:object|number|Array<Object>
* @return Promise<void>
*/
removeRoute: function removeRoute(route) {
return this._removeFunction("routes", route);
},
_addFunction: function _addFunction(optionName, addingValue) {
var optionValue = this.option(optionName),
addingValues = wrapToArray(addingValue);
optionValue.push.apply(optionValue, addingValues);
return this._partialArrayOptionChange(optionName, optionValue, addingValues, []);
},
_removeFunction: function _removeFunction(optionName, removingValue) {
var optionValue = this.option(optionName),
removingValues = wrapToArray(removingValue);
each(removingValues, function (removingIndex, removingValue) {
var index = isNumeric(removingValue) ? removingValue : inArray(removingValue, optionValue);
if (index !== -1) {
var removing = optionValue.splice(index, 1)[0];
removingValues.splice(removingIndex, 1, removing);
} else {
throw errors.log("E1021", inflector.titleize(optionName.substring(0, optionName.length - 1)), removingValue);
}
});
return this._partialArrayOptionChange(optionName, optionValue, [], removingValues);
},
_partialArrayOptionChange: function _partialArrayOptionChange(optionName, optionValue, addingValues, removingValues) {
return fromPromise(new Promise(function (resolve) {
this._optionChangeBag = {
resolve: resolve,
added: addingValues,
removed: removingValues
};
this.option(optionName, optionValue);
}.bind(this)).then(function (result) {
return result && result.length === 1 ? result[0] : result;
}), this);
}
});
registerComponent("dxMap", Map);
module.exports = Map;