692
695
# -----------------------------------------------------------------------------
696
class S3LocationSelectorWidget2(FormWidget):
699
Renders a gis_location SELECT to allow inline display/editing of linked fields.
702
Active Tab: Add New Location
703
Country Dropdown (to set the Number & Labels of Hierarchy)
705
Street Address (Line1/Line2?)
706
Trigger a geocoder lookup onblur
708
L2-L5 as Autocompletes which create missing locations automatically
709
L1 as Dropdown? (Have a gis_config setting to inform whether this is populated for a given L0)
710
Inline Map? (Deployment Option?)
712
Inactive Tab: Search Existing Locations
713
Hierarchical Filters above the Search Box
714
Search is filtered to values shown
717
Update form has uuid set server-side & hence S3.gis.uuid set client-side
718
If location is not shared by other resources:
719
Active Tab: Fields are editable for this location
720
Inactive Tab: Move to an existing Location
721
If location is shared by other resources: <- assume this mode for now
722
Active Tab: Fields are readable for this location
723
Inactive Tab: Edit this shared Location
724
Inactive Tab: Move to an existing Location
726
@author: Fran Boon (fran@aidiq.com)
728
@see: http://eden.sahanafoundation.org/wiki/BluePrintGISLocationSelector
742
self.deployment_settings = deployment_settings
743
self.request = request
744
self.response = response
747
def __call__(self, field, value, **attributes):
751
deployment_settings = self.deployment_settings
752
request = self.request
753
response = self.response
755
locations = db.gis_location
758
default = dict(_type = "text",
759
value = (value != None and str(value)) or "")
760
attr = StringWidget._attributes(field, default, **attributes)
761
# Hide the real field
762
attr["_class"] = "hidden"
765
# Are we restricted to just certain countries?
766
countries = response.s3.gis.countries
767
# Should we use a Map-based selector?
768
map_selector = deployment_settings.get_gis_map_selector()
769
# Navigate Away Confirm?
770
if deployment_settings.get_ui_navigate_away_confirm():
771
navigate_away_confirm = "true"
773
navigate_away_confirm = "false"
776
select_location = T("Select a location")
780
# Read current record
781
this_location = db(locations.id == value).select(locations.uuid,
786
locations.addr_street,
787
locations.addr_postcode,
790
limitby=(0, 1)).first()
791
uuid = this_location.uuid
792
level = this_location.level
793
default[level] = value
794
lat = this_location.lat
795
lon = this_location.lon
796
addr_street = this_location.addr_street or ""
797
addr_street_encoded = ""
799
addr_street_encoded = addr_street.replace("\r\n",
803
postcode = this_location.addr_postcode
804
parent = this_location.parent
805
path = this_location.path
807
# Populate default with Names of ancestors at each level
808
gis.get_parent_per_level(default, value, feature=this_location, names=True)
811
# If within the locations hierarchy then don't populate the visible name box
814
represent = this_location.name
817
config = gis.get_config()
819
if lat is None or lon is None:
826
layername = T("Location")
828
layer = gis.get_feature_layer("gis",
834
feature_queries = [layer]
837
map_popup = gis.show_map(
840
# Same as a single zoom on a cluster
842
feature_queries = feature_queries,
844
#add_feature_active = False,
851
# @ToDo: Check if this location is shared by other resources
852
# (If it is then we shouldn't let the user edit without caution)
853
# List of Tables with a location_id reference
854
#tables = locations._referenced_by
855
# Maybe just try hard-deleting the record to trigger an IntegrityError
856
# For now assume it is a shared location
866
addr_street_encoded = ""
869
map_popup = gis.show_map(
871
add_feature_active = True,
879
# Which Levels do we have in our hierarchy & what are their initial Labels?
880
# @ToDo: If we have hardcoded country or one from the value then we can lookup what options we should use for that location
882
#elif len(countries) == 1:
885
location_hierarchy = deployment_settings.get_gis_locations_hierarchy()
887
# Components to inject into Form
888
divider = TR(TD(_class="subheading"))
889
section_label = TR(TD(B("%s:" % field.label)))
891
# Tabs to select between the modes
892
add_button = A(T("Create New Location"),
893
_style="cursor:pointer; cursor:hand",
894
_id="gis_location_add-btn")
896
search_button = A(T("Select Existing Location"),
897
_style="cursor:pointer; cursor:hand",
898
_id="gis_location_search-btn")
900
tabs = DIV(SPAN(add_button, _id="gis_loc_add_tab", _class="tab_here"),
901
SPAN(search_button, _id="gis_loc_search_tab",
911
widget = INPUT(value=default[level],
912
_id="gis_location_%s" % level,
913
_disabled="disabled")
914
elif len(countries) == 1:
916
L0_rows = INPUT(_id="gis_location_%s" % level, _class="hidden")
918
attr_dropdown = OptionsWidget._attributes(field,
922
requires = IS_ONE_OF(db, "gis_location.id", repr_select,
924
filter_opts = (level,),
925
orderby = "gis_location.name",
927
zero = "%s..." % select_location)
929
# Use the list of countries from deployment_settings instead of from db
931
for country in countries:
932
options.append((countries[country].id,
933
countries[country].name))
935
# Prepopulate top-level dropdown from db
936
if hasattr(requires, "options"):
937
options = requires.options()
938
if options.__len__() == 2:
939
# If there is only a single country available then pre-select it
940
options = [options[1]]
942
raise SyntaxError, "widget cannot determine options of %s" \
944
opts = [OPTION(v, _value=k) for (k, v) in options]
945
attr_dropdown["_id"] = "gis_location_%s" % level
946
# Need to blank the name to prevent it from appearing in form.vars & requiring validation
947
attr_dropdown["_name"] = ""
948
widget = SELECT(*opts, **attr_dropdown)
950
label = LABEL("%s:" % location_hierarchy[level])
951
L0_rows = DIV(TR(TD(label),
952
_id="gis_location_%s_label__row" % level),
954
_id="gis_location_%s__row" % level))
956
name_label = T("Building Name")
957
street_label = T("Street Address")
958
postcode_label = T("Postcode")
959
lat_label = T("Latitude")
960
lon_label = T("Longitude")
961
autocomplete_help = T("Enter some characters to bring up a list of possible matches.")
962
new_help = T("If not found, you can have a new location created.")
963
def ac_help_widget(level):
964
return DIV( _class="tooltip",
965
_title="%s|%s|%s" % (location_hierarchy[level], autocomplete_help, new_help))
968
throbber = "/%s/static/img/ajax-loader.gif" % request.application
970
levels = ["L5", "L4", "L3", "L2", "L1"]
973
name_widget = INPUT(value=represent,
974
_id="gis_location_name",
975
_disabled="disabled")
976
street_widget = TEXTAREA(value=addr_street,
977
_id="gis_location_street",
978
_disabled="disabled")
979
postcode_widget = INPUT(value=postcode,
980
_id="gis_location_postcode",
981
_disabled="disabled")
982
lat_widget = INPUT(value=lat,
983
_id="gis_location_lat",
984
_disabled="disabled")
985
lon_widget = INPUT(value=lon,
986
_id="gis_location_lon",
987
_disabled="disabled")
989
if level not in location_hierarchy:
990
# Skip levels not in hierarchy
992
label = LABEL("%s:" % location_hierarchy[level])
994
_id="gis_location_%s_label__row" % level)
996
widget = INPUT(value=default[level],
997
_id="gis_location_%s" % level,
998
_disabled="disabled")
1000
_id="gis_location_%s__row" % level)
1004
name_widget = INPUT(_id="gis_location_name")
1005
street_widget = TEXTAREA(_id="gis_location_street")
1006
postcode_widget = INPUT(_id="gis_location_postcode")
1007
lat_widget = INPUT(_id="gis_location_lat")
1008
lon_widget = INPUT(_id="gis_location_lon")
1009
for level in levels:
1010
if level not in location_hierarchy:
1011
# Skip levels not in hierarchy
1013
if len(countries) == 1:
1014
# Q: Make the L1 selector into a dropdown?
1015
# deployment_setting? Based on flag in the region table to say that we have this populated?
1017
elif level in ["L5", "L4", "L3"]:
1018
# Have the levels ready, but don't display them until the Country Selector has run
1019
# so that we know which location hierarchy levels/labels to use
1021
label = LABEL("%s:" % location_hierarchy[level])
1024
_id="gis_location_%s_label__row" % level)
1026
widget = DIV(INPUT(_id="gis_location_%s" % level,
1028
INPUT(_id="gis_location_%s_ac" % level),
1030
_height=32, _width=32,
1031
_id="gis_location_%s_throbber" % level,
1032
_class="throbber hidden"))
1033
row = TR(TD(widget),
1034
TD(ac_help_widget(level)),
1036
_id="gis_location_%s__row" % level)
1039
name_rows = DIV(TR(LABEL("%s:" % name_label),
1040
_id="gis_location_name_label__row"),
1042
_id="gis_location_name__row"))
1043
street_rows = DIV(TR(LABEL("%s:" % street_label),
1044
_id="gis_location_street_label__row"),
1046
_id="gis_location_street__row"))
1047
postcode_rows = DIV(TR(LABEL("%s:" % postcode_label),
1048
_id="gis_location_postcode_label__row"),
1050
_id="gis_location_postcode__row"))
1052
latlon_help = locations.lat.comment
1053
converter_button = locations.lon.comment
1054
converter_button = ""
1055
latlon_rows = DIV(TR(LABEL("%s:" % lat_label),
1056
_id="gis_location_lat_label__row"),
1057
TR(TD(lat_widget), TD(latlon_help),
1058
_id="gis_location_lat__row"),
1059
TR(LABEL("%s:" % lon_label),
1060
_id="gis_location_lon_label__row"),
1061
TR(TD(lon_widget), TD(converter_button),
1062
_id="gis_location_lon__row"))
1066
map_button = A(T("View on Map"),
1067
_style="cursor:pointer; cursor:hand",
1068
_id="gis_location_map-btn")
1070
map_button = A(T("Place on Map"),
1071
_style="cursor:pointer; cursor:hand",
1072
_id="gis_location_map-btn")
1076
autocomplete = DIV(INPUT(_id="gis_location_autocomplete"),
1078
_height=32, _width=32,
1079
_id="gis_location_autocomplete_throbber",
1080
_class="throbber hidden"),
1081
_id="gis_location_autocomplete_div",
1084
# Settings to be read by static/scripts/S3/s3.locationselector.widget.js
1085
js_location_selector = """
1088
S3.gis.addr_street = '%s';
1089
S3.gis.postcode = '%s';
1092
var s3_gis_location_id = '%s';
1093
var s3_gis_url = '%s';
1094
var s3_navigate_away_confirm = %s;
1098
addr_street_encoded,
1102
attr["_id"], # Name of the real location field
1103
URL(r=request, c="gis", f="location"),
1104
navigate_away_confirm # Currently unused
1107
# The overall layout of the components
1109
TR(INPUT(**attr)), # Real input, which is hidden
1112
tab_rows, # When ready
1122
SCRIPT(js_location_selector)
1126
# -----------------------------------------------------------------------------
693
1127
class S3LocationSelectorWidget(FormWidget):
1422
1858
# -----------------------------------------------------------------------------
1423
class S3StreetAddressWidget(FormWidget):
1427
Renders a gis_location SELECT suitable for use as a Street Address:
1428
- a set of INPUT fields ordered in 'normal' order
1429
- street address field triggers a geocoder lookup (direct to Google @ToDo: configurable which back-end to use)
1430
- if succesful then the rest of the hierarchy is populated
1431
- Lx fields act as optional Autocompletes
1432
- any levels of hierarchy which weren't selected get created along with the main record through a single AJAX request
1433
Q: How do we avoid duplicates with the auto-population of the hierarchy from the geocoder? (Check for same name => Update in this processing controller?)
1434
- map opens up to the geocoded location & can be fine-tuned from there
1436
@author: Fran Boon (fran@aidiq.com)
1438
@see: http://eden.sahanafoundation.org/wiki/BluePrintGISLocationSelector
1444
deployment_settings,
1448
#level=None # @ToDo: Support forcing which level of the hierarchy is expected to be entered for this instance of the field
1453
self.deployment_settings = deployment_settings
1454
self.request = request
1455
self.response = response
1458
def __call__(self, field, value, **attributes):
1462
deployment_settings = self.deployment_settings
1463
request = self.request
1464
response = self.response
1468
locations = db.gis_location
1470
countries = response.s3.gis.countries # Also needed by location_represent hence want to keep in model, so useful not to repeat
1471
# Should we use a Map-based selector?
1472
map_selector = deployment_settings.get_gis_map_selector()
1473
# Which Levels do we have in our hierarchy & what are their initial Labels?
1474
location_hierarchy = deployment_settings.get_gis_locations_hierarchy()
1475
# What is the maximum level of hierarchy?
1476
max_hierarchy = deployment_settings.get_gis_max_hierarchy()
1478
# Navigate Away Confirm?
1479
if deployment_settings.get_ui_navigate_away_confirm():
1480
navigate_away_confirm = "true"
1482
navigate_away_confirm = "false"
1487
value = (value != None and str(value)) or "",
1495
attr = StringWidget._attributes(field, default, **attributes)
1496
# Hide the real field
1497
attr["_class"] = "hidden"
1502
# Read current record
1503
this_location = db(locations.id == value).select(locations.uuid,
1508
locations.addr_street,
1509
locations.addr_postcode,
1512
limitby=(0, 1)).first()
1513
# @ToDo Is it possible that value does not point to an existing
1515
uuid = this_location.uuid
1516
level = this_location.level
1517
default[level] = value
1518
lat = this_location.lat
1519
lon = this_location.lon
1520
addr_street = this_location.addr_street or ""
1521
addr_street_encoded = ""
1523
addr_street_encoded = addr_street.replace("\r\n",
1524
"%0d").replace("\r",
1525
"%0d").replace("\n",
1527
postcode = this_location.addr_postcode
1528
parent = this_location.parent
1529
path = this_location.path
1531
# Get ids of ancestors at each level.
1532
gis.get_parent_per_level(default, value, feature=this_location)
1534
# Provide the representation for the current/default Value
1535
#text = str(field.represent(default["value"]))
1539
# markup = etree.XML(text)
1540
# text = markup.xpath(".//text()")
1542
# text = " ".join(text)
1545
# except etree.XMLSyntaxError:
1549
# If within the locations hierarchy then don't populate the visible name box
1552
represent = this_location.name
1555
config = gis.get_config()
1557
if lat is None or lon is None:
1558
map_lat = config.lat
1559
map_lon = config.lon
1564
layername = T("Location")
1566
layer = gis.get_feature_layer("gis",
1572
feature_queries = [layer]
1574
feature_queries = []
1575
map_popup = gis.show_map(lat = map_lat,
1577
# Same as a single zoom on a cluster
1579
feature_queries = feature_queries,
1581
add_feature_active = False,
1596
addr_street_encoded = ""
1599
map_popup = gis.show_map(
1601
add_feature_active = True, # http://trac.osgeo.org/openlayers/ticket/3179
1608
# Settings to insert into static/scripts/S3/s3.locationselector.widget.js
1609
location_id = attr["_id"]
1610
url = URL(r=request, c="gis", f="location")
1613
loading_locations = T("Loading Locations")
1614
select_location = T("Select a location")
1615
degrees_validation_error = T("Degrees must be a number between -180 and 180")
1616
minutes_validation_error = T("Minutes must be a number greater than 0 and less than 60")
1617
seconds_validation_error = T("Seconds must be a number greater than 0 and less than 60")
1618
no_calculations_error = T("No calculations made")
1619
fill_lat = T("Fill in Latitude")
1620
fill_lon = T("Fill in Longitude")
1622
# Settings to be read by static/scripts/S3/s3.locationselector.widget.js
1623
js_location_selector = """
1624
var s3_gis_location_id = '%s';
1625
var s3_gis_maxlevel = '%s';
1626
var s3_gis_loading_locations = '<option value="">%s...</option>';
1627
var s3_gis_select_location = '<option value="" selected>%s...</option>';
1628
var s3_gis_url = '%s';
1631
S3.gis.addr_street = '%s';
1632
S3.gis.postcode = '%s';
1635
var s3_gis_degrees_validation_error = '%s';
1636
var s3_gis_minutes_validation_error = '%s';
1637
var s3_gis_seconds_validation_error = '%s';
1638
var s3_gis_no_calculations_error = '%s';
1639
var s3_gis_fill_lat = '%s';
1640
var s3_gis_fill_lon = '%s';
1641
var s3_navigate_away_confirm = %s;
1649
addr_street_encoded,
1653
degrees_validation_error,
1654
minutes_validation_error,
1655
seconds_validation_error,
1656
no_calculations_error,
1659
navigate_away_confirm
1663
name_label = DIV(LABEL("%s:" % T("Building Name")),
1664
#SPAN("*", _class="req"),
1665
_id="gis_location_name_label", _class=details_hidden)
1666
street_label = TR(LABEL("%s:" % T("Street Address")),
1667
_id="gis_location_addr_street_label",
1668
_class=details_hidden)
1669
postcode_label = TR(LABEL("%s:" % T("Postcode")),
1670
_id="gis_location_postcode_label",
1671
_class=details_hidden)
1672
lat_label = TR(LABEL("%s:" % T("Latitude")),
1673
_id="gis_location_lat_label",
1675
lon_label = TR(LABEL("%s:" % T("Longitude")),
1676
_id="gis_location_lon_label",
1680
street_widget = TEXTAREA(addr_street, _id="gis_location_addr_street")
1681
postcode_widget = INPUT(_id="gis_location_postcode", _value=postcode)
1682
lat_widget = INPUT(_id="gis_location_lat", _value=lat)
1683
lon_widget = INPUT(_id="gis_location_lon", _value=lon)
1685
autocomplete = DIV(LABEL("%s:" % T("Enter some characters to bring up a list of possible matches")),
1687
INPUT(_id="gis_location_autocomplete"),
1688
_id="gis_location_autocomplete_div",
1691
geolocate_button = A(T("Use Current Location"),
1692
_style="cursor:pointer; cursor:hand",
1693
_id="gis_location_geolocate-btn",
1694
_class=details_hidden)
1697
map_button = A(T("Place on Map"),
1698
_style="cursor:pointer; cursor:hand",
1699
_id="gis_location_map-btn",
1700
_class=details_hidden)
1704
geocoder_button = A(T("Lookup Address"),
1705
_style="cursor:pointer; cursor:hand",
1706
_id="gis_location_geocoder-btn")
1708
latlon_help = locations.lat.comment
1709
converter_button = locations.lon.comment
1711
advanced_checkbox = DIV("%s:" % T("Advanced"),
1712
INPUT(_type="checkbox",
1713
_id="gis_location_advanced_checkbox",
1715
_id="gis_location_advanced_div",
1716
_class=details_hidden)
1718
# @ToDo: Replace with simple alternate input forms: Radio button defaults to decimal degrees (real inputs), but can select GPS or DDMMSS
1719
gps_converter_popup = DIV(
1720
DIV(T("Coordinate Conversion"), _class="x-window-header"),
1727
B("%s:" % T("Enter a GPS Coordinate")),
1728
INPUT(_type="text", _size="3", _id="gps_deg"), "deg",
1729
INPUT(_type="text", _size="6", _id="gps_min"), "min",
1734
B("%s:" % T("Decimal Degrees")),
1735
INPUT(_type="text", _size="8", _id="gps_dec"),
1740
INPUT(_type="button", _value=T("Calculate"), _onclick="s3_gis_convertGps()"),
1741
INPUT(_type="reset", _value=T("Reset"), _onclick="s3_gis_convertGps()"),
1744
_border="0", _cellpadding="2", _width="400"
1747
_class="x-tab", _title=T("in GPS format")
1755
B("%s:" % T("Enter Coordinates")),
1756
INPUT(_type="text", _size="3", _id="DDMMSS_deg"), "Deg",
1757
INPUT(_type="text", _size="2", _id="DDMMSS_min"), "Min",
1758
INPUT(_type="text", _size="2", _id="DDMMSS_sec"), "Sec",
1763
B("%s:" % T("Decimal Degrees")),
1764
INPUT(_type="text", _size="8", _id="DDMMSS_dec"),
1769
INPUT(_type="button", _value=T("Calculate"), _onclick="s3_gis_convertDD()"),
1770
INPUT(_type="reset", _value=T("Reset")),
1773
_border="0", _cellpadding="2", _width="400"
1776
_class="x-tab", _title=T("in Deg Min Sec format")
1778
_id="gis-convert-tabs"),
1779
_id="gis-convert-win", _class="x-hidden")
1783
# # We have a specific location to show
1784
# name_rows = DIV(TR(name_label),
1785
# TR(INPUT(_id="gis_location_name", _value=represent)))
1787
name_rows = DIV(TR(name_label),
1788
TR(INPUT(_id="gis_location_name", _value=represent,
1789
_class=details_hidden),
1790
details_hide_button))
1791
street_rows = DIV(street_label,
1792
# @ToDo: Enable Geocoder here when ready
1793
#TR(street_widget, geocoder_button, _id="gis_location_addr_street_row", _class="hidden"))
1794
TR(street_widget, _id="gis_location_addr_street_row",
1795
_class=details_hidden))
1796
postcode_rows = DIV(postcode_label,
1798
_id="gis_location_postcode_row",
1799
_class=details_hidden))
1800
lat_rows = DIV(lat_label,
1801
TR(lat_widget, latlon_help, _id="gis_location_lat_row",
1803
lon_rows = DIV(lon_label,
1804
TR(lon_widget, converter_button,
1805
_id="gis_location_lon_row", _class="hidden"))
1806
divider = TR(TD(_class="subheading"))
1808
# The overall layout of the components
1810
#divider, # This is in the widget, so underneath the label :/ Add in JS? 'Sections'?
1811
TR(INPUT(**attr)), # Real input, which is hidden
1812
TR(gps_converter_popup),
1817
# @ToDo: Enable GeoLocate here when ready
1818
#TR(geolocate_button),
1820
TR(advanced_checkbox),
1824
SCRIPT(js_location_selector)
1828
# -----------------------------------------------------------------------------
1829
1859
class S3CheckboxesWidget(OptionsWidget):