First version

This commit is contained in:
Jelte Lagendijk
2016-04-04 17:26:48 +02:00
commit 5d79ad2f81
26 changed files with 12639 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
test/javasource/
test/deployment/
test/.classpath
test/.project
test/theme/
test/userlib/
test/.classpath
test/.project
*.launch
*.tmp
*.lock
.idea/
dist/
node_modules/
.editorconfig
*DS_Store*
.vscode/
*.bak

23
.jshintrc Normal file
View File

@@ -0,0 +1,23 @@
{
// Enforcing
"curly" : false,
"forin" : false,
"latedef" : "nofunc",
"newcap" : true,
"quotmark" : "double",
"eqeqeq" : true,
"undef" : true,
"globals" : {
"mendix" : false,
"mx" : false,
"logger" : false
},
// Relaxing
"laxbreak" : true,
// Environments
"browser" : true,
"devel" : true,
"dojo" : true
}

223
Gruntfile.js Normal file
View File

@@ -0,0 +1,223 @@
// Generated on 2016-02-12 using generator-mendix 1.3.3 :: git+https://github.com/mendix/generator-mendix.git
/*jshint -W069*/
/*global module*/
"use strict";
// In case you seem to have trouble starting Mendix through `grunt start-mendix`, you might have to set the path to the Mendix application.
// If it works, leave MODELER_PATH at null
var MODELER_PATH = null;
var MODELER_ARGS = "/file:{path}";
/********************************************************************************
* Do not edit anything below, unless you know what you are doing
********************************************************************************/
var path = require("path"),
mendixApp = require("node-mendix-modeler-path"),
base64 = require("node-base64-image"),
semver = require("semver"),
xml2js = require("xml2js"),
parser = new xml2js.Parser(),
builder = new xml2js.Builder({
renderOpts: { pretty: true, indent: " ", newline: "\n" },
xmldec: { standalone: null, encoding: "utf-8" }
}),
shelljs = require("shelljs"),
pkg = require("./package.json");
var TEST_PATH = path.join(shelljs.pwd(), "/test/Test.mpr");
var WIDGET_XML = path.join(shelljs.pwd(), "/src/", pkg.name, "/", pkg.name + ".xml");
var PACKAGE_XML = path.join(shelljs.pwd(), "/src/package.xml");
var TEST_WIDGETS_FOLDER = path.join(shelljs.pwd(), "./test/widgets");
var TEST_WIDGETS_DEPLOYMENT_FOLDER = path.join(shelljs.pwd(), "./test/deployment/web/widgets");
/**
* If you want to use a custom folder for the test project, make sure these are added to package.json:
* "paths": {
* "testProjectFolder": "./test/",
* "testProjectFileName": "Test.mpr"
* },
* You can test it by running: `grunt folders`
**/
if (pkg.paths && pkg.paths.testProjectFolder && pkg.paths.testProjectFileName) {
var folder = pkg.paths.testProjectFolder;
if (folder.indexOf(".") === 0) {
folder = path.join(shelljs.pwd(), folder);
}
TEST_PATH = path.join(folder, pkg.paths.testProjectFileName);
TEST_WIDGETS_FOLDER = path.join(folder, "/widgets");
TEST_WIDGETS_DEPLOYMENT_FOLDER = path.join(folder, "/deployment/web/widgets");
}
module.exports = function (grunt) {
grunt.initConfig({
watch: {
autoDeployUpdate: {
"files": [ "./src/**/*" ],
"tasks": [ "compress", "newer:copy" ],
options: {
debounceDelay: 250,
livereload: true
}
}
},
compress: {
makezip: {
options: {
archive: "./dist/" + pkg.name + ".mpk",
mode: "zip"
},
files: [{
expand: true,
date: new Date(),
store: false,
cwd: "./src",
src: ["**/*"]
}]
}
},
copy: {
deployment: {
files: [
{ dest: TEST_WIDGETS_DEPLOYMENT_FOLDER, cwd: "./src/", src: ["**/*"], expand: true }
]
},
mpks: {
files: [
{ dest: TEST_WIDGETS_FOLDER, cwd: "./dist/", src: [ pkg.name + ".mpk"], expand: true }
]
}
},
clean: {
build: [
path.join(shelljs.pwd(), "dist", pkg.name, "/*")
]
}
});
grunt.loadNpmTasks("grunt-contrib-compress");
grunt.loadNpmTasks("grunt-contrib-clean");
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-newer");
grunt.registerTask("start-modeler", function () {
var done = this.async();
if (MODELER_PATH !== null || (mendixApp.err === null && mendixApp.output !== null && mendixApp.output.cmd && mendixApp.output.arg)) {
grunt.util.spawn({
cmd: MODELER_PATH || mendixApp.output.cmd,
args: [
(MODELER_PATH !== null ? MODELER_ARGS : mendixApp.output.arg).replace("{path}", TEST_PATH)
]
}, function () {
done();
});
} else {
console.error("Cannot start Modeler, see error:");
console.log(mendixApp.err);
done();
}
});
grunt.registerTask("version", function (version) {
var done = this.async();
if (!grunt.file.exists(PACKAGE_XML)) {
grunt.log.error("Cannot find " + PACKAGE_XML);
return done();
}
var xml = grunt.file.read(PACKAGE_XML);
parser.parseString(xml, function (err, res) {
if (err) {
grunt.log.error(err);
return done();
}
if (res.package.clientModule[0]["$"]["version"]) {
var currentVersion = res.package.clientModule[0]["$"]["version"];
if (!version) {
grunt.log.writeln("\nCurrent version is " + currentVersion);
grunt.log.writeln("Set new version by running 'grunt version:x.y.z'");
done();
} else {
if (!semver.valid(version) || !semver.satisfies(version, ">= 1.0.0")) {
grunt.log.error("\nPlease provide a valid version that is higher than 1.0.0. Current version: " + currentVersion);
done();
} else {
res.package.clientModule[0]["$"]["version"] = version;
pkg.version = version;
var xmlString = builder.buildObject(res);
grunt.file.write(PACKAGE_XML, xmlString);
grunt.file.write("package.json", JSON.stringify(pkg, null, 2));
done();
}
}
} else {
grunt.log.error("Cannot find current version number");
}
});
});
grunt.registerTask("generate-icon", function () {
var iconPath = path.join(shelljs.pwd(), "/icon.png"),
options = {localFile: true, string: true},
done = this.async();
grunt.log.writeln("Processing icon");
if (!grunt.file.exists(iconPath) || !grunt.file.exists(WIDGET_XML)) {
grunt.log.error("can\'t generate icon");
return done();
}
base64.base64encoder(iconPath, options, function (err, image) {
if (!err) {
var xmlOld = grunt.file.read(WIDGET_XML);
parser.parseString(xmlOld, function (err, result) {
if (!err) {
if (result && result.widget && result.widget.icon) {
result.widget.icon[0] = image;
}
var xmlString = builder.buildObject(result);
grunt.file.write(WIDGET_XML, xmlString);
done();
}
});
} else {
grunt.log.error("can\'t generate icon");
return done();
}
});
});
grunt.registerTask("folders", function () {
var done = this.async();
grunt.log.writeln("\nShowing file paths that Grunt will use. You can edit the package.json accordingly\n");
grunt.log.writeln("TEST_PATH: ", TEST_PATH);
grunt.log.writeln("WIDGET_XML: ", WIDGET_XML);
grunt.log.writeln("PACKAGE_XML: ", PACKAGE_XML);
grunt.log.writeln("TEST_WIDGETS_FOLDER: ", TEST_WIDGETS_FOLDER);
grunt.log.writeln("TEST_WIDGETS_DEPLOYMENT_FOLDER: ", TEST_WIDGETS_DEPLOYMENT_FOLDER);
return done();
});
grunt.registerTask("start-mendix", [ "start-modeler" ]);
grunt.registerTask(
"default",
"Watches for changes and automatically creates an MPK file, as well as copying the changes to your deployment folder",
[ "watch" ]
);
grunt.registerTask(
"clean build",
"Compiles all the assets and copies the files to the build directory.",
[ "clean", "compress", "copy" ]
);
grunt.registerTask(
"build",
[ "clean build" ]
);
};

202
LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

134
README.md Normal file
View File

@@ -0,0 +1,134 @@
#OpenLayers Maps Widget
Add different type of maps to your application, using [Leaflet.js](http://leafletjs.com/)! This widget is almost the same as the GoogleMaps widget, without the typical interaction you have in Google Maps, but with a wide variety of maps available.
##Typical usage scenario
Showing an overview where all your branches/members/clients/orders/etc. are.
##Features and limitations
Easy to implement. Can use both a context object as well as without.
The maptypes come from a separate Leaflet plugin: [Leaflet-providers](https://github.com/leaflet-extras/leaflet-providers).
A preview of the various map types can be seen [here](http://leaflet-extras.github.io/leaflet-providers/preview/).
Note that some of the map types have been disabled in this widget (you will see more examples in the previous link). This is because:
* They are too localized (limited to a certain region)
* Need extra configuration (HERE and MapBox need API keys, not implemented yet)
* Are not very functional yet (is an overlay instead of a full map. Overlays might be enabled in the future)
We will be adding more maps in the future. This is the current list of map providers:
```
Providers:
OpenStreetMap
.Mapnik (this is the default map)
.BlackAndWhite
.DE
.France
.HOT
OpenTopoMap
Thunderforest
.OpenCycleMap
.Transport
.TransportDark
.SpinalMap
.Landscape
.Outdoors
.Pioneer
MapQuestOpen
.OSM
.Aerial
.HybridOverlay
Stamen
.Toner
.TonerBackground
.TonerHybrid
.TonerLines
.TonerLabels
.TonerLite
.Watercolor
Esri
.WorldStreetMap
.DeLorme
.WorldTopoMap
.WorldImagery
.WorldTerrain
.WorldShadedRelief
.WorldPhysical
.OceanBasemap
.NatGeoWorldMap
.WorldGrayCanvas
```
##Dependencies
Mendix 5.18 or higher.
##Configuration
There are 3 use-cases for which this widget can be used.
Outside a dataview: Will just retrieve the objects specified and show them on the map.
Inside a dataview not matching the Objects property: Will show the objects specified, can use '[%CurrentObject%]' in XPath Constraint.
Inside a dataview matching the Objects property: Will show the objects specified, can NOT use '[%CurrentObject%]'. Can set up the dataview to listen to a matching datagrid. If 'Pan to context' is set to true, it will focus on the marker of the object that is selected in the datagrid.
To finish up, just enter the correct values into the widget. For more information on the different input properties, read below.
##Properties
####Behaviour
* Pan to context: Set this only to true if your object containing the address matches your dataview. With this you can have your dataview listen to a datagrid of your Users objects containing the addresses and it will jump to the matching marker on the map.
* Default latitude: The default latitude the map should use when no objects are found or there is no object found (when using an XPath with CurrentObject)
* Default longitude: The default longitude the map should use when no objects are found or there is no object found (when using an XPath with CurrentObject)
* Single item zoom level: The zoom level to be used when showing a single item or the default location. Level 1 shows the entire globe, 15 is city level and 20 is house level.
* Refresh on entity changes: When set to true, the map refreshes on any changes to the mapped entity (and/or 1-deep entity). This includes on create/delete/change. Do note that it simply reloads the entire map, so this is not recommended when mapping a lot of objects.
####Data Source
* Objects: The widget retrieves its own objects based on the entity specified here.
* XPath constraint: The XPath constraint to be used for retrieving the objects. Important: Even though the Modeler lets you, you can't use '[%CurrentObject%]' if your dataview entity matches the entity of the objects you are retrieving. Doing so will result in an error.
* Marker attribute: The attribute that contains the text to display in the info window at the location. No info window will be shown if this is left empty. Tip: The window displays HTML, you can use the Rich Text Editor widget to create your styled text and have it saved as HTML. This can then be directly used for the info window! This can be 1-deep association.
* Latitude attribute: The attribute containing the latitudes of the objects. This can be 1-deep association.
* Longitude attribute: The attribute containing the longitudes of the objects. This can be 1-deep association.
* On click microflow: Microflow to be executed when clicking on a marker. The Microflow will have the corresponding object as an input parameter
####Appearance
* Height: The height the widget will have (in pixels) . This attribute is required.
* Width: The width of the widget, can be in pixels or percentage.
* Marker template: If you set the Marker attribute in Data Source, you can control the html that is used inside the popup that is shown when clicking a marker
####Map Type
* Maptype: Map type, see features
* Use custom map type: If you want to use your own map type, set this to 'Yes'
* Custom Map Url: Set the URL for the custom map type*
* Custom Map Options: Set extra options for the custom map type. Make sure this is a valid JSON object. Open with ``{`` and close with ``}``. The keys and values need to be strings, so surrounded by double quotes ``"key":"value"``*
> **More information about the Custom Map Url and the Custom Map Options can be found on the [Leaflet Reference](http://leafletjs.com/reference.html#tilelayer).**
####Controls
* Mouse dragging: Enable/disable dragging the map using the mouse
* Touch zoom: Enable/disable zooming by touch-dragging two fingers (mobile)
* Mouse scrolling: Enable/disable scrolling by using the mouse wheel
* Zoom control: Enable/disable zoom control (+/- buttons on the map)
* Zoom control position: Position of the zoomcontrol on the map
* Attribution control: Enable/disable attribution control (credits that are shown on the bottom right of the map)
* Attribution control position: Position of the attribution on the map
* Fullscreen control: Enable/disable fullscreen button
####Location
* Add geolocation button: This will add a button that you can use to find your location
* Geolocation control position: Position of the geolocation button on the map
* Draw an uncertainty circle: Draw a circle around the location, indicating how big the uncertainty is
* Keep zoom level: When locating, keep the current zoom level. If this is set to false, it will use the maximimum zoom level
####Scale
* Add scale: Add a scale indicator on the map
* Scale position
* Show metric scale line (m/km)
* Show imperial scale line (mi/ft)
* Max width (in pixels, dynamically rounded)
##Troubleshooting
* If your map does not load on Cloud deployment, it usually means it tries to download map files from a HTTP source, instead of HTTPS. This is usually blocked. If that happens, please report this as an issue, so we can disable the map type. If you use a custom Map Type, make sure the URL starts with ``https://`` or ``//``.

BIN
assets/app_store_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
assets/app_store_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

33
package.json Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "Leaflet",
"version": "1.0.0",
"description": "",
"license": "",
"author": "",
"private": true,
"dependencies": {},
"devDependencies": {
"grunt": "0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-compress": "^0.14.0",
"grunt-contrib-copy": "^0.8.2",
"grunt-contrib-watch": "^0.6.1",
"grunt-newer": "^1.1.1",
"node-base64-image": "^0.1.0",
"shelljs": "^0.5.3",
"xml2js": "^0.4.15",
"semver": "^5.1.0",
"node-mendix-modeler-path": "https://github.com/JelteMX/node-mendix-modeler-path/archive/v1.0.0.tar.gz"
},
"engines": {
"node": ">=0.12.0"
},
"generatorVersion": "1.3.3",
"paths": {
"testProjectFolder": "./test/",
"testProjectFileName": "Test.mpr"
},
"scripts": {
"test": "grunt test"
}
}

301
src/Leaflet/Leaflet.xml Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,197 @@
// Source: https://github.com/brunob/leaflet.fullscreen/blob/master/Control.FullScreen.js
/*
Edited by J.W. Lagendijk <jelte.lagendijk@mendix.com> to make sure it works within the Mendix environment
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['Leaflet/lib/leaflet-src'], factory);
} else if (typeof modules === 'object' && module.exports) {
// define a Common JS module that relies on 'leaflet'
module.exports = factory(require('leaflet'));
} else {
// Assume Leaflet is loaded into global object L already
factory(L);
}
}(this, function (L) {
'use strict';
L.Control.FullScreen = L.Control.extend({
options: {
position: 'topleft',
title: 'Full Screen',
titleCancel: 'Exit Full Screen',
forceSeparateButton: false,
forcePseudoFullscreen: false
},
onAdd: function(map) {
var className = 'leaflet-control-zoom-fullscreen',
container, content = '';
if (map.zoomControl && !this.options.forceSeparateButton) {
container = map.zoomControl._container;
} else {
container = L.DomUtil.create('div', 'leaflet-bar');
}
if (this.options.content) {
content = this.options.content;
} else {
className += ' fullscreen-icon';
}
this._createButton(this.options.title, className, content, container, this.toggleFullScreen, this);
this._map.on('enterFullscreen exitFullscreen', this._toggleTitle, this);
return container;
},
_createButton: function(title, className, content, container, fn, context) {
this.link = L.DomUtil.create('a', className, container);
this.link.href = '#';
this.link.title = title;
this.link.innerHTML = content;
L.DomEvent.addListener(this.link, 'click', L.DomEvent.stopPropagation)
.addListener(this.link, 'click', L.DomEvent.preventDefault)
.addListener(this.link, 'click', fn, context);
L.DomEvent.addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
.addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
.addListener(container, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
L.DomEvent.addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
.addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
.addListener(document, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
return this.link;
},
toggleFullScreen: function() {
var map = this._map;
map._exitFired = false;
if (map._isFullscreen) {
if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
fullScreenApi.cancelFullScreen(map._container);
} else {
L.DomUtil.removeClass(map._container, 'leaflet-pseudo-fullscreen');
}
map.invalidateSize();
map.fire('exitFullscreen');
map._exitFired = true;
map._isFullscreen = false;
} else {
if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
fullScreenApi.requestFullScreen(map._container);
} else {
L.DomUtil.addClass(map._container, 'leaflet-pseudo-fullscreen');
}
map.invalidateSize();
map.fire('enterFullscreen');
map._isFullscreen = true;
}
},
_toggleTitle: function() {
this.link.title = this._map._isFullscreen ? this.options.title : this.options.titleCancel;
},
_handleEscKey: function() {
var map = this._map;
if (!fullScreenApi.isFullScreen(map) && !map._exitFired) {
map.fire('exitFullscreen');
map._exitFired = true;
map._isFullscreen = false;
}
}
});
L.Map.addInitHook(function() {
if (this.options.fullscreenControl) {
this.fullscreenControl = L.control.fullscreen(this.options.fullscreenControlOptions);
this.addControl(this.fullscreenControl);
}
});
L.control.fullscreen = function(options) {
return new L.Control.FullScreen(options);
};
/*
Native FullScreen JavaScript API
-------------
Assumes Mozilla naming conventions instead of W3C for now
source : http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
*/
var
fullScreenApi = {
supportsFullScreen: false,
isFullScreen: function() {
return false;
},
requestFullScreen: function() {},
cancelFullScreen: function() {},
fullScreenEventName: '',
prefix: ''
},
browserPrefixes = 'webkit moz o ms khtml'.split(' ');
// check for native support
if (typeof document.exitFullscreen !== 'undefined') {
fullScreenApi.supportsFullScreen = true;
} else {
// check for fullscreen support by vendor prefix
for (var i = 0, il = browserPrefixes.length; i < il; i++) {
fullScreenApi.prefix = browserPrefixes[i];
if (typeof document[fullScreenApi.prefix + 'CancelFullScreen'] !== 'undefined') {
fullScreenApi.supportsFullScreen = true;
break;
}
}
}
// update methods to do something useful
if (fullScreenApi.supportsFullScreen) {
fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
fullScreenApi.isFullScreen = function() {
switch (this.prefix) {
case '':
return document.fullScreen;
case 'webkit':
return document.webkitIsFullScreen;
default:
return document[this.prefix + 'FullScreen'];
}
};
fullScreenApi.requestFullScreen = function(el) {
return (this.prefix === '') ? el.requestFullscreen() : el[this.prefix + 'RequestFullScreen']();
};
fullScreenApi.cancelFullScreen = function() {
return (this.prefix === '') ? document.exitFullscreen() : document[this.prefix + 'CancelFullScreen']();
};
}
// jQuery plugin
if (typeof jQuery !== 'undefined') {
jQuery.fn.requestFullScreen = function() {
return this.each(function() {
var el = jQuery(this);
if (fullScreenApi.supportsFullScreen) {
fullScreenApi.requestFullScreen(el);
}
});
};
}
// export api
window.fullScreenApi = fullScreenApi;
return L;
}));

View File

@@ -0,0 +1,504 @@
/*!
Copyright (c) 2016 Dominik Moritz
This file is part of the leaflet locate control. It is licensed under the MIT license.
You can find the project at: https://github.com/domoritz/leaflet-locatecontrol
*/
(function (factory, window) {
// see https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md#module-loaders
// for details on how to structure a leaflet plugin.
// define an AMD module that relies on 'leaflet'
if (typeof define === 'function' && define.amd) {
define(['Leaflet/lib/leaflet-src'], factory);
// define a Common JS module that relies on 'leaflet'
} else if (typeof exports === 'object') {
if (typeof window !== 'undefined' && window.L) {
module.exports = factory(L);
} else {
module.exports = factory(require('leaflet'));
}
}
// attach your plugin to the global 'L' variable
if(typeof window !== 'undefined' && window.L){
window.L.Locate = factory(L);
}
} (function (L) {
L.Control.Locate = L.Control.extend({
options: {
position: 'topleft',
layer: undefined, // use your own layer for the location marker
drawCircle: true,
follow: false, // follow with zoom and pan the user's location
stopFollowingOnDrag: false, // if follow is true, stop following when map is dragged (deprecated)
// if true locate control remains active on click even if the user's location is in view.
// clicking control will just pan to location
remainActive: false,
markerClass: L.circleMarker, // L.circleMarker or L.marker
// range circle
circleStyle: {
color: '#136AEC',
fillColor: '#136AEC',
fillOpacity: 0.15,
weight: 2,
opacity: 0.5
},
// inner marker
markerStyle: {
color: '#136AEC',
fillColor: '#2A93EE',
fillOpacity: 0.7,
weight: 2,
opacity: 0.9,
radius: 5
},
// changes to range circle and inner marker while following
// it is only necessary to provide the things that should change
followCircleStyle: {},
followMarkerStyle: {
//color: '#FFA500',
//fillColor: '#FFB000'
},
icon: 'fa fa-map-marker', // fa-location-arrow or fa-map-marker
iconLoading: 'fa fa-spinner fa-spin',
iconElementTag: 'span', // span or i
circlePadding: [0, 0],
metric: true,
onLocationError: function(err) {
// this event is called in case of any location error
// that is not a time out error.
alert(err.message);
},
onLocationOutsideMapBounds: function(control) {
// this event is repeatedly called when the location changes
control.stop();
alert(control.options.strings.outsideMapBoundsMsg);
},
setView: true, // automatically sets the map view to the user's location
// keep the current map zoom level when displaying the user's location. (if 'false', use maxZoom)
keepCurrentZoomLevel: false,
showPopup: true, // display a popup when the user click on the inner marker
strings: {
title: "Show me where I am",
metersUnit: "meters",
feetUnit: "feet",
popup: "You are within {distance} {unit} from this point",
outsideMapBoundsMsg: "You seem located outside the boundaries of the map"
},
locateOptions: {
maxZoom: Infinity,
watch: true // if you overwrite this, visualization cannot be updated
}
},
initialize: function (options) {
L.Map.addInitHook(function () {
if (this.options.locateControl) {
this.addControl(this);
}
});
for (var i in options) {
if (typeof this.options[i] === 'object') {
L.extend(this.options[i], options[i]);
} else {
this.options[i] = options[i];
}
}
L.extend(this.options.locateOptions, {
setView: false // have to set this to false because we have to
// do setView manually
});
},
/**
* This method launches the location engine.
* It is called before the marker is updated,
* event if it does not mean that the event will be ready.
*
* Override it if you want to add more functionalities.
* It should set the this._active to true and do nothing if
* this._active is not true.
*/
_activate: function() {
if (this.options.setView) {
this._locateOnNextLocationFound = true;
}
if(!this._active) {
this._map.locate(this.options.locateOptions);
}
this._active = true;
if (this.options.follow) {
this._startFollowing(this._map);
}
},
/**
* Called to stop the location engine.
*
* Override it to shutdown any functionalities you added on start.
*/
_deactivate: function() {
this._map.stopLocate();
this._map.off('dragstart', this._stopFollowing, this);
if (this.options.follow && this._following) {
this._stopFollowing(this._map);
}
},
/**
* Draw the resulting marker on the map.
*
* Uses the event retrieved from onLocationFound from the map.
*/
drawMarker: function(map) {
if (this._event.accuracy === undefined) {
this._event.accuracy = 0;
}
var radius = this._event.accuracy;
if (this._locateOnNextLocationFound) {
if (this._isOutsideMapBounds()) {
this.options.onLocationOutsideMapBounds(this);
} else {
// If accuracy info isn't desired, keep the current zoom level
if(this.options.keepCurrentZoomLevel) {
map.panTo([this._event.latitude, this._event.longitude]);
} else {
map.fitBounds(this._event.bounds, {
padding: this.options.circlePadding,
maxZoom: this.options.keepCurrentZoomLevel ?
map.getZoom() : this.options.locateOptions.maxZoom
});
}
}
this._locateOnNextLocationFound = false;
}
// circle with the radius of the location's accuracy
var style, o;
if (this.options.drawCircle) {
if (this._following) {
style = this.options.followCircleStyle;
} else {
style = this.options.circleStyle;
}
if (!this._circle) {
this._circle = L.circle(this._event.latlng, radius, style)
.addTo(this._layer);
} else {
this._circle.setLatLng(this._event.latlng).setRadius(radius);
for (o in style) {
this._circle.options[o] = style[o];
}
}
}
var distance, unit;
if (this.options.metric) {
distance = radius.toFixed(0);
unit = this.options.strings.metersUnit;
} else {
distance = (radius * 3.2808399).toFixed(0);
unit = this.options.strings.feetUnit;
}
// small inner marker
var mStyle;
if (this._following) {
mStyle = this.options.followMarkerStyle;
} else {
mStyle = this.options.markerStyle;
}
if (!this._marker) {
this._marker = this.createMarker(this._event.latlng, mStyle)
.addTo(this._layer);
} else {
this.updateMarker(this._event.latlng, mStyle);
}
var t = this.options.strings.popup;
if (this.options.showPopup && t) {
this._marker.bindPopup(L.Util.template(t, {distance: distance, unit: unit}))
._popup.setLatLng(this._event.latlng);
}
this._toggleContainerStyle();
},
/**
* Creates the marker.
*
* Should return the base marker so it is possible to bind a pop-up if the
* option is activated.
*
* Used by drawMarker, you can ignore it if you have overridden it.
*/
createMarker: function(latlng, mStyle) {
return this.options.markerClass(latlng, mStyle);
},
/**
* Updates the marker with current coordinates.
*
* Used by drawMarker, you can ignore it if you have overridden it.
*/
updateMarker: function(latlng, mStyle) {
this._marker.setLatLng(latlng);
for (var o in mStyle) {
this._marker.options[o] = mStyle[o];
}
},
/**
* Remove the marker from map.
*/
removeMarker: function() {
this._layer.clearLayers();
this._marker = undefined;
this._circle = undefined;
},
onAdd: function (map) {
var container = L.DomUtil.create('div',
'leaflet-control-locate leaflet-bar leaflet-control');
this._layer = this.options.layer || new L.LayerGroup();
this._layer.addTo(map);
this._event = undefined;
// extend the follow marker style and circle from the normal style
var tmp = {};
L.extend(tmp, this.options.markerStyle, this.options.followMarkerStyle);
this.options.followMarkerStyle = tmp;
tmp = {};
L.extend(tmp, this.options.circleStyle, this.options.followCircleStyle);
this.options.followCircleStyle = tmp;
this._link = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container);
this._link.href = '#';
this._link.title = this.options.strings.title;
this._icon = L.DomUtil.create(this.options.iconElementTag, this.options.icon, this._link);
L.DomEvent
.on(this._link, 'click', L.DomEvent.stopPropagation)
.on(this._link, 'click', L.DomEvent.preventDefault)
.on(this._link, 'click', function() {
var shouldStop = (this._event === undefined ||
this._map.getBounds().contains(this._event.latlng) ||
!this.options.setView || this._isOutsideMapBounds());
if (!this.options.remainActive && (this._active && shouldStop)) {
this.stop();
} else {
this.start();
}
}, this)
.on(this._link, 'dblclick', L.DomEvent.stopPropagation);
this._resetVariables();
this.bindEvents(map);
return container;
},
/**
* Binds the actions to the map events.
*/
bindEvents: function(map) {
map.on('locationfound', this._onLocationFound, this);
map.on('locationerror', this._onLocationError, this);
map.on('unload', this.stop, this);
},
/**
* Starts the plugin:
* - activates the engine
* - draws the marker (if coordinates available)
*/
start: function() {
this._activate();
if (!this._event) {
this._setClasses('requesting');
} else {
this.drawMarker(this._map);
}
},
/**
* Stops the plugin:
* - deactivates the engine
* - reinitializes the button
* - removes the marker
*/
stop: function() {
this._deactivate();
this._cleanClasses();
this._resetVariables();
this.removeMarker();
},
/**
* Calls deactivate and dispatches an error.
*/
_onLocationError: function(err) {
// ignore time out error if the location is watched
if (err.code == 3 && this.options.locateOptions.watch) {
return;
}
this.stop();
this.options.onLocationError(err);
},
/**
* Stores the received event and updates the marker.
*/
_onLocationFound: function(e) {
// no need to do anything if the location has not changed
if (this._event &&
(this._event.latlng.lat === e.latlng.lat &&
this._event.latlng.lng === e.latlng.lng &&
this._event.accuracy === e.accuracy)) {
return;
}
if (!this._active) {
return;
}
this._event = e;
if (this.options.follow && this._following) {
this._locateOnNextLocationFound = true;
}
this.drawMarker(this._map);
},
/**
* Dispatches the 'startfollowing' event on map.
*/
_startFollowing: function() {
this._map.fire('startfollowing', this);
this._following = true;
if (this.options.stopFollowingOnDrag) {
this._map.on('dragstart', this._stopFollowing, this);
}
},
/**
* Dispatches the 'stopfollowing' event on map.
*/
_stopFollowing: function() {
this._map.fire('stopfollowing', this);
this._following = false;
if (this.options.stopFollowingOnDrag) {
this._map.off('dragstart', this._stopFollowing, this);
}
this._toggleContainerStyle();
},
/**
* Check if location is in map bounds
*/
_isOutsideMapBounds: function() {
if (this._event === undefined)
return false;
return this._map.options.maxBounds &&
!this._map.options.maxBounds.contains(this._event.latlng);
},
/**
* Toggles button class between following and active.
*/
_toggleContainerStyle: function() {
if (!this._container) {
return;
}
if (this._following) {
this._setClasses('following');
} else {
this._setClasses('active');
}
},
/**
* Sets the CSS classes for the state.
*/
_setClasses: function(state) {
if (state == 'requesting') {
L.DomUtil.removeClasses(this._container, "active following");
L.DomUtil.addClasses(this._container, "requesting");
L.DomUtil.removeClasses(this._icon, this.options.icon);
L.DomUtil.addClasses(this._icon, this.options.iconLoading);
} else if (state == 'active') {
L.DomUtil.removeClasses(this._container, "requesting following");
L.DomUtil.addClasses(this._container, "active");
L.DomUtil.removeClasses(this._icon, this.options.iconLoading);
L.DomUtil.addClasses(this._icon, this.options.icon);
} else if (state == 'following') {
L.DomUtil.removeClasses(this._container, "requesting");
L.DomUtil.addClasses(this._container, "active following");
L.DomUtil.removeClasses(this._icon, this.options.iconLoading);
L.DomUtil.addClasses(this._icon, this.options.icon);
}
},
/**
* Removes all classes from button.
*/
_cleanClasses: function() {
L.DomUtil.removeClass(this._container, "requesting");
L.DomUtil.removeClass(this._container, "active");
L.DomUtil.removeClass(this._container, "following");
L.DomUtil.removeClasses(this._icon, this.options.iconLoading);
L.DomUtil.addClasses(this._icon, this.options.icon);
},
/**
* Reinitializes attributes.
*/
_resetVariables: function() {
this._active = false;
this._locateOnNextLocationFound = this.options.setView;
this._following = false;
}
});
L.control.locate = function (options) {
return new L.Control.Locate(options);
};
(function(){
// leaflet.js raises bug when trying to addClass / removeClass multiple classes at once
// Let's create a wrapper on it which fixes it.
var LDomUtilApplyClassesMethod = function(method, element, classNames) {
classNames = classNames.split(' ');
classNames.forEach(function(className) {
L.DomUtil[method].call(this, element, className);
});
};
L.DomUtil.addClasses = function(el, names) { LDomUtilApplyClassesMethod('addClass', el, names); };
L.DomUtil.removeClasses = function(el, names) { LDomUtilApplyClassesMethod('removeClass', el, names); };
})();
return L.Control.Locate;
}, window));

View File

@@ -0,0 +1,673 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['Leaflet/lib/leaflet-src'], factory);
} else if (typeof modules === 'object' && module.exports) {
// define a Common JS module that relies on 'leaflet'
module.exports = factory(require('leaflet'));
} else {
// Assume Leaflet is loaded into global object L already
factory(L);
}
}(this, function (L) {
'use strict';
L.TileLayer.Provider = L.TileLayer.extend({
initialize: function (arg, options) {
var providers = L.TileLayer.Provider.providers;
var parts = arg.split('.');
var providerName = parts[0];
var variantName = parts[1];
if (!providers[providerName]) {
throw 'No such provider (' + providerName + ')';
}
var provider = {
url: providers[providerName].url,
options: providers[providerName].options
};
// overwrite values in provider from variant.
if (variantName && 'variants' in providers[providerName]) {
if (!(variantName in providers[providerName].variants)) {
throw 'No such variant of ' + providerName + ' (' + variantName + ')';
}
var variant = providers[providerName].variants[variantName];
var variantOptions;
if (typeof variant === 'string') {
variantOptions = {
variant: variant
};
} else {
variantOptions = variant.options;
}
provider = {
url: variant.url || provider.url,
options: L.Util.extend({}, provider.options, variantOptions)
};
}
var forceHTTP = window.location.protocol === 'file:' || provider.options.forceHTTP;
if (provider.url.indexOf('//') === 0 && forceHTTP) {
provider.url = 'http:' + provider.url;
}
// If retina option is set
if (provider.options.retina) {
// Check retina screen
if (options.detectRetina && L.Browser.retina) {
// The retina option will be active now
// But we need to prevent Leaflet retina mode
options.detectRetina = false;
} else {
// No retina, remove option
provider.options.retina = '';
}
}
// replace attribution placeholders with their values from toplevel provider attribution,
// recursively
var attributionReplacer = function (attr) {
if (attr.indexOf('{attribution.') === -1) {
return attr;
}
return attr.replace(/\{attribution.(\w*)\}/,
function (match, attributionName) {
return attributionReplacer(providers[attributionName].options.attribution);
}
);
};
provider.options.attribution = attributionReplacer(provider.options.attribution);
// Compute final options combining provider options with any user overrides
var layerOpts = L.Util.extend({}, provider.options, options);
L.TileLayer.prototype.initialize.call(this, provider.url, layerOpts);
}
});
/**
* Definition of providers.
* see http://leafletjs.com/reference.html#tilelayer for options in the options map.
*/
L.TileLayer.Provider.providers = {
OpenStreetMap: {
url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
options: {
maxZoom: 19,
attribution:
'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
},
variants: {
Mapnik: {},
BlackAndWhite: {
url: 'http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
options: {
maxZoom: 18
}
},
DE: {
url: 'http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png',
options: {
maxZoom: 18
}
},
France: {
url: 'http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
options: {
attribution: '&copy; Openstreetmap France | {attribution.OpenStreetMap}'
}
},
HOT: {
url: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
options: {
attribution: '{attribution.OpenStreetMap}, Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>'
}
}
}
},
OpenSeaMap: {
url: 'http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png',
options: {
attribution: 'Map data: &copy; <a href="http://www.openseamap.org">OpenSeaMap</a> contributors'
}
},
OpenTopoMap: {
url: '//{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
options: {
maxZoom: 16,
attribution: 'Map data: {attribution.OpenStreetMap}, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
}
},
Thunderforest: {
url: '//{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png',
options: {
attribution:
'&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, {attribution.OpenStreetMap}',
variant: 'cycle'
},
variants: {
OpenCycleMap: 'cycle',
Transport: {
options: {
variant: 'transport',
maxZoom: 19
}
},
TransportDark: {
options: {
variant: 'transport-dark',
maxZoom: 19
}
},
SpinalMap: {
options: {
variant: 'spinal-map',
maxZoom: 11
}
},
Landscape: 'landscape',
Outdoors: 'outdoors',
Pioneer: 'pioneer'
}
},
OpenMapSurfer: {
url: 'http://korona.geog.uni-heidelberg.de/tiles/{variant}/x={x}&y={y}&z={z}',
options: {
maxZoom: 20,
variant: 'roads',
attribution: 'Imagery from <a href="http://giscience.uni-hd.de/">GIScience Research Group @ University of Heidelberg</a> &mdash; Map data {attribution.OpenStreetMap}'
},
variants: {
Roads: 'roads',
AdminBounds: {
options: {
variant: 'adminb',
maxZoom: 19
}
},
Grayscale: {
options: {
variant: 'roadsg',
maxZoom: 19
}
}
}
},
Hydda: {
url: 'http://{s}.tile.openstreetmap.se/hydda/{variant}/{z}/{x}/{y}.png',
options: {
variant: 'full',
attribution: 'Tiles courtesy of <a href="http://openstreetmap.se/" target="_blank">OpenStreetMap Sweden</a> &mdash; Map data {attribution.OpenStreetMap}'
},
variants: {
Full: 'full',
Base: 'base',
RoadsAndLabels: 'roads_and_labels'
}
},
MapQuestOpen: {
/* Mapquest does support https, but with a different subdomain:
* https://otile{s}-s.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.{ext}
* which makes implementing protocol relativity impossible.
*/
url: 'https://otile{s}-s.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.{ext}', // Changed default to HTTPS
options: {
type: 'map',
ext: 'jpg',
attribution:
'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> &mdash; ' +
'Map data {attribution.OpenStreetMap}',
subdomains: '1234'
},
variants: {
OSM: {},
Aerial: {
options: {
type: 'sat',
attribution:
'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> &mdash; ' +
'Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency'
}
},
HybridOverlay: {
options: {
type: 'hyb',
ext: 'png',
opacity: 0.9
}
}
}
},
MapBox: {
url: '//api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}',
options: {
attribution:
'Imagery from <a href="http://mapbox.com/about/maps/">MapBox</a> &mdash; ' +
'Map data {attribution.OpenStreetMap}',
subdomains: 'abcd'
}
},
Stamen: {
url: '//stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.{ext}',
options: {
attribution:
'Map tiles by <a href="http://stamen.com">Stamen Design</a>, ' +
'<a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; ' +
'Map data {attribution.OpenStreetMap}',
subdomains: 'abcd',
minZoom: 0,
maxZoom: 20,
variant: 'toner',
ext: 'png'
},
variants: {
Toner: 'toner',
TonerBackground: 'toner-background',
TonerHybrid: 'toner-hybrid',
TonerLines: 'toner-lines',
TonerLabels: 'toner-labels',
TonerLite: 'toner-lite',
Watercolor: {
options: {
variant: 'watercolor',
minZoom: 1,
maxZoom: 16
}
},
Terrain: {
options: {
variant: 'terrain',
minZoom: 4,
maxZoom: 18,
bounds: [[22, -132], [70, -56]]
}
},
TerrainBackground: {
options: {
variant: 'terrain-background',
minZoom: 4,
maxZoom: 18,
bounds: [[22, -132], [70, -56]]
}
},
TopOSMRelief: {
options: {
variant: 'toposm-color-relief',
ext: 'jpg',
bounds: [[22, -132], [51, -56]]
}
},
TopOSMFeatures: {
options: {
variant: 'toposm-features',
bounds: [[22, -132], [51, -56]],
opacity: 0.9
}
}
}
},
Esri: {
url: '//server.arcgisonline.com/ArcGIS/rest/services/{variant}/MapServer/tile/{z}/{y}/{x}',
options: {
variant: 'World_Street_Map',
attribution: 'Tiles &copy; Esri'
},
variants: {
WorldStreetMap: {
options: {
attribution:
'{attribution.Esri} &mdash; ' +
'Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
}
},
DeLorme: {
options: {
variant: 'Specialty/DeLorme_World_Base_Map',
minZoom: 1,
maxZoom: 11,
attribution: '{attribution.Esri} &mdash; Copyright: &copy;2012 DeLorme'
}
},
WorldTopoMap: {
options: {
variant: 'World_Topo_Map',
attribution:
'{attribution.Esri} &mdash; ' +
'Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
}
},
WorldImagery: {
options: {
variant: 'World_Imagery',
attribution:
'{attribution.Esri} &mdash; ' +
'Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
}
},
WorldTerrain: {
options: {
variant: 'World_Terrain_Base',
maxZoom: 13,
attribution:
'{attribution.Esri} &mdash; ' +
'Source: USGS, Esri, TANA, DeLorme, and NPS'
}
},
WorldShadedRelief: {
options: {
variant: 'World_Shaded_Relief',
maxZoom: 13,
attribution: '{attribution.Esri} &mdash; Source: Esri'
}
},
WorldPhysical: {
options: {
variant: 'World_Physical_Map',
maxZoom: 8,
attribution: '{attribution.Esri} &mdash; Source: US National Park Service'
}
},
OceanBasemap: {
options: {
variant: 'Ocean_Basemap',
maxZoom: 13,
attribution: '{attribution.Esri} &mdash; Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri'
}
},
NatGeoWorldMap: {
options: {
variant: 'NatGeo_World_Map',
maxZoom: 16,
attribution: '{attribution.Esri} &mdash; National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC'
}
},
WorldGrayCanvas: {
options: {
variant: 'Canvas/World_Light_Gray_Base',
maxZoom: 16,
attribution: '{attribution.Esri} &mdash; Esri, DeLorme, NAVTEQ'
}
}
}
},
OpenWeatherMap: {
url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png',
options: {
maxZoom: 19,
attribution: 'Map data &copy; <a href="http://openweathermap.org">OpenWeatherMap</a>',
opacity: 0.5
},
variants: {
Clouds: 'clouds',
CloudsClassic: 'clouds_cls',
Precipitation: 'precipitation',
PrecipitationClassic: 'precipitation_cls',
Rain: 'rain',
RainClassic: 'rain_cls',
Pressure: 'pressure',
PressureContour: 'pressure_cntr',
Wind: 'wind',
Temperature: 'temp',
Snow: 'snow'
}
},
HERE: {
/*
* HERE maps, formerly Nokia maps.
* These basemaps are free, but you need an API key. Please sign up at
* http://developer.here.com/getting-started
*
* Note that the base urls contain '.cit' whichs is HERE's
* 'Customer Integration Testing' environment. Please remove for production
* envirionments.
*/
url:
'//{s}.{base}.maps.cit.api.here.com/maptile/2.1/' +
'{type}/{mapID}/{variant}/{z}/{x}/{y}/{size}/{format}?' +
'app_id={app_id}&app_code={app_code}&lg={language}',
options: {
attribution:
'Map &copy; 1987-2014 <a href="http://developer.here.com">HERE</a>',
subdomains: '1234',
mapID: 'newest',
'app_id': '<insert your app_id here>',
'app_code': '<insert your app_code here>',
base: 'base',
variant: 'normal.day',
maxZoom: 20,
type: 'maptile',
language: 'eng',
format: 'png8',
size: '256'
},
variants: {
normalDay: 'normal.day',
normalDayCustom: 'normal.day.custom',
normalDayGrey: 'normal.day.grey',
normalDayMobile: 'normal.day.mobile',
normalDayGreyMobile: 'normal.day.grey.mobile',
normalDayTransit: 'normal.day.transit',
normalDayTransitMobile: 'normal.day.transit.mobile',
normalNight: 'normal.night',
normalNightMobile: 'normal.night.mobile',
normalNightGrey: 'normal.night.grey',
normalNightGreyMobile: 'normal.night.grey.mobile',
basicMap: {
options: {
type: 'basetile'
}
},
mapLabels: {
options: {
type: 'labeltile',
format: 'png'
}
},
trafficFlow: {
options: {
base: 'traffic',
type: 'flowtile'
}
},
carnavDayGrey: 'carnav.day.grey',
hybridDay: {
options: {
base: 'aerial',
variant: 'hybrid.day'
}
},
hybridDayMobile: {
options: {
base: 'aerial',
variant: 'hybrid.day.mobile'
}
},
pedestrianDay: 'pedestrian.day',
pedestrianNight: 'pedestrian.night',
satelliteDay: {
options: {
base: 'aerial',
variant: 'satellite.day'
}
},
terrainDay: {
options: {
base: 'aerial',
variant: 'terrain.day'
}
},
terrainDayMobile: {
options: {
base: 'aerial',
variant: 'terrain.day.mobile'
}
}
}
},
FreeMapSK: {
url: 'http://t{s}.freemap.sk/T/{z}/{x}/{y}.jpeg',
options: {
minZoom: 8,
maxZoom: 16,
subdomains: '1234',
bounds: [[47.204642, 15.996093], [49.830896, 22.576904]],
attribution:
'{attribution.OpenStreetMap}, vizualization CC-By-SA 2.0 <a href="http://freemap.sk">Freemap.sk</a>'
}
},
MtbMap: {
url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png',
options: {
attribution:
'{attribution.OpenStreetMap} &amp; USGS'
}
},
CartoDB: {
url: 'http://{s}.basemaps.cartocdn.com/{variant}/{z}/{x}/{y}.png',
options: {
attribution: '{attribution.OpenStreetMap} &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
subdomains: 'abcd',
maxZoom: 19,
variant: 'light_all'
},
variants: {
Positron: 'light_all',
PositronNoLabels: 'light_nolabels',
PositronOnlyLabels: 'light_only_labels',
DarkMatter: 'dark_all',
DarkMatterNoLabels: 'dark_nolabels',
DarkMatterOnlyLabels: 'dark_only_labels'
}
},
HikeBike: {
url: 'http://{s}.tiles.wmflabs.org/{variant}/{z}/{x}/{y}.png',
options: {
maxZoom: 19,
attribution: '{attribution.OpenStreetMap}',
variant: 'hikebike'
},
variants: {
HikeBike: {},
HillShading: {
options: {
maxZoom: 15,
variant: 'hillshading'
}
}
}
},
BasemapAT: {
url: '//maps{s}.wien.gv.at/basemap/{variant}/normal/google3857/{z}/{y}/{x}.{format}',
options: {
maxZoom: 19,
attribution: 'Datenquelle: <a href="www.basemap.at">basemap.at</a>',
subdomains: ['', '1', '2', '3', '4'],
format: 'png',
bounds: [[46.358770, 8.782379], [49.037872, 17.189532]],
variant: 'geolandbasemap'
},
variants: {
basemap: 'geolandbasemap',
grau: 'bmapgrau',
overlay: 'bmapoverlay',
highdpi: {
options: {
variant: 'bmaphidpi',
format: 'jpeg'
}
},
orthofoto: {
options: {
variant: 'bmaporthofoto30cm',
format: 'jpeg'
}
}
}
},
NASAGIBS: {
url: '//map1.vis.earthdata.nasa.gov/wmts-webmerc/{variant}/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}',
options: {
attribution:
'Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System ' +
'(<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
bounds: [[-85.0511287776, -179.999999975], [85.0511287776, 179.999999975]],
minZoom: 1,
maxZoom: 9,
format: 'jpg',
time: '',
tilematrixset: 'GoogleMapsCompatible_Level'
},
variants: {
ModisTerraTrueColorCR: 'MODIS_Terra_CorrectedReflectance_TrueColor',
ModisTerraBands367CR: 'MODIS_Terra_CorrectedReflectance_Bands367',
ViirsEarthAtNight2012: {
options: {
variant: 'VIIRS_CityLights_2012',
maxZoom: 8
}
},
ModisTerraLSTDay: {
options: {
variant: 'MODIS_Terra_Land_Surface_Temp_Day',
format: 'png',
maxZoom: 7,
opacity: 0.75
}
},
ModisTerraSnowCover: {
options: {
variant: 'MODIS_Terra_Snow_Cover',
format: 'png',
maxZoom: 8,
opacity: 0.75
}
},
ModisTerraAOD: {
options: {
variant: 'MODIS_Terra_Aerosol',
format: 'png',
maxZoom: 6,
opacity: 0.75
}
},
ModisTerraChlorophyll: {
options: {
variant: 'MODIS_Terra_Chlorophyll_A',
format: 'png',
maxZoom: 7,
opacity: 0.75
}
}
}
},
NLS: {
// NLS maps are copyright National library of Scotland.
// http://maps.nls.uk/projects/api/index.html
// Please contact NLS for anything other than non-commercial low volume usage
//
// Map sources: Ordnance Survey 1:1m to 1:63K, 1920s-1940s
// z0-9 - 1:1m
// z10-11 - quarter inch (1:253440)
// z12-18 - one inch (1:63360)
url: '//nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg',
options: {
attribution: '<a href="http://geo.nls.uk/maps/">National Library of Scotland Historic Maps</a>',
bounds: [[49.6, -12], [61.7, 3]],
minZoom: 1,
maxZoom: 18,
subdomains: '0123',
}
}
};
L.tileLayer.provider = function (provider, options) {
return new L.TileLayer.Provider(provider, options);
};
return L;
}));

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,471 @@
define([
"dojo/_base/declare",
"mxui/widget/_WidgetBase",
"dijit/_TemplatedMixin",
"dojo/dom-style",
"dojo/dom-construct",
"dojo/_base/array",
"dojo/_base/lang",
"dojo/text!Leaflet/widget/template/Leaflet.html",
// Leaflet
"Leaflet/lib/leaflet-src",
// Plugins
"Leaflet/lib/leaflet-providers",
"Leaflet/lib/leaflet-locatecontrol",
"Leaflet/lib/leaflet-fullscreen"
], function (declare, _WidgetBase, _TemplatedMixin, domStyle, domConstruct, dojoArray, lang, widgetTemplate, Leaflet) {
"use strict";
var LL = Leaflet.noConflict();
LL.Icon.Default.imagePath = require.toUrl("Leaflet/widget/ui/").split("?")[0];
return declare("Leaflet.widget.Leaflet", [_WidgetBase, _TemplatedMixin], {
// Template
templateString: widgetTemplate,
// DOM node
mapContainer: null,
// Set by modeler
gotocontext: false,
defaultLat: 0,
defaultLng: 0,
minZoom: 0,
maxZoom: 20,
lowestZoom: 10,
updateRefresh: false,
mapEntity: "",
xpathConstraint: "",
markerDisplayAttr: "",
latAttr: "",
lngAttr: "",
onClickMarkerMf: "",
mapHeight: "",
mapWidth: "",
markerTemplate: "<p>{Marker}</p>",
mapType: "OpenStreetMap_Mapnik",
customMapType: false,
customMapTypeUrl: "//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
customMapTypOptions: "{subdomains:'abc'}",
controlDragging: true,
controlTouchZoom: true,
controlScrollWheelZoom: true,
controlZoomControl: true,
controlZoomControlPosition: "topleft",
controlAttribution: true,
controlAttributionPosition: "bottomright",
controlFullscreen: false,
controlFullscreenPosition: "topright",
locateControl: false,
locateControlPosition: "topleft",
locateControlDrawCircle: true,
locateControlKeepZoomLevel: false,
scaleControl: false,
scaleControlPosition: "bottomleft",
scaleControlMetric: true,
scaleControlImperial: true,
scaleControlMaxWidth: 100,
// Internal variables
_markerCache: [],
_layerGroup: null,
_minZoom: 0,
_maxZoom: 20,
_defaultPosition: [],
_handle: null,
_contextObj: null,
_map: null,
postCreate: function () {
logger.debug(this.id + ".postCreate");
this._defaultPosition = [
parseFloat(this.defaultLat),
parseFloat(this.defaultLng)
];
this._minZoom = this.minZoom >= 0 ? this.minZoom : 0;
this._maxZoom = this.maxZoom > this.minZoom ? this.maxZoom : this.minZoom;
this._layerGroup = new LL.layerGroup();
},
update: function (obj, callback) {
logger.debug(this.id + ".update");
this._contextObj = obj;
this._resetSubscriptions();
if (!this._map) {
this._loadMap(callback);
} else {
this._fetchMarkers(callback);
}
},
resize: function (box) {
logger.debug(this.id + ".resize");
if (this._map) {
this._map.invalidateSize();
}
},
_resetSubscriptions: function () {
logger.debug(this.id + "._resetSubscriptions");
if (this._handle) {
logger.debug(this.id + "._resetSubscriptions unsubscribe", this._handle);
this.unsubscribe(this._handle);
this._handle = null;
}
if (this._contextObj) {
logger.debug(this.id + "._resetSubscriptions subscribe", this._contextObj.getGuid());
this._handle = this.subscribe({
guid: this._contextObj.getGuid(),
callback: lang.hitch(this, function (guid) {
this._fetchMarkers();
})
});
} else {
this._handle = this.subscribe({
entity: this.mapEntity,
callback: lang.hitch(this, function (entity) {
this._fetchMarkers();
})
});
}
},
_getMapLayer: function () {
var tileLayer = null;
if (this.customMapType && this.customMapTypeUrl) {
var options = null;
try {
options = JSON.parse(this.customMapTypeOptions);
} catch (e) {
console.error(this.id + "._getMapLayer error parsing Custom Map Options: " + e.toString());
options = {};
}
tileLayer = LL.tileLayer(this.customMapTypeUrl, options);
} else {
var providerName = this.mapType.replace(/_/g, ".");
tileLayer = LL.tileLayer.provider(providerName);
}
if (tileLayer.options.minZoom < this._minZoom) {
tileLayer.options.minZoom = this._minZoom;
}
if (tileLayer.options.maxZoom > this._maxZoom) {
tileLayer.options.maxZoom = this._maxZoom;
}
return tileLayer;
},
_loadMap: function (callback) {
logger.debug(this.id + "._loadMap");
domStyle.set(this.domNode, { height: this.mapHeight });
domStyle.set(this.mapContainer, {
height: this.mapHeight,
width: this.mapWidth
});
this._map = LL.map(this.mapContainer, {
dragging: this.controlDragging,
touchZoom: this.controlTouchZoom,
scrollWheelZoom: this.controlScrollWheelZoom,
zoomControl: this.controlZoomControl,
attributionControl: this.controlAttribution
}).setView(this._defaultPosition, this.lowestZoom);
if (this.controlZoomControl) {
this._map.zoomControl.setPosition(this.controlZoomControlPosition);
}
if (this.controlAttribution) {
this._map.attributionControl.setPosition(this.controlAttributionPosition);
}
if (this.controlFullscreen) {
LL.control.fullscreen({
position: "topright",
forceSeparateButton: true
}).addTo(this._map);
}
if (this.scaleControl) {
LL.control.scale({
position: this.scaleControlPosition,
maxWidth: this.scaleControlMaxWidth > 0 ? this.scaleControlMaxWidth : 100,
metric: this.scaleControlMetric,
imperial: this.scaleControlImperial
}).addTo(this._map);
}
if (this.locateControl) {
LL.control.locate({
position: this.locateControlPosition,
drawCircle: this.locateControlDrawCircle,
keepCurrentZoomLevel: this.locateControlKeepZoomLevel,
icon: "glyphicon glyphicon-screenshot", // Using glyphicons that are part of Mendix
iconLoading: "glyphicon glyphicon-refresh"
}).addTo(this._map);
}
this._map.addLayer(this._getMapLayer());
this._map.setZoom(this.lowestZoom); // trigger setzoom to make sure it is rendered
this._layerGroup.addTo(this._map);
this._fetchMarkers(callback);
},
_fetchMarkers: function (callback) {
logger.debug(this.id + "._fetchMarkers");
if (this.gotocontext) {
this._goToContext(callback);
} else {
if (this.updateRefresh) {
this._fetchFromDB(callback);
} else {
if (this._markerCache) {
this._fetchFromCache(callback);
} else {
this._fetchFromDB(callback);
}
}
}
},
_refreshMap: function (objs, callback) {
logger.debug(this.id + "._refreshMap");
var panPosition = this._defaultPosition,
positions = [];
dojoArray.forEach(objs, lang.hitch(this, function (obj) {
this._addMarker(obj);
var position = this._getLatLng(obj);
if (position) {
positions.push(position); // reversing lat lng for boundingExtent
panPosition = position;
} else {
logger.error(this.id + ": " + "Incorrect coordinates (" + this.checkAttrForDecimal(obj, this.latAttr) + "," + this.checkAttrForDecimal(obj, this.lngAttr) + ")");
}
}));
if (positions.length < 2) {
this._map.setZoom(this.lowestZoom);
this._map.panTo(panPosition);
} else {
this._map.fitBounds(positions);
}
mendix.lang.nullExec(callback);
},
_fetchFromDB: function (callback) {
logger.debug(this.id + "._fetchFromDB");
var xpath = "//" + this.mapEntity + this.xpathConstraint;
this._removeAllMarkers();
if (this._contextObj) {
xpath = xpath.replace("[%CurrentObject%]", this._contextObj.getGuid());
mx.data.get({
xpath: xpath,
callback: lang.hitch(this, function (objs) {
this._refreshMap(objs, callback);
})
});
} else if (!this._contextObj && (xpath.indexOf("[%CurrentObject%]") > -1)) {
console.warn("No context for xpath, not fetching.");
if (typeof callback === "function") {
callback();
}
} else {
mx.data.get({
xpath: xpath,
callback: lang.hitch(this, function (objs) {
this._refreshMap(objs, callback);
})
});
}
},
_fetchFromCache: function (callback) {
logger.debug(this.id + "._fetchFromCache");
var cached = false,
bounds = [];
this._removeAllMarkers();
dojoArray.forEach(this._markerCache, lang.hitch(this, function (markerObj, index) {
if (this._contextObj) {
if (markerObj.id === this._contextObj.getGuid()) {
markerObj.marker.addTo(this._map);
bounds.push(markerObj.loc);
cached = true;
}
} else {
markerObj.marker.addTo(this._map);
}
if (index === this._markerCache.length - 1) {
this._map.fitBounds(bounds);
}
}));
if (!cached) {
this._fetchFromDB(callback);
} else if (typeof callback === "function") {
callback();
}
},
_removeAllMarkers: function () {
logger.debug(this.id + "._removeAllMarkers");
if (this._map) {
this._layerGroup.clearLayers();
}
},
_addMarker: function (obj) {
logger.debug(this.id + "._addMarker");
var id = this._contextObj ? this._contextObj.getGuid() : null,
lat = parseFloat(this.checkAttrForDecimal(obj, this.latAttr)),
lng = parseFloat(this.checkAttrForDecimal(obj, this.lngAttr)),
loc = [lat, lng],
markerObj = {
context: id,
obj: obj,
marker: null,
loc: loc
};
var marker = LL.marker(loc);
if (this.onClickMarkerMf !== "") {
marker.on("click", lang.hitch(this, function (e) {
this._executeMf(this.onClickMarkerMf, obj);
}));
}
if (this.markerDisplayAttr) {
var template = this.markerTemplate !== "" ?
this.markerTemplate.replace("{Marker}", obj.get(this.markerDisplayAttr)) :
"<p>" + obj.get(this.markerDisplayAttr) + "<p/>";
marker.bindPopup(template, {
closeButton: false
});
}
this._layerGroup.addLayer(marker);
markerObj.marker = marker;
if (!this._markerCache) {
this._markerCache = [];
}
var found = false;
dojoArray.forEach(this._markerCache, lang.hitch(this, function (markerObj) {
if (markerObj.obj.getGuid() === obj.getGuid()) {
found = true;
}
}));
if (!found) {
this._markerCache.push(markerObj);
}
},
checkAttrForDecimal: function (obj, attr) {
logger.debug(this.id + ".checkAttrForDecimal");
if (obj.get(attr) === "Decimal") {
return obj.get(attr).toFixed(5);
} else {
return obj.get(attr);
}
},
_getLatLng: function (obj) {
logger.debug(this.id + "._getLatLng");
var lat = this.checkAttrForDecimal(obj, this.latAttr),
lng = this.checkAttrForDecimal(obj, this.lngAttr);
if (lat === "" && lng === "") {
return this._defaultPosition;
} else if (!isNaN(lat) && !isNaN(lng) && lat !== "" && lng !== "") {
return [
parseFloat(lat),
parseFloat(lng)
];
} else {
return null;
}
},
_goToContext: function (callback) {
logger.debug(this.id + "._goToContext");
this._removeAllMarkers();
if (this._map && this._contextObj) {
var objs = [];
if (this._contextObj) {
objs = [ this._contextObj ];
} else {
logger.error(this.id + "._goToContext: no Context object while you have set \"Pan to context\" in the Modeler! Showing default position");
}
this._refreshMap(objs, callback);
} else {
mendix.lang.nullExec(callback);
}
},
_executeMf: function(mf, obj) {
logger.debug(this.id + "._executeMf");
if (mf && obj && obj.getGuid()) {
mx.data.action({
store: {
caller: this.mxform
},
params: {
guids: [ obj.getGuid() ],
applyto: "selection",
actionname: mf
},
callback: lang.hitch(this, function() {
logger.debug(this.id + "._executeMf success");
}),
error: lang.hitch(this, function(e) {
console.error(this.id + "._executeMf failed, error: ", e.toString());
})
});
}
},
uninitialize: function () {
logger.debug(this.id + ".uninitialize");
if (this._map) {
this._map.remove();
this._markerCache = [];
}
}
});
});
require(["Leaflet/widget/Leaflet"], function() {});

View File

@@ -0,0 +1,3 @@
<div class="openLayers">
<div data-dojo-attach-point="mapContainer"></div>
</div>

View File

@@ -0,0 +1,520 @@
/* required styles */
.leaflet-map-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-pane,
.leaflet-tile-container,
.leaflet-overlay-pane,
.leaflet-shadow-pane,
.leaflet-marker-pane,
.leaflet-popup-pane,
.leaflet-overlay-pane svg,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
-ms-touch-action: none;
touch-action: none;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container img {
max-width: none !important;
}
/* stupid Android 2 doesn't understand "max-width: none" properly */
.leaflet-container img.leaflet-image-layer {
max-width: 15000px !important;
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-tile-pane { z-index: 2; }
.leaflet-objects-pane { z-index: 3; }
.leaflet-overlay-pane { z-index: 4; }
.leaflet-shadow-pane { z-index: 5; }
.leaflet-marker-pane { z-index: 6; }
.leaflet-popup-pane { z-index: 7; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 7;
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile,
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-tile-loaded,
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile,
.leaflet-touching .leaflet-zoom-animated {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-clickable {
cursor: pointer;
}
.leaflet-container {
cursor: -webkit-grab;
cursor: -moz-grab;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-container,
.leaflet-dragging .leaflet-clickable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline: 0;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-control-zoom-out {
font-size: 20px;
}
.leaflet-touch .leaflet-control-zoom-in {
font-size: 22px;
}
.leaflet-touch .leaflet-control-zoom-out {
font-size: 24px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: content-box;
box-sizing: content-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
}
.leaflet-popup-content p {
margin: 18px 0;
}
.leaflet-popup-tip-container {
margin: 0 auto;
width: 40px;
height: 20px;
position: relative;
overflow: hidden;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Leaflet Locate Control */
.leaflet-control-locate .glyphicon {
top: 4px;
}
.leaflet-control-locate a {
font-size: 1.4em;
color: #444;
}
.leaflet-control-locate.active a {
color: #2074B6;
}
.leaflet-control-locate.active.following a {
color: #FC8428;
}
/* Leaflet Fullscreen */
.fullscreen-icon {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAYElEQVR42u2UwQkAIAwDHckROpobdgRHURQRQcgngn3kIL9o+kibhAhFO+TA56eXDTLgs5dBFfgqG+Rj2vVRBr68PDbfkJQ98a3ytBhICvoQRJQhQr35hQ19gvijKkQYOnXBYeFRr/ZKAAAAAElFTkSuQmCC');
}
.leaflet-retina .fullscreen-icon {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAYAAADFeBvrAAAAq0lEQVR42u2Y0QmAMAwFHcHR3DAjOUJH6AhV/CgE0UJK6wPv4H0aOFtjmwUAAAAAfkV5yR6otzdqjqUhswbqrVVKSCg7mZhUVlshW+KYzgr1S5nKN5QHCuV5Qr4B2IAtZ65RDOfezaxH5qlOlfqY7Uyqb7uddD0jhZcpkahKpQ6hpChUeoIQQggh5ELb5scqcvThcMr1YdIFjys4Q5IJYywGjYyCAQAAAECZA/11ewjNJ8u0AAAAAElFTkSuQmCC');
background-size: 26px 26px;
}
.leaflet-container:-webkit-full-screen {
width: 100% !important;
height: 100% !important;
z-index: 99999;
}
.leaflet-pseudo-fullscreen {
position: fixed !important;
width: 100% !important;
height: 100% !important;
top: 0px !important;
left: 0px !important;
z-index: 99999;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

11
src/package.xml Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="OpenLayers" version="1.0.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="Leaflet/Leaflet.xml"/>
</widgetFiles>
<files>
<file path="Leaflet/widget/"/>
</files>
</clientModule>
</package>

BIN
test/Test.mpr Normal file

Binary file not shown.

BIN
test/widgets/Leaflet.mpk Normal file

Binary file not shown.

156
xsd/widget.xsd Normal file
View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.mendix.com/widget/1.0/"
xmlns="http://www.mendix.com/widget/1.0/"
elementFormDefault="qualified">
<xs:element name="widget" type="widgetType"/>
<xs:complexType name="widgetType">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="description" type="xs:string"/>
<xs:element name="icon" type="xs:base64Binary" minOccurs="0"/>
<xs:element name="properties" type="propertiesType"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="needsEntityContext" type="xs:boolean" use="required"/>
<xs:attribute name="mobile" type="xs:boolean" default="false"/>
</xs:complexType>
<xs:complexType name="propertiesType">
<xs:sequence>
<xs:element name="property" type="propertyType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="propertyType">
<xs:sequence>
<xs:element name="caption" type="xs:string"/>
<xs:element name="category" type="xs:string"/>
<xs:element name="description" type="xs:string"/>
<xs:element name="attributeTypes" type="attributeTypesType" minOccurs="0"/>
<xs:element name="enumerationValues" type="enumerationValuesType" minOccurs="0"/>
<xs:element name="properties" type="propertiesType" minOccurs="0"/>
<xs:element name="returnType" type="returnTypeType" minOccurs="0"/>
<xs:element name="translations" type="translationsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="key" type="xs:string" use="required"/>
<xs:attribute name="type" type="propertyTypeType" use="required"/>
<xs:attribute name="isList" type="xs:boolean" default="false"/>
<xs:attribute name="entityProperty" type="xs:string"/>
<xs:attribute name="allowNonPersistableEntities" type="xs:boolean" default="false"/>
<xs:attribute name="isPath" type="isPathType" default="no"/>
<xs:attribute name="pathType" type="pathTypeType"/>
<xs:attribute name="parameterIsList" type="xs:boolean" default="false"/>
<xs:attribute name="multiline" type="xs:boolean" default="false"/>
<xs:attribute name="defaultValue" type="xs:string" default=""/>
<xs:attribute name="required" type="xs:boolean" default="true"/>
<xs:attribute name="isDefault" type="xs:boolean" default="false"/>
</xs:complexType>
<xs:simpleType name="propertyTypeType">
<xs:restriction base="xs:string">
<xs:enumeration value="attribute"/>
<xs:enumeration value="boolean"/>
<xs:enumeration value="entity"/>
<xs:enumeration value="entityConstraint"/>
<xs:enumeration value="enumeration"/>
<xs:enumeration value="form"/>
<xs:enumeration value="image"/>
<xs:enumeration value="integer"/>
<xs:enumeration value="microflow"/>
<xs:enumeration value="object"/>
<xs:enumeration value="string"/>
<xs:enumeration value="translatableString"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="attributeTypesType">
<xs:sequence>
<xs:element name="attributeType" type="attributeTypeType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="attributeTypeType">
<xs:attribute name="name" type="attributeTypeNameType" use="required"/>
</xs:complexType>
<xs:simpleType name="attributeTypeNameType">
<xs:restriction base="xs:string">
<xs:enumeration value="AutoNumber"/>
<xs:enumeration value="Binary"/>
<xs:enumeration value="Boolean"/>
<xs:enumeration value="Currency"/>
<xs:enumeration value="DateTime"/>
<xs:enumeration value="Enum"/>
<xs:enumeration value="Float"/>
<xs:enumeration value="HashString"/>
<xs:enumeration value="Integer"/>
<xs:enumeration value="Long"/>
<xs:enumeration value="String"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="isPathType">
<xs:restriction base="xs:string">
<xs:enumeration value="no"/>
<xs:enumeration value="optional"/>
<xs:enumeration value="yes"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="pathTypeType">
<xs:restriction base="xs:string">
<xs:enumeration value="reference"/>
<xs:enumeration value="referenceSet"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="enumerationValuesType">
<xs:sequence>
<xs:element name="enumerationValue" type="enumerationValueType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="enumerationValueType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="key" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="returnTypeType">
<xs:attribute name="type" type="returnTypeTypeType" use="required"/>
<xs:attribute name="isList" type="xs:boolean" default="false"/>
<xs:attribute name="entityProperty" type="xs:string"/>
</xs:complexType>
<xs:simpleType name="returnTypeTypeType">
<xs:restriction base="xs:string">
<xs:enumeration value="Void"/>
<xs:enumeration value="Boolean"/>
<xs:enumeration value="Integer"/>
<xs:enumeration value="Float"/>
<xs:enumeration value="DateTime"/>
<xs:enumeration value="String"/>
<xs:enumeration value="Object"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="translationsType">
<xs:sequence>
<xs:element name="translation" type="translationType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="translationType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="lang" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>