* added static map field * leaflet draw plugin added to map control * Editable layer working no icons * Save and load data in form field * images and icons working * Locate plugin added, and loads on geolocation * organized map control code * loads layer as per form * new form clears editableLayers * update: leaflet 1.2.0 and leaflet-draw 0.4.2 * changed to ERPNext colors * Multiple map fields and fixes * fixes codacy suggestions * loaded geojson data editable * Map layers editable * add only one layer on draw:create * code organized with helper functions * Replaced Leaflet Draw with Leaflet Editable * read values from database * layers saved in db * Using Patched Leaflet Draw (mobile-friendly) * Working Map field with multiple forms open * Leaflet Draw css image path fix * Leaflet Draw Fixes Leaflet draw css image paths Fixes Circle marker and Circle * locate to geolocation or set featurecollection as center * [fix] leaflet fitBounds padding 50,50 * [Fix] Leaflet (auto) locate * Map field basic test * added refresh button on map * Mute map geojson in print format * renamed Map field to Geolocation * Suggested changes and fixes * eslint disable * csslint allow importantversion-14
@@ -119,6 +119,7 @@ | |||||
"getCookies": true, | "getCookies": true, | ||||
"get_url_arg": true, | "get_url_arg": true, | ||||
"QUnit": true, | "QUnit": true, | ||||
"JsBarcode": true | |||||
"JsBarcode": true, | |||||
"L": true | |||||
} | } | ||||
} | } |
@@ -96,7 +96,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "fieldtype", | "oldfieldname": "fieldtype", | ||||
"oldfieldtype": "Select", | "oldfieldtype": "Select", | ||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", | |||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
@@ -1364,7 +1364,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 1, | "istable": 1, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2017-10-07 19:20:15.888708", | |||||
"modified": "2017-10-24 11:39:56.795852", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "DocField", | "name": "DocField", | ||||
@@ -220,7 +220,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "fieldtype", | "oldfieldname": "fieldtype", | ||||
"oldfieldtype": "Select", | "oldfieldtype": "Select", | ||||
"options": "Attach\nAttach Image\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", | |||||
"options": "Attach\nAttach Image\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
@@ -1161,7 +1161,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2017-07-06 17:23:43.835189", | |||||
"modified": "2017-10-24 11:40:37.986457", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Custom", | "module": "Custom", | ||||
"name": "Custom Field", | "name": "Custom Field", | ||||
@@ -0,0 +1,23 @@ | |||||
/* eslint-disable */ | |||||
// rename this file from _test_[name] to test_[name] to activate | |||||
// and remove above this line | |||||
QUnit.test("test: Custom Field", function (assert) { | |||||
let done = assert.async(); | |||||
// number of asserts | |||||
assert.expect(1); | |||||
frappe.run_serially([ | |||||
// insert a new Custom Field | |||||
() => frappe.tests.make('Custom Field', [ | |||||
// values to be set | |||||
{key: 'value'} | |||||
]), | |||||
() => { | |||||
assert.equal(cur_frm.doc.key, 'value'); | |||||
}, | |||||
() => done() | |||||
]); | |||||
}); |
@@ -66,7 +66,7 @@ docfield_properties = { | |||||
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), | allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), | ||||
('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature'), ('Data', 'Select'), | ('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature'), ('Data', 'Select'), | ||||
('Text', 'Small Text'), ('Text', 'Data', 'Barcode')) | |||||
('Text', 'Small Text'), ('Text', 'Data', 'Barcode'), ('Code', 'Geolocation')) | |||||
allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select',) | allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select',) | ||||
@@ -94,7 +94,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "fieldtype", | "oldfieldname": "fieldtype", | ||||
"oldfieldtype": "Select", | "oldfieldtype": "Select", | ||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nText\nText Editor\nTime", | |||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nText\nText Editor\nTime", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
@@ -1202,7 +1202,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 1, | "istable": 1, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2017-10-11 06:45:20.172291", | |||||
"modified": "2017-10-24 11:41:31.075929", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Custom", | "module": "Custom", | ||||
"name": "Customize Form Field", | "name": "Customize Form Field", | ||||
@@ -26,25 +26,26 @@ type_map = { | |||||
,'Float': ('decimal', '18,6') | ,'Float': ('decimal', '18,6') | ||||
,'Percent': ('decimal', '18,6') | ,'Percent': ('decimal', '18,6') | ||||
,'Check': ('int', '1') | ,'Check': ('int', '1') | ||||
,'Small Text': ('text', '') | |||||
,'Long Text': ('longtext', '') | |||||
,'Small Text': ('text', '') | |||||
,'Long Text': ('longtext', '') | |||||
,'Code': ('longtext', '') | ,'Code': ('longtext', '') | ||||
,'Text Editor': ('longtext', '') | |||||
,'Text Editor': ('longtext', '') | |||||
,'Date': ('date', '') | ,'Date': ('date', '') | ||||
,'Datetime': ('datetime', '6') | |||||
,'Datetime': ('datetime', '6') | |||||
,'Time': ('time', '6') | ,'Time': ('time', '6') | ||||
,'Text': ('text', '') | ,'Text': ('text', '') | ||||
,'Data': ('varchar', varchar_len) | ,'Data': ('varchar', varchar_len) | ||||
,'Link': ('varchar', varchar_len) | ,'Link': ('varchar', varchar_len) | ||||
,'Dynamic Link':('varchar', varchar_len) | |||||
,'Password': ('varchar', varchar_len) | |||||
,'Dynamic Link': ('varchar', varchar_len) | |||||
,'Password': ('varchar', varchar_len) | |||||
,'Select': ('varchar', varchar_len) | ,'Select': ('varchar', varchar_len) | ||||
,'Read Only': ('varchar', varchar_len) | |||||
,'Read Only': ('varchar', varchar_len) | |||||
,'Attach': ('text', '') | ,'Attach': ('text', '') | ||||
,'Attach Image':('text', '') | |||||
,'Signature': ('longtext', '') | |||||
,'Attach Image': ('text', '') | |||||
,'Signature': ('longtext', '') | |||||
,'Color': ('varchar', varchar_len) | ,'Color': ('varchar', varchar_len) | ||||
,'Barcode': ('longtext', '') | ,'Barcode': ('longtext', '') | ||||
,'Geolocation': ('longtext', '') | |||||
} | } | ||||
default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', | default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', | ||||
@@ -53,7 +53,8 @@ | |||||
"public/js/frappe/form/controls/html.js", | "public/js/frappe/form/controls/html.js", | ||||
"public/js/frappe/form/controls/heading.js", | "public/js/frappe/form/controls/heading.js", | ||||
"public/js/frappe/form/controls/autocomplete.js", | "public/js/frappe/form/controls/autocomplete.js", | ||||
"public/js/frappe/form/controls/barcode.js" | |||||
"public/js/frappe/form/controls/barcode.js", | |||||
"public/js/frappe/form/controls/geolocation.js" | |||||
], | ], | ||||
"js/dialog.min.js": [ | "js/dialog.min.js": [ | ||||
"public/js/frappe/dom.js", | "public/js/frappe/dom.js", | ||||
@@ -94,12 +95,17 @@ | |||||
"public/js/frappe/form/controls/read_only.js", | "public/js/frappe/form/controls/read_only.js", | ||||
"public/js/frappe/form/controls/button.js", | "public/js/frappe/form/controls/button.js", | ||||
"public/js/frappe/form/controls/html.js", | "public/js/frappe/form/controls/html.js", | ||||
"public/js/frappe/form/controls/heading.js" | |||||
"public/js/frappe/form/controls/heading.js", | |||||
"public/js/frappe/form/controls/geolocation.js" | |||||
], | ], | ||||
"css/desk.min.css": [ | "css/desk.min.css": [ | ||||
"public/js/lib/datepicker/datepicker.min.css", | "public/js/lib/datepicker/datepicker.min.css", | ||||
"public/js/lib/awesomplete/awesomplete.css", | "public/js/lib/awesomplete/awesomplete.css", | ||||
"public/js/lib/summernote/summernote.css", | "public/js/lib/summernote/summernote.css", | ||||
"public/js/lib/leaflet/leaflet.css", | |||||
"public/js/lib/leaflet/leaflet.draw.css", | |||||
"public/js/lib/leaflet/L.Control.Locate.css", | |||||
"public/js/lib/leaflet/easy-button.css", | |||||
"public/css/bootstrap.css", | "public/css/bootstrap.css", | ||||
"public/css/font-awesome.css", | "public/css/font-awesome.css", | ||||
"public/css/octicons/octicons.css", | "public/css/octicons/octicons.css", | ||||
@@ -137,7 +143,11 @@ | |||||
"public/js/lib/datepicker/datepicker.min.js", | "public/js/lib/datepicker/datepicker.min.js", | ||||
"public/js/lib/datepicker/locale-all.js", | "public/js/lib/datepicker/locale-all.js", | ||||
"public/js/lib/jquery.jrumble.min.js", | "public/js/lib/jquery.jrumble.min.js", | ||||
"public/js/lib/webcam.min.js" | |||||
"public/js/lib/webcam.min.js", | |||||
"public/js/lib/leaflet/leaflet.js", | |||||
"public/js/lib/leaflet/leaflet.draw.js", | |||||
"public/js/lib/leaflet/L.Control.Locate.js", | |||||
"public/js/lib/leaflet/easy-button.js" | |||||
], | ], | ||||
"js/desk.min.js": [ | "js/desk.min.js": [ | ||||
"public/js/frappe/class.js", | "public/js/frappe/class.js", | ||||
@@ -0,0 +1,156 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:xlink="http://www.w3.org/1999/xlink" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
viewBox="0 0 600 60" | |||||
height="60" | |||||
width="600" | |||||
id="svg4225" | |||||
version="1.1" | |||||
inkscape:version="0.91 r13725" | |||||
sodipodi:docname="spritesheet.svg" | |||||
inkscape:export-filename="/home/fpuga/development/upstream/icarto.Leaflet.draw/src/images/spritesheet-2x.png" | |||||
inkscape:export-xdpi="90" | |||||
inkscape:export-ydpi="90"> | |||||
<metadata | |||||
id="metadata4258"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title /> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<defs | |||||
id="defs4256" /> | |||||
<sodipodi:namedview | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1" | |||||
objecttolerance="10" | |||||
gridtolerance="10" | |||||
guidetolerance="10" | |||||
inkscape:pageopacity="0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:window-width="1920" | |||||
inkscape:window-height="1056" | |||||
id="namedview4254" | |||||
showgrid="false" | |||||
inkscape:zoom="1.3101852" | |||||
inkscape:cx="237.56928" | |||||
inkscape:cy="7.2419621" | |||||
inkscape:window-x="1920" | |||||
inkscape:window-y="24" | |||||
inkscape:window-maximized="1" | |||||
inkscape:current-layer="svg4225" /> | |||||
<g | |||||
id="enabled" | |||||
style="fill:#464646;fill-opacity:1"> | |||||
<g | |||||
id="polyline" | |||||
style="fill:#464646;fill-opacity:1"> | |||||
<path | |||||
d="m 18,36 0,6 6,0 0,-6 -6,0 z m 4,4 -2,0 0,-2 2,0 0,2 z" | |||||
id="path4229" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<path | |||||
d="m 36,18 0,6 6,0 0,-6 -6,0 z m 4,4 -2,0 0,-2 2,0 0,2 z" | |||||
id="path4231" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<path | |||||
d="m 23.142,39.145 -2.285,-2.29 16,-15.998 2.285,2.285 z" | |||||
id="path4233" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
</g> | |||||
<path | |||||
id="polygon" | |||||
d="M 100,24.565 97.904,39.395 83.07,42 76,28.773 86.463,18 Z" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<path | |||||
id="rectangle" | |||||
d="m 140,20 20,0 0,20 -20,0 z" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<path | |||||
id="circle" | |||||
d="m 221,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<path | |||||
id="marker" | |||||
d="m 270,19 c -4.971,0 -9,4.029 -9,9 0,4.971 5.001,12 9,14 4.001,-2 9,-9.029 9,-14 0,-4.971 -4.029,-9 -9,-9 z m 0,12.5 c -2.484,0 -4.5,-2.014 -4.5,-4.5 0,-2.484 2.016,-4.5 4.5,-4.5 2.485,0 4.5,2.016 4.5,4.5 0,2.486 -2.015,4.5 -4.5,4.5 z" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<g | |||||
id="edit" | |||||
style="fill:#464646;fill-opacity:1"> | |||||
<path | |||||
d="m 337,30.156 0,0.407 0,5.604 c 0,1.658 -1.344,3 -3,3 l -10,0 c -1.655,0 -3,-1.342 -3,-3 l 0,-10 c 0,-1.657 1.345,-3 3,-3 l 6.345,0 3.19,-3.17 -9.535,0 c -3.313,0 -6,2.687 -6,6 l 0,10 c 0,3.313 2.687,6 6,6 l 10,0 c 3.314,0 6,-2.687 6,-6 l 0,-8.809 -3,2.968" | |||||
id="path4240" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<path | |||||
d="m 338.72,24.637 -8.892,8.892 -2.828,0 0,-2.829 8.89,-8.89 z" | |||||
id="path4242" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<path | |||||
d="m 338.697,17.826 4,0 0,4 -4,0 z" | |||||
transform="matrix(-0.70698336,-0.70723018,0.70723018,-0.70698336,567.55917,274.78273)" | |||||
id="path4244" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
</g> | |||||
<g | |||||
id="remove" | |||||
style="fill:#464646;fill-opacity:1"> | |||||
<path | |||||
d="m 381,42 18,0 0,-18 -18,0 0,18 z m 14,-16 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z" | |||||
id="path4247" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
<path | |||||
d="m 395,20 0,-4 -10,0 0,4 -6,0 0,2 22,0 0,-2 -6,0 z m -2,0 -6,0 0,-2 6,0 0,2 z" | |||||
id="path4249" | |||||
inkscape:connector-curvature="0" | |||||
style="fill:#464646;fill-opacity:1" /> | |||||
</g> | |||||
</g> | |||||
<g | |||||
id="disabled" | |||||
transform="translate(120,0)" | |||||
style="fill:#bbbbbb"> | |||||
<use | |||||
xlink:href="#edit" | |||||
id="edit-disabled" | |||||
x="0" | |||||
y="0" | |||||
width="100%" | |||||
height="100%" /> | |||||
<use | |||||
xlink:href="#remove" | |||||
id="remove-disabled" | |||||
x="0" | |||||
y="0" | |||||
width="100%" | |||||
height="100%" /> | |||||
</g> | |||||
<path | |||||
style="fill:none;stroke:#464646;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
id="circle-3" | |||||
d="m 581.65725,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z" | |||||
inkscape:connector-curvature="0" /> | |||||
</svg> |
@@ -10,6 +10,11 @@ frappe.dom = { | |||||
by_id: function(id) { | by_id: function(id) { | ||||
return document.getElementById(id); | return document.getElementById(id); | ||||
}, | }, | ||||
get_unique_id: function() { | |||||
const id = 'unique-' + frappe.dom.id_count; | |||||
frappe.dom.id_count++; | |||||
return id; | |||||
}, | |||||
set_unique_id: function(ele) { | set_unique_id: function(ele) { | ||||
var $ele = $(ele); | var $ele = $(ele); | ||||
if($ele.attr('id')) { | if($ele.attr('id')) { | ||||
@@ -0,0 +1,183 @@ | |||||
frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ | |||||
make_wrapper() { | |||||
// Create the elements for map area | |||||
this._super(); | |||||
let $input_wrapper = this.$wrapper.find('.control-input-wrapper'); | |||||
this.map_id = frappe.dom.get_unique_id(); | |||||
this.map_area = $( | |||||
`<div class="map-wrapper border"> | |||||
<div id="` + this.map_id + `" style="min-height: 400px; z-index: 1; max-width:100%"></div> | |||||
</div>` | |||||
); | |||||
this.map_area.prependTo($input_wrapper); | |||||
this.$wrapper.find('.control-input').addClass("hidden"); | |||||
this.bind_leaflet_map(); | |||||
this.bind_leaflet_draw_control(); | |||||
this.bind_leaflet_locate_control(); | |||||
this.bind_leaflet_refresh_button(); | |||||
}, | |||||
format_for_input(value) { | |||||
// render raw value from db into map | |||||
this.clear_editable_layers(); | |||||
if(value) { | |||||
var data_layers = new L.FeatureGroup() | |||||
.addLayer(L.geoJson(JSON.parse(value),{ | |||||
pointToLayer: function(geoJsonPoint, latlng) { | |||||
if (geoJsonPoint.properties.point_type == "circle"){ | |||||
return L.circle(latlng, {radius: geoJsonPoint.properties.radius}); | |||||
} else if (geoJsonPoint.properties.point_type == "circlemarker") { | |||||
return L.circleMarker(latlng, {radius: geoJsonPoint.properties.radius}); | |||||
} | |||||
else { | |||||
return L.marker(latlng); | |||||
} | |||||
} | |||||
})); | |||||
this.add_non_group_layers(data_layers, this.editableLayers); | |||||
try { | |||||
this.map.flyToBounds(this.editableLayers.getBounds(), { | |||||
padding: [50,50] | |||||
}); | |||||
} | |||||
catch(err) { | |||||
// suppress error if layer has a point. | |||||
} | |||||
this.editableLayers.addTo(this.map); | |||||
} else if ((value===undefined) || (value == JSON.stringify(new L.FeatureGroup().toGeoJSON()))) { | |||||
this.locate_control.start(); | |||||
} | |||||
}, | |||||
bind_leaflet_map() { | |||||
var circleToGeoJSON = L.Circle.prototype.toGeoJSON; | |||||
L.Circle.include({ | |||||
toGeoJSON: function() { | |||||
var feature = circleToGeoJSON.call(this); | |||||
feature.properties = { | |||||
point_type: 'circle', | |||||
radius: this.getRadius() | |||||
}; | |||||
return feature; | |||||
} | |||||
}); | |||||
L.CircleMarker.include({ | |||||
toGeoJSON: function() { | |||||
var feature = circleToGeoJSON.call(this); | |||||
feature.properties = { | |||||
point_type: 'circlemarker', | |||||
radius: this.getRadius() | |||||
}; | |||||
return feature; | |||||
} | |||||
}); | |||||
L.Icon.Default.imagePath = '/assets/frappe/images/leaflet/'; | |||||
this.map = L.map(this.map_id).setView([19.0800, 72.8961], 13); | |||||
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { | |||||
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' | |||||
}).addTo(this.map); | |||||
}, | |||||
bind_leaflet_locate_control() { | |||||
// To request location update and set location, sets current geolocation on load | |||||
this.locate_control = L.control.locate({position:'topright'}); | |||||
this.locate_control.addTo(this.map); | |||||
}, | |||||
bind_leaflet_draw_control() { | |||||
this.editableLayers = new L.FeatureGroup(); | |||||
var options = { | |||||
position: 'topleft', | |||||
draw: { | |||||
polyline: { | |||||
shapeOptions: { | |||||
color: frappe.ui.color.get('blue'), | |||||
weight: 10 | |||||
} | |||||
}, | |||||
polygon: { | |||||
allowIntersection: false, // Restricts shapes to simple polygons | |||||
drawError: { | |||||
color: frappe.ui.color.get('orange'), // Color the shape will turn when intersects | |||||
message: '<strong>Oh snap!<strong> you can\'t draw that!' // Message that will show when intersect | |||||
}, | |||||
shapeOptions: { | |||||
color: frappe.ui.color.get('blue') | |||||
} | |||||
}, | |||||
circle: true, | |||||
rectangle: { | |||||
shapeOptions: { | |||||
clickable: false | |||||
} | |||||
} | |||||
}, | |||||
edit: { | |||||
featureGroup: this.editableLayers, //REQUIRED!! | |||||
remove: true | |||||
} | |||||
}; | |||||
// create control and add to map | |||||
var drawControl = new L.Control.Draw(options); | |||||
this.map.addControl(drawControl); | |||||
this.map.on('draw:created', (e) => { | |||||
var type = e.layerType, | |||||
layer = e.layer; | |||||
if (type === 'marker') { | |||||
layer.bindPopup('Marker'); | |||||
} | |||||
this.editableLayers.addLayer(layer); | |||||
this.set_value(JSON.stringify(this.editableLayers.toGeoJSON())); | |||||
}); | |||||
this.map.on('draw:deleted draw:edited', (e) => { | |||||
var layer = e.layer; | |||||
this.editableLayers.removeLayer(layer); | |||||
this.set_value(JSON.stringify(this.editableLayers.toGeoJSON())); | |||||
}); | |||||
}, | |||||
bind_leaflet_refresh_button() { | |||||
L.easyButton({ | |||||
id: 'refresh-map-'+this.df.fieldname, | |||||
position: 'topright', | |||||
type: 'replace', | |||||
leafletClasses: true, | |||||
states:[{ | |||||
stateName: 'refresh-map', | |||||
onClick: function(button, map){ | |||||
map._onResize(); | |||||
}, | |||||
title: 'Refresh map', | |||||
icon: 'fa fa-refresh' | |||||
}] | |||||
}).addTo(this.map); | |||||
}, | |||||
add_non_group_layers(source_layer, target_group) { | |||||
// https://gis.stackexchange.com/a/203773 | |||||
// Would benefit from https://github.com/Leaflet/Leaflet/issues/4461 | |||||
if (source_layer instanceof L.LayerGroup) { | |||||
source_layer.eachLayer((layer)=>{ | |||||
this.add_non_group_layers(layer, target_group); | |||||
}); | |||||
} else { | |||||
target_group.addLayer(source_layer); | |||||
} | |||||
}, | |||||
clear_editable_layers() { | |||||
this.editableLayers.eachLayer((l)=>{ | |||||
this.editableLayers.removeLayer(l); | |||||
}); | |||||
} | |||||
}); |
@@ -0,0 +1,12 @@ | |||||
/* Compatible with Leaflet 0.7 */ | |||||
.leaflet-control-locate a { | |||||
font-size: 1.4em; | |||||
color: #444; | |||||
cursor: pointer; | |||||
} | |||||
.leaflet-control-locate.active a { | |||||
color: #2074B6; | |||||
} | |||||
.leaflet-control-locate.active.following a { | |||||
color: #FC8428; | |||||
} |
@@ -0,0 +1,591 @@ | |||||
/*! | |||||
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'], 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.Control.Locate = factory(L); | |||||
} | |||||
} (function (L) { | |||||
var LDomUtilApplyClassesMethod = function(method, element, classNames) { | |||||
classNames = classNames.split(' '); | |||||
classNames.forEach(function(className) { | |||||
L.DomUtil[method].call(this, element, className); | |||||
}); | |||||
}; | |||||
var addClasses = function(el, names) { LDomUtilApplyClassesMethod('addClass', el, names); }; | |||||
var removeClasses = function(el, names) { LDomUtilApplyClassesMethod('removeClass', el, names); }; | |||||
var LocateControl = L.Control.extend({ | |||||
options: { | |||||
/** Position of the control */ | |||||
position: 'topleft', | |||||
/** The layer that the user's location should be drawn on. By default creates a new layer. */ | |||||
layer: undefined, | |||||
/** | |||||
* Automatically sets the map view (zoom and pan) to the user's location as it updates. | |||||
* While the map is following the user's location, the control is in the `following` state, | |||||
* which changes the style of the control and the circle marker. | |||||
* | |||||
* Possible values: | |||||
* - false: never updates the map view when location changes. | |||||
* - 'once': set the view when the location is first determined | |||||
* - 'always': always updates the map view when location changes. | |||||
* The map view follows the users location. | |||||
* - 'untilPan': (default) like 'always', except stops updating the | |||||
* view if the user has manually panned the map. | |||||
* The map view follows the users location until she pans. | |||||
*/ | |||||
setView: 'untilPan', | |||||
/** Keep the current map zoom level when setting the view and only pan. */ | |||||
keepCurrentZoomLevel: false, | |||||
/** Smooth pan and zoom to the location of the marker. Only works in Leaflet 1.0+. */ | |||||
flyTo: false, | |||||
/** | |||||
* The user location can be inside and outside the current view when the user clicks on the | |||||
* control that is already active. Both cases can be configures separately. | |||||
* Possible values are: | |||||
* - 'setView': zoom and pan to the current location | |||||
* - 'stop': stop locating and remove the location marker | |||||
*/ | |||||
clickBehavior: { | |||||
/** What should happen if the user clicks on the control while the location is within the current view. */ | |||||
inView: 'stop', | |||||
/** What should happen if the user clicks on the control while the location is outside the current view. */ | |||||
outOfView: 'setView', | |||||
}, | |||||
/** | |||||
* If set, save the map bounds just before centering to the user's | |||||
* location. When control is disabled, set the view back to the | |||||
* bounds that were saved. | |||||
*/ | |||||
returnToPrevBounds: false, | |||||
/** | |||||
* Keep a cache of the location after the user deactivates the control. If set to false, the user has to wait | |||||
* until the locate API returns a new location before they see where they are again. | |||||
*/ | |||||
cacheLocation: true, | |||||
/** If set, a circle that shows the location accuracy is drawn. */ | |||||
drawCircle: true, | |||||
/** If set, the marker at the users' location is drawn. */ | |||||
drawMarker: true, | |||||
/** The class to be used to create the marker. For example L.CircleMarker or L.Marker */ | |||||
markerClass: L.CircleMarker, | |||||
/** Accuracy circle style properties. */ | |||||
circleStyle: { | |||||
color: '#136AEC', | |||||
fillColor: '#136AEC', | |||||
fillOpacity: 0.15, | |||||
weight: 2, | |||||
opacity: 0.5 | |||||
}, | |||||
/** Inner marker style properties. Only works if your marker class supports `setStyle`. */ | |||||
markerStyle: { | |||||
color: '#136AEC', | |||||
fillColor: '#2A93EE', | |||||
fillOpacity: 0.7, | |||||
weight: 2, | |||||
opacity: 0.9, | |||||
radius: 5 | |||||
}, | |||||
/** | |||||
* Changes to accuracy circle and inner marker while following. | |||||
* It is only necessary to provide the properties that should change. | |||||
*/ | |||||
followCircleStyle: {}, | |||||
followMarkerStyle: { | |||||
// color: '#FFA500', | |||||
// fillColor: '#FFB000' | |||||
}, | |||||
/** The CSS class for the icon. For example fa-location-arrow or fa-map-marker */ | |||||
icon: 'fa fa-map-marker', | |||||
iconLoading: 'fa fa-spinner fa-spin', | |||||
/** The element to be created for icons. For example span or i */ | |||||
iconElementTag: 'span', | |||||
/** Padding around the accuracy circle. */ | |||||
circlePadding: [0, 0], | |||||
/** Use metric units. */ | |||||
metric: true, | |||||
/** | |||||
* This callback can be used in case you would like to override button creation behavior. | |||||
* This is useful for DOM manipulation frameworks such as angular etc. | |||||
* This function should return an object with HtmlElement for the button (link property) and the icon (icon property). | |||||
*/ | |||||
createButtonCallback: function (container, options) { | |||||
var link = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container); | |||||
link.title = options.strings.title; | |||||
var icon = L.DomUtil.create(options.iconElementTag, options.icon, link); | |||||
return { link: link, icon: icon }; | |||||
}, | |||||
/** This event is called in case of any location error that is not a time out error. */ | |||||
onLocationError: function(err, control) { | |||||
alert(err.message); | |||||
}, | |||||
/** | |||||
* This even is called when the user's location is outside the bounds set on the map. | |||||
* The event is called repeatedly when the location changes. | |||||
*/ | |||||
onLocationOutsideMapBounds: function(control) { | |||||
control.stop(); | |||||
alert(control.options.strings.outsideMapBoundsMsg); | |||||
}, | |||||
/** Display a pop-up when the user click on the inner marker. */ | |||||
showPopup: true, | |||||
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" | |||||
}, | |||||
/** The default options passed to leaflets locate method. */ | |||||
locateOptions: { | |||||
maxZoom: Infinity, | |||||
watch: true, // if you overwrite this, visualization cannot be updated | |||||
setView: false // have to set this to false because we have to | |||||
// do setView manually | |||||
} | |||||
}, | |||||
initialize: function (options) { | |||||
// set default options if nothing is set (merge one step deep) | |||||
for (var i in options) { | |||||
if (typeof this.options[i] === 'object') { | |||||
L.extend(this.options[i], options[i]); | |||||
} else { | |||||
this.options[i] = options[i]; | |||||
} | |||||
} | |||||
// extend the follow marker style and circle from the normal style | |||||
this.options.followMarkerStyle = L.extend({}, this.options.markerStyle, this.options.followMarkerStyle); | |||||
this.options.followCircleStyle = L.extend({}, this.options.circleStyle, this.options.followCircleStyle); | |||||
}, | |||||
/** | |||||
* Add control to map. Returns the container for the control. | |||||
*/ | |||||
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; | |||||
this._prevBounds = null; | |||||
var linkAndIcon = this.options.createButtonCallback(container, this.options); | |||||
this._link = linkAndIcon.link; | |||||
this._icon = linkAndIcon.icon; | |||||
L.DomEvent | |||||
.on(this._link, 'click', L.DomEvent.stopPropagation) | |||||
.on(this._link, 'click', L.DomEvent.preventDefault) | |||||
.on(this._link, 'click', this._onClick, this) | |||||
.on(this._link, 'dblclick', L.DomEvent.stopPropagation); | |||||
this._resetVariables(); | |||||
this._map.on('unload', this._unload, this); | |||||
return container; | |||||
}, | |||||
/** | |||||
* This method is called when the user clicks on the control. | |||||
*/ | |||||
_onClick: function() { | |||||
this._justClicked = true; | |||||
this._userPanned = false; | |||||
if (this._active && !this._event) { | |||||
// click while requesting | |||||
this.stop(); | |||||
} else if (this._active && this._event !== undefined) { | |||||
var behavior = this._map.getBounds().contains(this._event.latlng) ? | |||||
this.options.clickBehavior.inView : this.options.clickBehavior.outOfView; | |||||
switch (behavior) { | |||||
case 'setView': | |||||
this.setView(); | |||||
break; | |||||
case 'stop': | |||||
this.stop(); | |||||
if (this.options.returnToPrevBounds) { | |||||
var f = this.options.flyTo ? this._map.flyToBounds : this._map.fitBounds; | |||||
f.bind(this._map)(this._prevBounds); | |||||
} | |||||
break; | |||||
} | |||||
} else { | |||||
if (this.options.returnToPrevBounds) { | |||||
this._prevBounds = this._map.getBounds(); | |||||
} | |||||
this.start(); | |||||
} | |||||
this._updateContainerStyle(); | |||||
}, | |||||
/** | |||||
* Starts the plugin: | |||||
* - activates the engine | |||||
* - draws the marker (if coordinates available) | |||||
*/ | |||||
start: function() { | |||||
this._activate(); | |||||
if (this._event) { | |||||
this._drawMarker(this._map); | |||||
// if we already have a location but the user clicked on the control | |||||
if (this.options.setView) { | |||||
this.setView(); | |||||
} | |||||
} | |||||
this._updateContainerStyle(); | |||||
}, | |||||
/** | |||||
* Stops the plugin: | |||||
* - deactivates the engine | |||||
* - reinitializes the button | |||||
* - removes the marker | |||||
*/ | |||||
stop: function() { | |||||
this._deactivate(); | |||||
this._cleanClasses(); | |||||
this._resetVariables(); | |||||
this._removeMarker(); | |||||
}, | |||||
/** | |||||
* 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 true. | |||||
*/ | |||||
_activate: function() { | |||||
if (!this._active) { | |||||
this._map.locate(this.options.locateOptions); | |||||
this._active = true; | |||||
// bind event listeners | |||||
this._map.on('locationfound', this._onLocationFound, this); | |||||
this._map.on('locationerror', this._onLocationError, this); | |||||
this._map.on('dragstart', this._onDrag, this); | |||||
} | |||||
}, | |||||
/** | |||||
* Called to stop the location engine. | |||||
* | |||||
* Override it to shutdown any functionalities you added on start. | |||||
*/ | |||||
_deactivate: function() { | |||||
this._map.stopLocate(); | |||||
this._active = false; | |||||
if (!this.options.cacheLocation) { | |||||
this._event = undefined; | |||||
} | |||||
// unbind event listeners | |||||
this._map.off('locationfound', this._onLocationFound, this); | |||||
this._map.off('locationerror', this._onLocationError, this); | |||||
this._map.off('dragstart', this._onDrag, this); | |||||
}, | |||||
/** | |||||
* Zoom (unless we should keep the zoom level) and an to the current view. | |||||
*/ | |||||
setView: function() { | |||||
this._drawMarker(); | |||||
if (this._isOutsideMapBounds()) { | |||||
this._event = undefined; // clear the current location so we can get back into the bounds | |||||
this.options.onLocationOutsideMapBounds(this); | |||||
} else { | |||||
if (this.options.keepCurrentZoomLevel) { | |||||
var f = this.options.flyTo ? this._map.flyTo : this._map.panTo; | |||||
f.bind(this._map)([this._event.latitude, this._event.longitude]); | |||||
} else { | |||||
var f = this.options.flyTo ? this._map.flyToBounds : this._map.fitBounds; | |||||
f.bind(this._map)(this._event.bounds, { | |||||
padding: this.options.circlePadding, | |||||
maxZoom: this.options.locateOptions.maxZoom | |||||
}); | |||||
} | |||||
} | |||||
}, | |||||
/** | |||||
* Draw the marker and accuracy circle on the map. | |||||
* | |||||
* Uses the event retrieved from onLocationFound from the map. | |||||
*/ | |||||
_drawMarker: function() { | |||||
if (this._event.accuracy === undefined) { | |||||
this._event.accuracy = 0; | |||||
} | |||||
var radius = this._event.accuracy; | |||||
var latlng = this._event.latlng; | |||||
// circle with the radius of the location's accuracy | |||||
if (this.options.drawCircle) { | |||||
var style = this._isFollowing() ? this.options.followCircleStyle : this.options.circleStyle; | |||||
if (!this._circle) { | |||||
this._circle = L.circle(latlng, radius, style).addTo(this._layer); | |||||
} else { | |||||
this._circle.setLatLng(latlng).setRadius(radius).setStyle(style); | |||||
} | |||||
} | |||||
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 | |||||
if (this.options.drawMarker) { | |||||
var mStyle = this._isFollowing() ? this.options.followMarkerStyle : this.options.markerStyle; | |||||
if (!this._marker) { | |||||
this._marker = new this.options.markerClass(latlng, mStyle).addTo(this._layer); | |||||
} else { | |||||
this._marker.setLatLng(latlng); | |||||
// If the markerClass can be updated with setStyle, update it. | |||||
if (this._marker.setStyle) { | |||||
this._marker.setStyle(mStyle); | |||||
} | |||||
} | |||||
} | |||||
var t = this.options.strings.popup; | |||||
if (this.options.showPopup && t && this._marker) { | |||||
this._marker | |||||
.bindPopup(L.Util.template(t, {distance: distance, unit: unit})) | |||||
._popup.setLatLng(latlng); | |||||
} | |||||
}, | |||||
/** | |||||
* Remove the marker from map. | |||||
*/ | |||||
_removeMarker: function() { | |||||
this._layer.clearLayers(); | |||||
this._marker = undefined; | |||||
this._circle = undefined; | |||||
}, | |||||
/** | |||||
* Unload the plugin and all event listeners. | |||||
* Kind of the opposite of onAdd. | |||||
*/ | |||||
_unload: function() { | |||||
this.stop(); | |||||
this._map.off('unload', this._unload, this); | |||||
}, | |||||
/** | |||||
* 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, this); | |||||
}, | |||||
/** | |||||
* 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) { | |||||
// we may have a stray event | |||||
return; | |||||
} | |||||
this._event = e; | |||||
this._drawMarker(); | |||||
this._updateContainerStyle(); | |||||
switch (this.options.setView) { | |||||
case 'once': | |||||
if (this._justClicked) { | |||||
this.setView(); | |||||
} | |||||
break; | |||||
case 'untilPan': | |||||
if (!this._userPanned) { | |||||
this.setView(); | |||||
} | |||||
break; | |||||
case 'always': | |||||
this.setView(); | |||||
break; | |||||
case false: | |||||
// don't set the view | |||||
break; | |||||
} | |||||
this._justClicked = false; | |||||
}, | |||||
/** | |||||
* When the user drags. Need a separate even so we can bind and unbind even listeners. | |||||
*/ | |||||
_onDrag: function() { | |||||
// only react to drags once we have a location | |||||
if (this._event) { | |||||
this._userPanned = true; | |||||
this._updateContainerStyle(); | |||||
this._drawMarker(); | |||||
} | |||||
}, | |||||
/** | |||||
* Compute whether the map is following the user location with pan and zoom. | |||||
*/ | |||||
_isFollowing: function() { | |||||
if (!this._active) { | |||||
return false; | |||||
} | |||||
if (this.options.setView === 'always') { | |||||
return true; | |||||
} else if (this.options.setView === 'untilPan') { | |||||
return !this._userPanned; | |||||
} | |||||
}, | |||||
/** | |||||
* 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. | |||||
*/ | |||||
_updateContainerStyle: function() { | |||||
if (!this._container) { | |||||
return; | |||||
} | |||||
if (this._active && !this._event) { | |||||
// active but don't have a location yet | |||||
this._setClasses('requesting'); | |||||
} else if (this._isFollowing()) { | |||||
this._setClasses('following'); | |||||
} else if (this._active) { | |||||
this._setClasses('active'); | |||||
} else { | |||||
this._cleanClasses(); | |||||
} | |||||
}, | |||||
/** | |||||
* Sets the CSS classes for the state. | |||||
*/ | |||||
_setClasses: function(state) { | |||||
if (state == 'requesting') { | |||||
removeClasses(this._container, "active following"); | |||||
addClasses(this._container, "requesting"); | |||||
removeClasses(this._icon, this.options.icon); | |||||
addClasses(this._icon, this.options.iconLoading); | |||||
} else if (state == 'active') { | |||||
removeClasses(this._container, "requesting following"); | |||||
addClasses(this._container, "active"); | |||||
removeClasses(this._icon, this.options.iconLoading); | |||||
addClasses(this._icon, this.options.icon); | |||||
} else if (state == 'following') { | |||||
removeClasses(this._container, "requesting"); | |||||
addClasses(this._container, "active following"); | |||||
removeClasses(this._icon, this.options.iconLoading); | |||||
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"); | |||||
removeClasses(this._icon, this.options.iconLoading); | |||||
addClasses(this._icon, this.options.icon); | |||||
}, | |||||
/** | |||||
* Reinitializes state variables. | |||||
*/ | |||||
_resetVariables: function() { | |||||
// whether locate is active or not | |||||
this._active = false; | |||||
// true if the control was clicked for the first time | |||||
// we need this so we can pan and zoom once we have the location | |||||
this._justClicked = false; | |||||
// true if the user has panned the map after clicking the control | |||||
this._userPanned = false; | |||||
} | |||||
}); | |||||
L.control.locate = function (options) { | |||||
return new L.Control.Locate(options); | |||||
}; | |||||
return LocateControl; | |||||
}, window)); |
@@ -0,0 +1,56 @@ | |||||
.leaflet-bar button, | |||||
.leaflet-bar button:hover { | |||||
background-color: #fff; | |||||
border: none; | |||||
border-bottom: 1px solid #ccc; | |||||
width: 26px; | |||||
height: 26px; | |||||
line-height: 26px; | |||||
display: block; | |||||
text-align: center; | |||||
text-decoration: none; | |||||
color: black; | |||||
} | |||||
.leaflet-bar button { | |||||
background-position: 50% 50%; | |||||
background-repeat: no-repeat; | |||||
overflow: hidden; | |||||
display: block; | |||||
} | |||||
.leaflet-bar button:hover { | |||||
background-color: #f4f4f4; | |||||
} | |||||
.leaflet-bar button:first-of-type { | |||||
border-top-left-radius: 4px; | |||||
border-top-right-radius: 4px; | |||||
} | |||||
.leaflet-bar button:last-of-type { | |||||
border-bottom-left-radius: 4px; | |||||
border-bottom-right-radius: 4px; | |||||
border-bottom: none; | |||||
} | |||||
.leaflet-bar.disabled, | |||||
.leaflet-bar button.disabled { | |||||
cursor: default; | |||||
pointer-events: none; | |||||
opacity: .4; | |||||
} | |||||
.easy-button-button .button-state{ | |||||
display: block; | |||||
width: 100%; | |||||
height: 100%; | |||||
position: relative; | |||||
} | |||||
.leaflet-touch .leaflet-bar button { | |||||
width: 30px; | |||||
height: 30px; | |||||
line-height: 30px; | |||||
} |
@@ -0,0 +1,370 @@ | |||||
(function(){ | |||||
// This is for grouping buttons into a bar | |||||
// takes an array of `L.easyButton`s and | |||||
// then the usual `.addTo(map)` | |||||
L.Control.EasyBar = L.Control.extend({ | |||||
options: { | |||||
position: 'topleft', // part of leaflet's defaults | |||||
id: null, // an id to tag the Bar with | |||||
leafletClasses: true // use leaflet classes? | |||||
}, | |||||
initialize: function(buttons, options){ | |||||
if(options){ | |||||
L.Util.setOptions( this, options ); | |||||
} | |||||
this._buildContainer(); | |||||
this._buttons = []; | |||||
for(var i = 0; i < buttons.length; i++){ | |||||
buttons[i]._bar = this; | |||||
buttons[i]._container = buttons[i].button; | |||||
this._buttons.push(buttons[i]); | |||||
this.container.appendChild(buttons[i].button); | |||||
} | |||||
}, | |||||
_buildContainer: function(){ | |||||
this._container = this.container = L.DomUtil.create('div', ''); | |||||
this.options.leafletClasses && L.DomUtil.addClass(this.container, 'leaflet-bar easy-button-container leaflet-control'); | |||||
this.options.id && (this.container.id = this.options.id); | |||||
}, | |||||
enable: function(){ | |||||
L.DomUtil.addClass(this.container, 'enabled'); | |||||
L.DomUtil.removeClass(this.container, 'disabled'); | |||||
this.container.setAttribute('aria-hidden', 'false'); | |||||
return this; | |||||
}, | |||||
disable: function(){ | |||||
L.DomUtil.addClass(this.container, 'disabled'); | |||||
L.DomUtil.removeClass(this.container, 'enabled'); | |||||
this.container.setAttribute('aria-hidden', 'true'); | |||||
return this; | |||||
}, | |||||
onAdd: function () { | |||||
return this.container; | |||||
}, | |||||
addTo: function (map) { | |||||
this._map = map; | |||||
for(var i = 0; i < this._buttons.length; i++){ | |||||
this._buttons[i]._map = map; | |||||
} | |||||
var container = this._container = this.onAdd(map), | |||||
pos = this.getPosition(), | |||||
corner = map._controlCorners[pos]; | |||||
L.DomUtil.addClass(container, 'leaflet-control'); | |||||
if (pos.indexOf('bottom') !== -1) { | |||||
corner.insertBefore(container, corner.firstChild); | |||||
} else { | |||||
corner.appendChild(container); | |||||
} | |||||
return this; | |||||
} | |||||
}); | |||||
L.easyBar = function(){ | |||||
var args = [L.Control.EasyBar]; | |||||
for(var i = 0; i < arguments.length; i++){ | |||||
args.push( arguments[i] ); | |||||
} | |||||
return new (Function.prototype.bind.apply(L.Control.EasyBar, args)); | |||||
}; | |||||
// L.EasyButton is the actual buttons | |||||
// can be called without being grouped into a bar | |||||
L.Control.EasyButton = L.Control.extend({ | |||||
options: { | |||||
position: 'topleft', // part of leaflet's defaults | |||||
id: null, // an id to tag the button with | |||||
type: 'replace', // [(replace|animate)] | |||||
// replace swaps out elements | |||||
// animate changes classes with all elements inserted | |||||
states: [], // state names look like this | |||||
// { | |||||
// stateName: 'untracked', | |||||
// onClick: function(){ handle_nav_manually(); }; | |||||
// title: 'click to make inactive', | |||||
// icon: 'fa-circle', // wrapped with <a> | |||||
// } | |||||
leafletClasses: true, // use leaflet styles for the button | |||||
tagName: 'button', | |||||
}, | |||||
initialize: function(icon, onClick, title, id){ | |||||
// clear the states manually | |||||
this.options.states = []; | |||||
// add id to options | |||||
if(id != null){ | |||||
this.options.id = id; | |||||
} | |||||
// storage between state functions | |||||
this.storage = {}; | |||||
// is the last item an object? | |||||
if( typeof arguments[arguments.length-1] === 'object' ){ | |||||
// if so, it should be the options | |||||
L.Util.setOptions( this, arguments[arguments.length-1] ); | |||||
} | |||||
// if there aren't any states in options | |||||
// use the early params | |||||
if( this.options.states.length === 0 && | |||||
typeof icon === 'string' && | |||||
typeof onClick === 'function'){ | |||||
// turn the options object into a state | |||||
this.options.states.push({ | |||||
icon: icon, | |||||
onClick: onClick, | |||||
title: typeof title === 'string' ? title : '' | |||||
}); | |||||
} | |||||
// curate and move user's states into | |||||
// the _states for internal use | |||||
this._states = []; | |||||
for(var i = 0; i < this.options.states.length; i++){ | |||||
this._states.push( new State(this.options.states[i], this) ); | |||||
} | |||||
this._buildButton(); | |||||
this._activateState(this._states[0]); | |||||
}, | |||||
_buildButton: function(){ | |||||
this.button = L.DomUtil.create(this.options.tagName, ''); | |||||
if (this.options.tagName === 'button') { | |||||
this.button.setAttribute('type', 'button'); | |||||
} | |||||
if (this.options.id ){ | |||||
this.button.id = this.options.id; | |||||
} | |||||
if (this.options.leafletClasses){ | |||||
L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part leaflet-interactive'); | |||||
} | |||||
// don't let double clicks and mousedown get to the map | |||||
L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop); | |||||
L.DomEvent.addListener(this.button, 'mousedown', L.DomEvent.stop); | |||||
// take care of normal clicks | |||||
L.DomEvent.addListener(this.button,'click', function(e){ | |||||
L.DomEvent.stop(e); | |||||
this._currentState.onClick(this, this._map ? this._map : null ); | |||||
this._map && this._map.getContainer().focus(); | |||||
}, this); | |||||
// prep the contents of the control | |||||
if(this.options.type == 'replace'){ | |||||
this.button.appendChild(this._currentState.icon); | |||||
} else { | |||||
for(var i=0;i<this._states.length;i++){ | |||||
this.button.appendChild(this._states[i].icon); | |||||
} | |||||
} | |||||
}, | |||||
_currentState: { | |||||
// placeholder content | |||||
stateName: 'unnamed', | |||||
icon: (function(){ return document.createElement('span'); })() | |||||
}, | |||||
_states: null, // populated on init | |||||
state: function(newState){ | |||||
// activate by name | |||||
if(typeof newState == 'string'){ | |||||
this._activateStateNamed(newState); | |||||
// activate by index | |||||
} else if (typeof newState == 'number'){ | |||||
this._activateState(this._states[newState]); | |||||
} | |||||
return this; | |||||
}, | |||||
_activateStateNamed: function(stateName){ | |||||
for(var i = 0; i < this._states.length; i++){ | |||||
if( this._states[i].stateName == stateName ){ | |||||
this._activateState( this._states[i] ); | |||||
} | |||||
} | |||||
}, | |||||
_activateState: function(newState){ | |||||
if( newState === this._currentState ){ | |||||
// don't touch the dom if it'll just be the same after | |||||
return; | |||||
} else { | |||||
// swap out elements... if you're into that kind of thing | |||||
if( this.options.type == 'replace' ){ | |||||
this.button.appendChild(newState.icon); | |||||
this.button.removeChild(this._currentState.icon); | |||||
} | |||||
if( newState.title ){ | |||||
this.button.title = newState.title; | |||||
} else { | |||||
this.button.removeAttribute('title'); | |||||
} | |||||
// update classes for animations | |||||
for(var i=0;i<this._states.length;i++){ | |||||
L.DomUtil.removeClass(this._states[i].icon, this._currentState.stateName + '-active'); | |||||
L.DomUtil.addClass(this._states[i].icon, newState.stateName + '-active'); | |||||
} | |||||
// update classes for animations | |||||
L.DomUtil.removeClass(this.button, this._currentState.stateName + '-active'); | |||||
L.DomUtil.addClass(this.button, newState.stateName + '-active'); | |||||
// update the record | |||||
this._currentState = newState; | |||||
} | |||||
}, | |||||
enable: function(){ | |||||
L.DomUtil.addClass(this.button, 'enabled'); | |||||
L.DomUtil.removeClass(this.button, 'disabled'); | |||||
this.button.setAttribute('aria-hidden', 'false'); | |||||
return this; | |||||
}, | |||||
disable: function(){ | |||||
L.DomUtil.addClass(this.button, 'disabled'); | |||||
L.DomUtil.removeClass(this.button, 'enabled'); | |||||
this.button.setAttribute('aria-hidden', 'true'); | |||||
return this; | |||||
}, | |||||
onAdd: function(map){ | |||||
var bar = L.easyBar([this], { | |||||
position: this.options.position, | |||||
leafletClasses: this.options.leafletClasses | |||||
}); | |||||
this._anonymousBar = bar; | |||||
this._container = bar.container; | |||||
return this._anonymousBar.container; | |||||
}, | |||||
removeFrom: function (map) { | |||||
if (this._map === map) | |||||
this.remove(); | |||||
return this; | |||||
}, | |||||
}); | |||||
L.easyButton = function(/* args will pass automatically */){ | |||||
var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments); | |||||
return new (Function.prototype.bind.apply(L.Control.EasyButton, args)); | |||||
}; | |||||
/************************* | |||||
* | |||||
* util functions | |||||
* | |||||
*************************/ | |||||
// constructor for states so only curated | |||||
// states end up getting called | |||||
function State(template, easyButton){ | |||||
this.title = template.title; | |||||
this.stateName = template.stateName ? template.stateName : 'unnamed-state'; | |||||
// build the wrapper | |||||
this.icon = L.DomUtil.create('span', ''); | |||||
L.DomUtil.addClass(this.icon, 'button-state state-' + this.stateName.replace(/(^\s*|\s*$)/g,'')); | |||||
this.icon.innerHTML = buildIcon(template.icon); | |||||
this.onClick = L.Util.bind(template.onClick?template.onClick:function(){}, easyButton); | |||||
} | |||||
function buildIcon(ambiguousIconString) { | |||||
var tmpIcon; | |||||
// does this look like html? (i.e. not a class) | |||||
if( ambiguousIconString.match(/[&;=<>"']/) ){ | |||||
// if so, the user should have put in html | |||||
// so move forward as such | |||||
tmpIcon = ambiguousIconString; | |||||
// then it wasn't html, so | |||||
// it's a class list, figure out what kind | |||||
} else { | |||||
ambiguousIconString = ambiguousIconString.replace(/(^\s*|\s*$)/g,''); | |||||
tmpIcon = L.DomUtil.create('span', ''); | |||||
if( ambiguousIconString.indexOf('fa-') === 0 ){ | |||||
L.DomUtil.addClass(tmpIcon, 'fa ' + ambiguousIconString) | |||||
} else if ( ambiguousIconString.indexOf('glyphicon-') === 0 ) { | |||||
L.DomUtil.addClass(tmpIcon, 'glyphicon ' + ambiguousIconString) | |||||
} else { | |||||
L.DomUtil.addClass(tmpIcon, /*rollwithit*/ ambiguousIconString) | |||||
} | |||||
// make this a string so that it's easy to set innerHTML below | |||||
tmpIcon = tmpIcon.outerHTML; | |||||
} | |||||
return tmpIcon; | |||||
} | |||||
})(); |
@@ -0,0 +1,632 @@ | |||||
/* required styles */ | |||||
.leaflet-pane, | |||||
.leaflet-tile, | |||||
.leaflet-marker-icon, | |||||
.leaflet-marker-shadow, | |||||
.leaflet-tile-container, | |||||
.leaflet-pane > svg, | |||||
.leaflet-pane > canvas, | |||||
.leaflet-zoom-box, | |||||
.leaflet-image-layer, | |||||
.leaflet-layer { | |||||
position: absolute; | |||||
left: 0; | |||||
top: 0; | |||||
} | |||||
.leaflet-container { | |||||
overflow: hidden; | |||||
} | |||||
.leaflet-tile, | |||||
.leaflet-marker-icon, | |||||
.leaflet-marker-shadow { | |||||
-webkit-user-select: none; | |||||
-moz-user-select: none; | |||||
user-select: none; | |||||
-webkit-user-drag: none; | |||||
} | |||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ | |||||
.leaflet-safari .leaflet-tile { | |||||
image-rendering: -webkit-optimize-contrast; | |||||
} | |||||
/* hack that prevents hw layers "stretching" when loading new tiles */ | |||||
.leaflet-safari .leaflet-tile-container { | |||||
width: 1600px; | |||||
height: 1600px; | |||||
-webkit-transform-origin: 0 0; | |||||
} | |||||
.leaflet-marker-icon, | |||||
.leaflet-marker-shadow { | |||||
display: block; | |||||
} | |||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ | |||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ | |||||
.leaflet-container .leaflet-overlay-pane svg, | |||||
.leaflet-container .leaflet-marker-pane img, | |||||
.leaflet-container .leaflet-shadow-pane img, | |||||
.leaflet-container .leaflet-tile-pane img, | |||||
.leaflet-container img.leaflet-image-layer { | |||||
max-width: none !important; /* csslint allow: important */ | |||||
} | |||||
.leaflet-container.leaflet-touch-zoom { | |||||
-ms-touch-action: pan-x pan-y; | |||||
touch-action: pan-x pan-y; | |||||
} | |||||
.leaflet-container.leaflet-touch-drag { | |||||
-ms-touch-action: pinch-zoom; | |||||
} | |||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { | |||||
-ms-touch-action: none; | |||||
touch-action: none; | |||||
} | |||||
.leaflet-container { | |||||
-webkit-tap-highlight-color: transparent; | |||||
} | |||||
.leaflet-container a { | |||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); | |||||
} | |||||
.leaflet-tile { | |||||
filter: inherit; | |||||
visibility: hidden; | |||||
} | |||||
.leaflet-tile-loaded { | |||||
visibility: inherit; | |||||
} | |||||
.leaflet-zoom-box { | |||||
width: 0; | |||||
height: 0; | |||||
-moz-box-sizing: border-box; | |||||
box-sizing: border-box; | |||||
z-index: 800; | |||||
} | |||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ | |||||
.leaflet-overlay-pane svg { | |||||
-moz-user-select: none; | |||||
} | |||||
.leaflet-pane { z-index: 400; } | |||||
.leaflet-tile-pane { z-index: 200; } | |||||
.leaflet-overlay-pane { z-index: 400; } | |||||
.leaflet-shadow-pane { z-index: 500; } | |||||
.leaflet-marker-pane { z-index: 600; } | |||||
.leaflet-tooltip-pane { z-index: 650; } | |||||
.leaflet-popup-pane { z-index: 700; } | |||||
.leaflet-map-pane canvas { z-index: 100; } | |||||
.leaflet-map-pane svg { z-index: 200; } | |||||
.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: 800; | |||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ | |||||
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 { | |||||
will-change: opacity; | |||||
} | |||||
.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-map-pane .leaflet-popup { | |||||
opacity: 1; | |||||
} | |||||
.leaflet-zoom-animated { | |||||
-webkit-transform-origin: 0 0; | |||||
-ms-transform-origin: 0 0; | |||||
transform-origin: 0 0; | |||||
} | |||||
.leaflet-zoom-anim .leaflet-zoom-animated { | |||||
will-change: transform; | |||||
} | |||||
.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 { | |||||
-webkit-transition: none; | |||||
-moz-transition: none; | |||||
-o-transition: none; | |||||
transition: none; | |||||
} | |||||
.leaflet-zoom-anim .leaflet-zoom-hide { | |||||
visibility: hidden; | |||||
} | |||||
/* cursors */ | |||||
.leaflet-interactive { | |||||
cursor: pointer; | |||||
} | |||||
.leaflet-grab { | |||||
cursor: -webkit-grab; | |||||
cursor: -moz-grab; | |||||
} | |||||
.leaflet-crosshair, | |||||
.leaflet-crosshair .leaflet-interactive { | |||||
cursor: crosshair; | |||||
} | |||||
.leaflet-popup-pane, | |||||
.leaflet-control { | |||||
cursor: auto; | |||||
} | |||||
.leaflet-dragging .leaflet-grab, | |||||
.leaflet-dragging .leaflet-grab .leaflet-interactive, | |||||
.leaflet-dragging .leaflet-marker-draggable { | |||||
cursor: move; | |||||
cursor: -webkit-grabbing; | |||||
cursor: -moz-grabbing; | |||||
} | |||||
/* marker & overlays interactivity */ | |||||
.leaflet-marker-icon, | |||||
.leaflet-marker-shadow, | |||||
.leaflet-image-layer, | |||||
.leaflet-pane > svg path, | |||||
.leaflet-tile-container { | |||||
pointer-events: none; | |||||
} | |||||
.leaflet-marker-icon.leaflet-interactive, | |||||
.leaflet-image-layer.leaflet-interactive, | |||||
.leaflet-pane > svg path.leaflet-interactive { | |||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ | |||||
pointer-events: auto; | |||||
} | |||||
/* 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; | |||||
} | |||||
.leaflet-touch .leaflet-bar a:first-child { | |||||
border-top-left-radius: 2px; | |||||
border-top-right-radius: 2px; | |||||
} | |||||
.leaflet-touch .leaflet-bar a:last-child { | |||||
border-bottom-left-radius: 2px; | |||||
border-bottom-right-radius: 2px; | |||||
} | |||||
/* zoom control */ | |||||
.leaflet-control-zoom-in, | |||||
.leaflet-control-zoom-out { | |||||
font: bold 18px 'Lucida Console', Monaco, monospace; | |||||
text-indent: 1px; | |||||
} | |||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { | |||||
font-size: 22px; | |||||
} | |||||
/* 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(/assets/frappe/images/leaflet/layers.png); | |||||
width: 36px; | |||||
height: 36px; | |||||
} | |||||
.leaflet-retina .leaflet-control-layers-toggle { | |||||
background-image: url(/assets/frappe/images/leaflet/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-scrollbar { | |||||
overflow-y: scroll; | |||||
overflow-x: hidden; | |||||
padding-right: 5px; | |||||
} | |||||
.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; | |||||
} | |||||
/* Default icon URLs */ | |||||
.leaflet-default-icon-path { | |||||
background-image: url(/assets/frappe/images/leaflet/marker-icon.png); | |||||
} | |||||
/* 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: border-box; | |||||
box-sizing: border-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; | |||||
margin-bottom: 20px; | |||||
} | |||||
.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 { | |||||
width: 40px; | |||||
height: 20px; | |||||
position: absolute; | |||||
left: 50%; | |||||
margin-left: -20px; | |||||
overflow: hidden; | |||||
pointer-events: none; | |||||
} | |||||
.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; | |||||
color: #333; | |||||
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; | |||||
border: none; | |||||
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; | |||||
} | |||||
/* Tooltip */ | |||||
/* Base styles for the element that has a tooltip */ | |||||
.leaflet-tooltip { | |||||
position: absolute; | |||||
padding: 6px; | |||||
background-color: #fff; | |||||
border: 1px solid #fff; | |||||
border-radius: 3px; | |||||
color: #222; | |||||
white-space: nowrap; | |||||
-webkit-user-select: none; | |||||
-moz-user-select: none; | |||||
-ms-user-select: none; | |||||
user-select: none; | |||||
pointer-events: none; | |||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4); | |||||
} | |||||
.leaflet-tooltip.leaflet-clickable { | |||||
cursor: pointer; | |||||
pointer-events: auto; | |||||
} | |||||
.leaflet-tooltip-top:before, | |||||
.leaflet-tooltip-bottom:before, | |||||
.leaflet-tooltip-left:before, | |||||
.leaflet-tooltip-right:before { | |||||
position: absolute; | |||||
pointer-events: none; | |||||
border: 6px solid transparent; | |||||
background: transparent; | |||||
content: ""; | |||||
} | |||||
/* Directions */ | |||||
.leaflet-tooltip-bottom { | |||||
margin-top: 6px; | |||||
} | |||||
.leaflet-tooltip-top { | |||||
margin-top: -6px; | |||||
} | |||||
.leaflet-tooltip-bottom:before, | |||||
.leaflet-tooltip-top:before { | |||||
left: 50%; | |||||
margin-left: -6px; | |||||
} | |||||
.leaflet-tooltip-top:before { | |||||
bottom: 0; | |||||
margin-bottom: -12px; | |||||
border-top-color: #fff; | |||||
} | |||||
.leaflet-tooltip-bottom:before { | |||||
top: 0; | |||||
margin-top: -12px; | |||||
margin-left: -6px; | |||||
border-bottom-color: #fff; | |||||
} | |||||
.leaflet-tooltip-left { | |||||
margin-left: -6px; | |||||
} | |||||
.leaflet-tooltip-right { | |||||
margin-left: 6px; | |||||
} | |||||
.leaflet-tooltip-left:before, | |||||
.leaflet-tooltip-right:before { | |||||
top: 50%; | |||||
margin-top: -6px; | |||||
} | |||||
.leaflet-tooltip-left:before { | |||||
right: 0; | |||||
margin-right: -12px; | |||||
border-left-color: #fff; | |||||
} | |||||
.leaflet-tooltip-right:before { | |||||
left: 0; | |||||
margin-left: -12px; | |||||
border-right-color: #fff; | |||||
} |
@@ -0,0 +1,10 @@ | |||||
.leaflet-draw-section{position:relative}.leaflet-draw-toolbar{margin-top:12px}.leaflet-draw-toolbar-top{margin-top:0}.leaflet-draw-toolbar-notop a:first-child{border-top-right-radius:0}.leaflet-draw-toolbar-nobottom a:last-child{border-bottom-right-radius:0}.leaflet-draw-toolbar a{background-image:url('/assets/frappe/images/leaflet/spritesheet.png');background-image:linear-gradient(transparent,transparent),url('/assets/frappe/images/leaflet/spritesheet.svg');background-repeat:no-repeat;background-size:300px 30px;background-clip:padding-box}.leaflet-retina .leaflet-draw-toolbar a{background-image:url('/assets/frappe/images/leaflet/spritesheet-2x.png');background-image:linear-gradient(transparent,transparent),url('/assets/frappe/images/leaflet/spritesheet.svg')} | |||||
.leaflet-draw a{display:block;text-align:center;text-decoration:none}.leaflet-draw a .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.leaflet-draw-actions{display:none;list-style:none;margin:0;padding:0;position:absolute;left:26px;top:0;white-space:nowrap}.leaflet-touch .leaflet-draw-actions{left:32px}.leaflet-right .leaflet-draw-actions{right:26px;left:auto}.leaflet-touch .leaflet-right .leaflet-draw-actions{right:32px;left:auto}.leaflet-draw-actions li{display:inline-block} | |||||
.leaflet-draw-actions li:first-child a{border-left:0}.leaflet-draw-actions li:last-child a{-webkit-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.leaflet-right .leaflet-draw-actions li:last-child a{-webkit-border-radius:0;border-radius:0}.leaflet-right .leaflet-draw-actions li:first-child a{-webkit-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.leaflet-draw-actions a{background-color:#919187;border-left:1px solid #AAA;color:#FFF;font:11px/19px "Helvetica Neue",Arial,Helvetica,sans-serif;line-height:28px;text-decoration:none;padding-left:10px;padding-right:10px;height:28px} | |||||
.leaflet-touch .leaflet-draw-actions a{font-size:12px;line-height:30px;height:30px}.leaflet-draw-actions-bottom{margin-top:0}.leaflet-draw-actions-top{margin-top:1px}.leaflet-draw-actions-top a,.leaflet-draw-actions-bottom a{height:27px;line-height:27px}.leaflet-draw-actions a:hover{background-color:#a0a098}.leaflet-draw-actions-top.leaflet-draw-actions-bottom a{height:26px;line-height:26px}.leaflet-draw-toolbar .leaflet-draw-draw-polyline{background-position:-2px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polyline{background-position:0 -1px} | |||||
.leaflet-draw-toolbar .leaflet-draw-draw-polygon{background-position:-31px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polygon{background-position:-29px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-rectangle{background-position:-62px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-rectangle{background-position:-60px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-circle{background-position:-92px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circle{background-position:-90px -1px} | |||||
.leaflet-draw-toolbar .leaflet-draw-draw-marker{background-position:-122px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-marker{background-position:-120px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-circlemarker{background-position:-273px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circlemarker{background-position:-271px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-edit{background-position:-152px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit{background-position:-150px -1px} | |||||
.leaflet-draw-toolbar .leaflet-draw-edit-remove{background-position:-182px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove{background-position:-180px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled{background-position:-212px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled{background-position:-210px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled{background-position:-242px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled{background-position:-240px -2px} | |||||
.leaflet-mouse-marker{background-color:#fff;cursor:crosshair}.leaflet-draw-tooltip{background:#363636;background:rgba(0,0,0,0.5);border:1px solid transparent;-webkit-border-radius:4px;border-radius:4px;color:#fff;font:12px/18px "Helvetica Neue",Arial,Helvetica,sans-serif;margin-left:20px;margin-top:-21px;padding:4px 8px;position:absolute;visibility:hidden;white-space:nowrap;z-index:6}.leaflet-draw-tooltip:before{border-right:6px solid black;border-right-color:rgba(0,0,0,0.5);border-top:6px solid transparent;border-bottom:6px solid transparent;content:"";position:absolute;top:7px;left:-7px} | |||||
.leaflet-error-draw-tooltip{background-color:#f2dede;border:1px solid #e6b6bd;color:#b94a48}.leaflet-error-draw-tooltip:before{border-right-color:#e6b6bd}.leaflet-draw-tooltip-single{margin-top:-12px}.leaflet-draw-tooltip-subtext{color:#f8d5e4}.leaflet-draw-guide-dash{font-size:1%;opacity:.6;position:absolute;width:5px;height:5px}.leaflet-edit-marker-selected{background-color:rgba(254,87,161,0.1);border:4px dashed rgba(254,87,161,0.6);-webkit-border-radius:4px;border-radius:4px;box-sizing:content-box} | |||||
.leaflet-edit-move{cursor:move}.leaflet-edit-resize{cursor:pointer}.leaflet-oldie .leaflet-draw-toolbar{border:1px solid #999} |
@@ -8,6 +8,8 @@ | |||||
{%- elif df.fieldtype in ("Image", "Attach Image", "Attach", "Signature") | {%- elif df.fieldtype in ("Image", "Attach Image", "Attach", "Signature") | ||||
and (guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/") -%} | and (guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/") -%} | ||||
{{ render_image(df, doc) }} | {{ render_image(df, doc) }} | ||||
{%- elif df.fieldtype=="Geolocation" -%} | |||||
{{ render_geolocation(df, doc) }} | |||||
{%- else -%} | {%- else -%} | ||||
{{ render_field_with_label(df, doc) }} | {{ render_field_with_label(df, doc) }} | ||||
{%- endif -%} | {%- endif -%} | ||||
@@ -98,6 +100,10 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}" | |||||
{{ print_value(df, doc) }} | {{ print_value(df, doc) }} | ||||
{% endmacro %} | {% endmacro %} | ||||
{%- macro render_geolocation(df, doc) -%} | |||||
{{ "" }} | |||||
{%- endmacro -%} | |||||
{%- macro print_value(df, doc, parent_doc=None, visible_columns=None) -%} | {%- macro print_value(df, doc, parent_doc=None, visible_columns=None) -%} | ||||
{% if doc.print_templates and | {% if doc.print_templates and | ||||
doc.print_templates.get(df.fieldname) %} | doc.print_templates.get(df.fieldname) %} | ||||
@@ -0,0 +1,39 @@ | |||||
QUnit.module('controls'); | |||||
QUnit.test("Test ControlGeolocation", function(assert) { | |||||
assert.expect(1); | |||||
const random_name = frappe.utils.get_random(3).toLowerCase(); | |||||
let done = assert.async(); | |||||
// geolocation alert dialog suppressed (only secure origins or localhost allowed) | |||||
window.alert = function() { | |||||
console.log.apply(console, arguments); //eslint-disable-line | |||||
}; | |||||
frappe.run_serially([ | |||||
() => { | |||||
return frappe.tests.make('Custom Field', [ | |||||
{dt: 'ToDo'}, | |||||
{fieldtype: 'Geolocation'}, | |||||
{label: random_name}, | |||||
]); | |||||
}, | |||||
() => frappe.set_route('List', 'ToDo'), | |||||
() => frappe.new_doc('ToDo'), | |||||
() => { | |||||
if (frappe.quick_entry) | |||||
{ | |||||
frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(); | |||||
return frappe.timeout(1); | |||||
} | |||||
}, | |||||
() => { | |||||
const control = $(`.frappe-control[data-fieldname="${random_name}"]`); | |||||
return assert.ok(control.data('fieldtype') === 'Geolocation'); | |||||
}, | |||||
() => done() | |||||
]); | |||||
}); |
@@ -14,3 +14,4 @@ frappe/desk/doctype/event/test_event.js | |||||
frappe/workflow/doctype/workflow/tests/test_workflow_create.js | frappe/workflow/doctype/workflow/tests/test_workflow_create.js | ||||
frappe/workflow/doctype/workflow/tests/test_workflow_test.js | frappe/workflow/doctype/workflow/tests/test_workflow_test.js | ||||
frappe/tests/ui/test_control_html.js | frappe/tests/ui/test_control_html.js | ||||
frappe/tests/ui/test_control_geolocation.js |