From cd28209fa4653b5c2df233d099f33d8546a5c4ec Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Tue, 25 Aug 2020 11:25:21 +0200 Subject: [PATCH] contextmenu init --- NAMESPACE | 12 + NEWS.md | 1 + R/contextmenu.R | 208 ++++++ R/leaflet.extras2-package.R | 2 +- README.md | 1 + inst/examples/contextmenu_app.R | 209 ++++++ .../leaflet.contextmenu-bindings.js | 64 ++ .../lfx-contextmenu/leaflet.contextmenu.css | 54 ++ .../lfx-contextmenu/leaflet.contextmenu.js | 602 ++++++++++++++++++ .../leaflet.contextmenu.min.css | 1 + .../leaflet.contextmenu.min.js | 7 + man/addContextmenu.Rd | 87 +++ man/addItemContextmenu.Rd | 33 + man/hideContextmenu.Rd | 31 + man/insertItemContextmenu.Rd | 36 ++ man/mapmenuItems.Rd | 31 + man/markermenuItems.Rd | 31 + man/menuItem.Rd | 39 ++ man/removeItemContextmenu.Rd | 34 + man/removeallItemsContextmenu.Rd | 31 + man/setDisabledContextmenu.Rd | 37 ++ man/showContextmenu.Rd | 45 ++ tests/testthat/test-contextmenu.R | 128 ++++ 23 files changed, 1723 insertions(+), 1 deletion(-) create mode 100644 R/contextmenu.R create mode 100644 inst/examples/contextmenu_app.R create mode 100644 inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu-bindings.js create mode 100644 inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.css create mode 100644 inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.js create mode 100644 inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.min.css create mode 100644 inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.min.js create mode 100644 man/addContextmenu.Rd create mode 100644 man/addItemContextmenu.Rd create mode 100644 man/hideContextmenu.Rd create mode 100644 man/insertItemContextmenu.Rd create mode 100644 man/mapmenuItems.Rd create mode 100644 man/markermenuItems.Rd create mode 100644 man/menuItem.Rd create mode 100644 man/removeItemContextmenu.Rd create mode 100644 man/removeallItemsContextmenu.Rd create mode 100644 man/setDisabledContextmenu.Rd create mode 100644 man/showContextmenu.Rd create mode 100644 tests/testthat/test-contextmenu.R diff --git a/NAMESPACE b/NAMESPACE index b8527011..fe0d60df 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,11 +2,13 @@ S3method("[",leaflet_mapkey_icon_set) export(addAntpath) +export(addContextmenu) export(addEasyprint) export(addGIBS) export(addHeightgraph) export(addHexbin) export(addHistory) +export(addItemContextmenu) export(addMapkeyMarkers) export(addOpenweatherCurrent) export(addOpenweatherTiles) @@ -30,11 +32,16 @@ export(goBackHistory) export(goForwardHistory) export(heightgraphOptions) export(hexbinOptions) +export(hideContextmenu) export(hideHexbin) export(historyOptions) +export(insertItemContextmenu) export(makeMapkeyIcon) export(mapkeyIconList) export(mapkeyIcons) +export(mapmenuItems) +export(markermenuItems) +export(menuItem) export(openSidebar) export(openweatherCurrentOptions) export(openweatherOptions) @@ -42,15 +49,19 @@ export(playbackOptions) export(reachabilityOptions) export(removeAntpath) export(removeEasyprint) +export(removeItemContextmenu) export(removePlayback) export(removeReachability) export(removeSidebar) export(removeSidebyside) export(removeTimeslider) export(removeVelocity) +export(removeallItemsContextmenu) export(setDate) +export(setDisabledContextmenu) export(setOptionsVelocity) export(setTransparent) +export(showContextmenu) export(showHexbin) export(sidebar_pane) export(sidebar_tabs) @@ -65,3 +76,4 @@ importFrom(htmltools,tags) importFrom(magrittr,"%>%") importFrom(utils,adist) importFrom(utils,globalVariables) +importFrom(utils,packageVersion) diff --git a/NEWS.md b/NEWS.md index 1ca4c996..a735e958 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # leaflet.extras2 (development version) +* Included [Leaflet Contextmenu](https://github.com/aratcliffe/Leaflet.contextmenu) plugin * Included [Leaflet TimeSlider](https://github.com/dwilhelm89/LeafletSlider) plugin * `addWMS` gained the `layerId` argument and works like `leaflet::addWMSTiles` except for the `popupOptions` * `Side-by-Side` doesn't propagate click events when dragging. Thanks to `f905a47` of [#23](https://github.com/digidem/leaflet-side-by-side/pull/23) diff --git a/R/contextmenu.R b/R/contextmenu.R new file mode 100644 index 00000000..e1dcaff0 --- /dev/null +++ b/R/contextmenu.R @@ -0,0 +1,208 @@ +contextmenuDependency <- function() { + list( + htmltools::htmlDependency( + "lfx-contextmenu", version = "1.0.0", + src = system.file("htmlwidgets/lfx-contextmenu", package = "leaflet.extras2"), + script = c("leaflet.contextmenu.js", + "leaflet.contextmenu-bindings.js"), + stylesheet = "leaflet.contextmenu.css" + ) + ) +} + +#' Add contextmenu Plugin +#' +#' Add a contextmenu to the map or markers/vector layers. +#' @param map a map widget object created from \code{\link[leaflet]{leaflet}} +#' @family Contextmenu Functions +#' @references \url{https://github.com/aratcliffe/Leaflet.contextmenu} +#' @export +#' @return A leaflet map object +#' +#' @details +#' This function is only used to include the required JavaScript and CSS +#' bindings and to set up some Shiny event handlers. +#' +#' \subsection{Contextmenu initialization}{ +#' The contextmenu for +#' \itemize{ +#' \item {the \strong{map} must be defined in \code{\link[leaflet]{leafletOptions}}.} +#' \item {the \strong{markers/vector layers} must be defined in \code{\link[leaflet]{markerOptions}} +#' or \code{\link[leaflet]{pathOptions}}.} +#' } +#' } +#' +#' \subsection{Contextmenu selection}{ +#' When a contextmenu is selected, a Shiny input with the ID \code{"MAPID_contextmenu_select"} +#' is set (`MAPID` refers to the map's id). +#' +#' If the selected contextmenu item is triggered from: +#' \itemize{ +#' \item {the \strong{map}, the returned list containts the \code{text} of the item.} +#' \item {the \strong{markers}, the returned list also contains the +#' \code{layerId}, \code{group}, \code{lat}, \code{lng} and \code{label}.} +#' \item {the \strong{vector layers}, the returned list also contains the +#' \code{layerId}, \code{group} and \code{label}.} +#' } +#' } +#' +#' @examples +#' library(leaflet) +#' leaflet(options = leafletOptions( +#' contextmenu = TRUE, +#' contextmenuWidth = 200, +#' contextmenuItems = +#' mapmenuItems( +#' menuItem("Zoom Out", "function(e) {this.zoomOut()}", disabled=FALSE), +#' "-", +#' menuItem("Zoom In", "function(e) {this.zoomIn()}")))) %>% +#' addTiles(group = "base") %>% +#' addContextmenu() %>% +#' addMarkers(data = breweries91, label = ~brewery, +#' layerId = ~founded, group = "marker", +#' options = markerOptions( +#' contextmenu = TRUE, +#' contextmenuWidth = 200, +#' contextmenuItems = +#' markermenuItems( +#' menuItem(text = "Show Marker Coords", +#' callback = "function(e) {alert(e.latlng);}", +#' index = 1) +#' ) +#' )) +#' +addContextmenu <- function(map) { + map$dependencies <- c(map$dependencies, contextmenuDependency()) + leaflet::invokeMethod(map, NULL, "addContextmenu") +} + +#' showContextmenu +#' +#' Open the contextmenu at certain lat/lng-coordinates +#' @family Contextmenu Functions +#' @inheritParams leaflet::addMarkers +#' @return A leaflet map object +#' @export +showContextmenu <- function(map, lat=NULL, lng=NULL, data=leaflet::getMapData(map)) { + pts <- leaflet::derivePoints(data, lng, lat, missing(lng), missing(lat), "showContextmenu") + leaflet::invokeMethod(map, NULL, "showContextmenu", pts) +} + +#' hideContextmenu +#' +#' Hide the contextmenu +#' @family Contextmenu Functions +#' @inheritParams addContextmenu +#' @return A leaflet map object +#' @export +hideContextmenu <- function(map) { + leaflet::invokeMethod(map, NULL, "hideContextmenu") +} + +#' addItemContextmenu +#' +#' Add a new contextmenu menu item +#' @family Contextmenu Functions +#' @inheritParams addContextmenu +#' @param option new menu item to add +#' @return A leaflet map object +#' @export +addItemContextmenu <- function(map, option) { + if (utils::packageVersion("leaflet") < "2.0.4") { + warning("The `addItemContextmenu` function requires leaflet `2.0.4` to correctly register callbacks.") + } + leaflet::invokeMethod(map, NULL, "addItemContextmenu", option) +} +# remotes::install_github("rstudio/leaflet#696") + +#' insertItemContextmenu +#' +#' Insert a new contextmenu menu item at a specific index +#' @family Contextmenu Functions +#' @inheritParams addItemContextmenu +#' @inheritParams removeItemContextmenu +#' @return A leaflet map object +#' @export +insertItemContextmenu <- function(map, option, index) { + if (utils::packageVersion("leaflet") < "2.0.4") { + warning("The `insertItemContextmenu` function requires leaflet `2.0.4` to correctly register callbacks.") + } + leaflet::invokeMethod(map, NULL, "insertItemContextmenu", option, index) +} +# remotes::install_github("rstudio/leaflet#696") + +#' removeItemContextmenu +#' +#' Remove a contextmenu item by index. +#' @family Contextmenu Functions +#' @inheritParams addContextmenu +#' @param index Index of the contextmenu. (NOTE: Since the index is passed to JavaScript, +#' it is zero-based) +#' @return A leaflet map object +#' @export +removeItemContextmenu <- function(map, index) { + leaflet::invokeMethod(map, NULL, "removeItemContextmenu", index) +} + +#' setDisabledContextmenu +#' +#' Enable/Disable a contextmenu item by index. +#' @family Contextmenu Functions +#' @inheritParams removeItemContextmenu +#' @param disabled Set to \code{TRUE} to disable the element and \code{FALSE} +#' to enable it. Default is \code{TRUE} +#' @return A leaflet map object +#' @export +setDisabledContextmenu <- function(map, index, disabled=TRUE) { + leaflet::invokeMethod(map, NULL, "setDisabledContextmenu", index, disabled) +} + +#' removeallItemsContextmenu +#' +#' Remove all contextmenu items from the map. +#' @family Contextmenu Functions +#' @inheritParams removeItemContextmenu +#' @return A leaflet map object +#' @export +removeallItemsContextmenu <- function(map) { + leaflet::invokeMethod(map, NULL, "removeallItemsContextmenu") +} + + + +#' menuItem +#' @param text The label to use for the menu item +#' @param callback A callback function to be invoked when the menu item is +#' clicked. The callback is passed an object with properties identifying the +#' location the menu was opened at: \code{latlng}, \code{layerPoint} and \code{containerPoint}. +#' The callback-function must be valid JavaScript and will be wrapped in +#' \code{\link[leaflet]{JS}}. +#' @param ... For further options please visit \url{https://github.com/aratcliffe/Leaflet.contextmenu} +#' @family Contextmenu Functions +#' @return A contextmenu item list +#' @export +menuItem <- function(text, callback=NULL, ...) { + list(text=text, + callback=leaflet::JS(callback), + ...) +} + +#' mapmenuItems +#' @param ... contextmenu item/s +#' @family Contextmenu Functions +#' @return A list of \code{menuItem} for the map +#' @export +mapmenuItems <- function(...) { + list(...) +} + +#' markermenuItems +#' @param ... contextmenu item/s +#' @family Contextmenu Functions +#' @return A list of \code{menuItem} for markers +#' @export +markermenuItems <- function(...) { + list(list(...)) +} + + diff --git a/R/leaflet.extras2-package.R b/R/leaflet.extras2-package.R index 94bd7228..f442b22f 100644 --- a/R/leaflet.extras2-package.R +++ b/R/leaflet.extras2-package.R @@ -6,7 +6,7 @@ #' @importFrom magrittr %>% #' @import leaflet #' @importFrom htmltools htmlDependency tagGetAttribute tags tagList -#' @importFrom utils globalVariables adist +#' @importFrom utils globalVariables adist packageVersion #' #' @name leaflet.extras2 #' @docType package diff --git a/README.md b/README.md index 809684b1..4af2a1b0 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ remotes::install_github('trafficonese/leaflet.extras2') Plugins integrated so far ... - [Ant Path](https://github.com/rubenspgcavalcante/leaflet-ant-path) +- [Contextmenu](https://github.com/aratcliffe/Leaflet.contextmenu) - [Easy Print](https://github.com/rowanwins/leaflet-easyPrint) - [GIBS](https://github.com/aparshin/leaflet-GIBS) - [Heightgraph](https://github.com/GIScience/Leaflet.Heightgraph) diff --git a/inst/examples/contextmenu_app.R b/inst/examples/contextmenu_app.R new file mode 100644 index 00000000..6a453e81 --- /dev/null +++ b/inst/examples/contextmenu_app.R @@ -0,0 +1,209 @@ +library(shiny) +library(leaflet) +library(leaflet.extras2) + +ui <- fluidPage( + leafletOutput("map", height = "800px"), + actionButton("show", "Show Contextmenu at given Coordinates"), + actionButton("hide", "Hide Contextmenu"), + actionButton("add", "Add Options to Contextmenu"), + actionButton("ins", "Insert Options to Contextmenu"), + actionButton("rm", "Remove Option 2"), + actionButton("disable", "Disable Contextmenu Item 1"), + actionButton("enable", "Enable Contextmenu Item 1"), + actionButton("rmall", "Remove All Map Items"), + splitLayout( + div( + h4("Contextmenu Selection:"), + verbatimTextOutput("print"), + ), + div( + h4("Custom Marker Selection:"), + verbatimTextOutput("print1") + ) + ) +) + +server <- function(input, output, session) { + data <- reactiveVal() + data1 <- reactiveVal() + + output$map <- renderLeaflet({ + ## Basemap #################### + suppressWarnings( + leaflet(options = leafletOptions( + contextmenu=TRUE, + contextmenuWidth = 200, + contextmenuItems = + mapmenuItems( + menuItem("Zoom Out", "function(e) {this.zoomOut();}", disabled=TRUE), + menuItem("Zoom In", "function(e) {this.zoomIn();}"), + "-", + menuItem("Disable index 0", + "function(e) {this.contextmenu.setDisabled(0, true)}", + hideOnSelect = TRUE), + menuItem("Enable index 0", + "function(e) {this.contextmenu.setDisabled(0, false)}", + hideOnSelect = FALSE), + "-", + menuItem(text="Center Map", + callback="function(e) {this.panTo(e.latlng);}", + icon="https://cdn3.iconfinder.com/data/icons/web-15/128/RSSvgLink-2-512.png"), + list(text="Console Log", + callback=JS("function(e) {console.log('e');console.log(e);}")) + ))) %>% + addTiles(group = "base") %>% + addContextmenu() %>% + + ## Points ############################### + addMarkers(data = sf::st_as_sf(leaflet::breweries91), + label=~brewery, layerId = ~founded, group="marker", + options = markerOptions( + contextmenu=TRUE, + contextmenuWidth = 200, + contextmenuItems = + markermenuItems( + menuItem(text = "Show Marker Coords", + "function(e) {debugger; + Shiny.setInputValue(map.id + '_mymarkertrigger', { + menuid: e.relatedTarget.options.contextmenuItems[0].id, + layerId: e.relatedTarget.options.layerId, + lat: e.relatedTarget.options.lat, + lng: e.relatedTarget.options.lng, + opacity: e.relatedTarget.options.opacity + }); + alert(e.latlng);}", + id = "somemarkerid", + icon="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d8/Person_icon_BLACK-01.svg/1200px-Person_icon_BLACK-01.svg.png", + index=0) + ) + )) %>% + ## Lines ############################### + addPolylines(data = sf::st_as_sf(leaflet::atlStorms2005), + layerId = ~Name, group="lines", + label = ~Name, + options = pathOptions( + contextmenu=TRUE, + contextmenuWidth = 400, + contextmenuInheritItems = FALSE, + contextmenuItems = + markermenuItems( + menuItem(text = "Get Line Data", + NULL, + index=0), + menuItem(text = "Delete Line", + "function(e) {e.relatedTarget.remove()}", + icon="https://image.flaticon.com/icons/png/512/1175/1175343.png", + index=1), + menuItem(text = "Change Color/Weight", + "function(e) {e.relatedTarget.setStyle({color: '#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6), + weight: Math.round(Math.random()*10)});}", + icon = "https://cdn3.iconfinder.com/data/icons/ui-glynh-blue-02-of-5/100/UI_Blue_2_of_3_30-512.png", + index = 2), + menuItem(text = "Add Centroid", + "function(e) {L.marker(Object.values(e.relatedTarget.getCenter())).addTo(this);}", + icon = "https://bodylab.ch/wp-content/uploads/2015/11/map-marker-icon.png", + index = 3), + menuItem(text = "Log GeoJSON", + "function(e) {console.log(e.relatedTarget.toGeoJSON());}", + icon = "https://cdn0.iconfinder.com/data/icons/outlinecons-filetypes/512/log-512.png", + index = 4), + menuItem(text = "-", separator=TRUE, + index = 5) + ) + )) %>% + ## Shapes ############################### + addPolygons(data = sf::st_as_sf(leaflet::gadmCHE), + label=~NAME_1, layerId = ~OBJECTID, group="shapes", + options = pathOptions( + contextmenu=TRUE, + contextmenuWidth = 200, + contextmenuItems = + markermenuItems( + menuItem(text = "Get Polygon Coords", + "function(e) {console.log(e.latlng);}", + index = 0), + menuItem(text = "Delete Polygon", + "function(e) {e.relatedTarget.remove()}", + index = 1), + menuItem(text = "Change Color", + "function(e) {e.relatedTarget.setStyle({color: '#4B1BDE', + fillColor : '#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6), + fillOpacity: 1});}", + index = 2), + menuItem(text = "Add Centroid", + "function(e) {L.marker(Object.values(e.relatedTarget.getCenter())).addTo(this);}", + index = 3), + menuItem(text = "Log GeoJSON", + "function(e) {console.log(e.relatedTarget.toGeoJSON());}", + index = 4), + menuItem(text = "-", NULL, separator=TRUE, + index = 5) + ) + )) + ) + }) + + observeEvent(input$map_contextmenu_select, { + txt <- input$map_contextmenu_select + data(txt) + print(txt) + }) + observeEvent(input$map_mymarkertrigger, { + message("Return Value from 'Show Marker Coords' - callback") + txt <- rbind(input$map_mymarkertrigger) + data1(txt) + print(txt) + }) + observeEvent(input$show, { + leafletProxy("map") %>% + showContextmenu(data = leaflet::breweries91[sample(1:32, 1),]) + }) + observeEvent(input$hide, { + leafletProxy("map") %>% + hideContextmenu() + }) + observeEvent(input$add, { + ## Requires https://github.com/rstudio/leaflet/pull/696 to be merged! + leafletProxy("map") %>% + addItemContextmenu( + menuItem(text = "Added Menu Item", + callback = ("function(e) {alert('I am a new menuItem!'); + console.log('e');console.log(e);}"))) + }) + observeEvent(input$ins, { + ## Requires https://github.com/rstudio/leaflet/pull/696 to be merged! + leafletProxy("map") %>% + addItemContextmenu( + menuItem(text = "Inserted Menu Item at Index 2", + callback = ("function(e) {alert('I am an inserted menuItem!'); + console.log('e');console.log(e);}"))) + }) + observeEvent(input$rm, { + leafletProxy("map") %>% + removeItemContextmenu(2) + }) + observeEvent(input$disable, { + leafletProxy("map") %>% + setDisabledContextmenu(1, TRUE) + }) + observeEvent(input$enable, { + leafletProxy("map") %>% + setDisabledContextmenu(1, FALSE) + }) + observeEvent(input$rmall, { + leafletProxy("map") %>% + removeallItemsContextmenu() + }) + + output$print <- renderPrint({ + txt <- req(data()) + print(txt) + }) + output$print1 <- renderPrint({ + txt <- req(data1()) + print(txt) + }) +} + +shinyApp(ui, server) diff --git a/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu-bindings.js b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu-bindings.js new file mode 100644 index 00000000..36533f43 --- /dev/null +++ b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu-bindings.js @@ -0,0 +1,64 @@ +/* global LeafletWidget, $, L, Shiny, HTMLWidgets */ +LeafletWidget.methods.addContextmenu = function() { + var map = this; + + if (HTMLWidgets.shinyMode) { + map.on("contextmenu.select", function(e) { + var obj = { + text: e.el.innerText + }; + if (e.data.relatedTarget) { + var data = { + layerId: e.data.relatedTarget.options.layerId, + group: e.data.relatedTarget.options.group, + lat: e.data.relatedTarget.options.lat, + lng: e.data.relatedTarget.options.lng, + label: e.data.relatedTarget.options.label + }; + obj = Object.assign(obj, data); + } + Shiny.setInputValue(map.id + "_contextmenu_select", obj, {priority: "event"}); + }); + map.on("contextmenu.show", function(e) { + //console.log("contextmenu.show!!!!"); + //console.log("e"); console.log(e); + }); + map.on("contextmenu.hide", function(e) { + //console.log("contextmenu.hide!!!!"); + //console.log("e"); console.log(e); + }); + map.on("contextmenu.additem", function(e) { + //console.log("contextmenu.additem!!!!"); + //console.log("e"); console.log(e); + }); + map.on("contextmenu.removeitem", function(e) { + //console.log("contextmenu.removeitem!!!!"); + //console.log(e); + }); + } +}; + +LeafletWidget.methods.showContextmenu = function(coords) { + this.contextmenu.showAt(new L.LatLng(coords.lat[0], coords.lng[0])); +}; +LeafletWidget.methods.hideContextmenu = function() { + this.contextmenu.hide(); +}; +LeafletWidget.methods.addItemContextmenu = function(options) { + // Requires https://github.com/rstudio/leaflet/pull/696 to be merged! + this.contextmenu.addItem(options); +}; +LeafletWidget.methods.insertItemContextmenu = function(options, index) { + // Requires https://github.com/rstudio/leaflet/pull/696 to be merged! + this.contextmenu.insertItem(options, index); +}; +LeafletWidget.methods.removeItemContextmenu = function(index) { + this.contextmenu.removeItem(index); +}; +LeafletWidget.methods.setDisabledContextmenu = function(index, disabled) { + this.contextmenu.setDisabled(index, disabled); +}; +LeafletWidget.methods.removeallItemsContextmenu = function() { + this.contextmenu.removeAllItems(); +}; + diff --git a/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.css b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.css new file mode 100644 index 00000000..0b5e2def --- /dev/null +++ b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.css @@ -0,0 +1,54 @@ +.leaflet-contextmenu { + display: none; + box-shadow: 0 1px 7px rgba(0,0,0,0.4); + -webkit-border-radius: 4px; + border-radius: 4px; + padding: 4px 0; + background-color: #fff; + cursor: default; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.leaflet-contextmenu a.leaflet-contextmenu-item { + display: block; + color: #222; + font-size: 12px; + line-height: 20px; + text-decoration: none; + padding: 0 12px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + cursor: default; + outline: none; +} + +.leaflet-contextmenu a.leaflet-contextmenu-item-disabled { + opacity: 0.5; +} + +.leaflet-contextmenu a.leaflet-contextmenu-item.over { + background-color: #f4f4f4; + border-top: 1px solid #f0f0f0; + border-bottom: 1px solid #f0f0f0; +} + +.leaflet-contextmenu a.leaflet-contextmenu-item-disabled.over { + background-color: inherit; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; +} + +.leaflet-contextmenu-icon { + margin: 2px 8px 0 0; + width: 16px; + height: 16px; + float: left; + border: 0; +} + +.leaflet-contextmenu-separator { + border-bottom: 1px solid #ccc; + margin: 5px 0; +} diff --git a/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.js b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.js new file mode 100644 index 00000000..d9e7127e --- /dev/null +++ b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.js @@ -0,0 +1,602 @@ +/* + Leaflet.contextmenu, a context menu for Leaflet. + (c) 2015, Adam Ratcliffe, GeoSmart Maps Limited + + @preserve +*/ + +(function(factory) { + // Packaging/modules magic dance + var L; + if (typeof define === 'function' && define.amd) { + // AMD + define(['leaflet'], factory); + } else if (typeof module === 'object' && typeof module.exports === 'object') { + // Node/CommonJS + L = require('leaflet'); + module.exports = factory(L); + } else { + // Browser globals + if (typeof window.L === 'undefined') { + throw new Error('Leaflet must be loaded first'); + } + factory(window.L); + } +})(function(L) { +L.Map.mergeOptions({ + contextmenuItems: [] +}); + +L.Map.ContextMenu = L.Handler.extend({ + _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', + + statics: { + BASE_CLS: 'leaflet-contextmenu' + }, + + initialize: function (map) { + L.Handler.prototype.initialize.call(this, map); + + this._items = []; + this._visible = false; + + var container = this._container = L.DomUtil.create('div', L.Map.ContextMenu.BASE_CLS, map._container); + container.style.zIndex = 10000; + container.style.position = 'absolute'; + + if (map.options.contextmenuWidth) { + container.style.width = map.options.contextmenuWidth + 'px'; + } + + this._createItems(); + + L.DomEvent + .on(container, 'click', L.DomEvent.stop) + .on(container, 'mousedown', L.DomEvent.stop) + .on(container, 'dblclick', L.DomEvent.stop) + .on(container, 'contextmenu', L.DomEvent.stop); + }, + + addHooks: function () { + var container = this._map.getContainer(); + + L.DomEvent + .on(container, 'mouseleave', this._hide, this) + .on(document, 'keydown', this._onKeyDown, this); + + if (L.Browser.touch) { + L.DomEvent.on(document, this._touchstart, this._hide, this); + } + + this._map.on({ + contextmenu: this._show, + mousedown: this._hide, + zoomstart: this._hide + }, this); + }, + + removeHooks: function () { + var container = this._map.getContainer(); + + L.DomEvent + .off(container, 'mouseleave', this._hide, this) + .off(document, 'keydown', this._onKeyDown, this); + + if (L.Browser.touch) { + L.DomEvent.off(document, this._touchstart, this._hide, this); + } + + this._map.off({ + contextmenu: this._show, + mousedown: this._hide, + zoomstart: this._hide + }, this); + }, + + showAt: function (point, data) { + if (point instanceof L.LatLng) { + point = this._map.latLngToContainerPoint(point); + } + + this._showAtPoint(point, data); + }, + + hide: function () { + this._hide(); + }, + + addItem: function (options) { + return this.insertItem(options); + }, + + insertItem: function (options, index) { + index = index !== undefined ? index: this._items.length; + + var item = this._createItem(this._container, options, index); + + this._items.push(item); + + this._sizeChanged = true; + + this._map.fire('contextmenu.additem', { + contextmenu: this, + el: item.el, + index: index + }); + + return item.el; + }, + + removeItem: function (item) { + var container = this._container; + + if (!isNaN(item)) { + item = container.children[item]; + } + + if (item) { + this._removeItem(L.Util.stamp(item)); + + this._sizeChanged = true; + + this._map.fire('contextmenu.removeitem', { + contextmenu: this, + el: item + }); + + return item; + } + + return null; + }, + + removeAllItems: function () { + var items = this._container.children, + item; + + while (items.length) { + item = items[0]; + this._removeItem(L.Util.stamp(item)); + } + return items; + }, + + hideAllItems: function () { + var item, i, l; + + for (i = 0, l = this._items.length; i < l; i++) { + item = this._items[i]; + item.el.style.display = 'none'; + } + }, + + showAllItems: function () { + var item, i, l; + + for (i = 0, l = this._items.length; i < l; i++) { + item = this._items[i]; + item.el.style.display = ''; + } + }, + + setDisabled: function (item, disabled) { + var container = this._container, + itemCls = L.Map.ContextMenu.BASE_CLS + '-item'; + + if (!isNaN(item)) { + item = container.children[item]; + } + + if (item && L.DomUtil.hasClass(item, itemCls)) { + if (disabled) { + L.DomUtil.addClass(item, itemCls + '-disabled'); + this._map.fire('contextmenu.disableitem', { + contextmenu: this, + el: item + }); + } else { + L.DomUtil.removeClass(item, itemCls + '-disabled'); + this._map.fire('contextmenu.enableitem', { + contextmenu: this, + el: item + }); + } + } + }, + + isVisible: function () { + return this._visible; + }, + + _createItems: function () { + var itemOptions = this._map.options.contextmenuItems, + item, + i, l; + + for (i = 0, l = itemOptions.length; i < l; i++) { + this._items.push(this._createItem(this._container, itemOptions[i])); + } + }, + + _createItem: function (container, options, index) { + if (options.separator || options === '-') { + return this._createSeparator(container, index); + } + + var itemCls = L.Map.ContextMenu.BASE_CLS + '-item', + cls = options.disabled ? (itemCls + ' ' + itemCls + '-disabled') : itemCls, + el = this._insertElementAt('a', cls, container, index), + callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect), + icon = this._getIcon(options), + iconCls = this._getIconCls(options), + html = ''; + + if (icon) { + html = ''; + } else if (iconCls) { + html = ''; + } + + el.innerHTML = html + options.text; + el.href = '#'; + + L.DomEvent + .on(el, 'mouseover', this._onItemMouseOver, this) + .on(el, 'mouseout', this._onItemMouseOut, this) + .on(el, 'mousedown', L.DomEvent.stopPropagation) + .on(el, 'click', callback); + + if (L.Browser.touch) { + L.DomEvent.on(el, this._touchstart, L.DomEvent.stopPropagation); + } + + // Devices without a mouse fire "mouseover" on tap, but never “mouseout" + if (!L.Browser.pointer) { + L.DomEvent.on(el, 'click', this._onItemMouseOut, this); + } + + return { + id: L.Util.stamp(el), + el: el, + callback: callback + }; + }, + + _removeItem: function (id) { + var item, + el, + i, l, callback; + + for (i = 0, l = this._items.length; i < l; i++) { + item = this._items[i]; + + if (item.id === id) { + el = item.el; + callback = item.callback; + + if (callback) { + L.DomEvent + .off(el, 'mouseover', this._onItemMouseOver, this) + .off(el, 'mouseover', this._onItemMouseOut, this) + .off(el, 'mousedown', L.DomEvent.stopPropagation) + .off(el, 'click', callback); + + if (L.Browser.touch) { + L.DomEvent.off(el, this._touchstart, L.DomEvent.stopPropagation); + } + + if (!L.Browser.pointer) { + L.DomEvent.on(el, 'click', this._onItemMouseOut, this); + } + } + + this._container.removeChild(el); + this._items.splice(i, 1); + + return item; + } + } + return null; + }, + + _createSeparator: function (container, index) { + var el = this._insertElementAt('div', L.Map.ContextMenu.BASE_CLS + '-separator', container, index); + + return { + id: L.Util.stamp(el), + el: el + }; + }, + + _createEventHandler: function (el, func, context, hideOnSelect) { + var me = this, + map = this._map, + disabledCls = L.Map.ContextMenu.BASE_CLS + '-item-disabled', + hideOnSelect = (hideOnSelect !== undefined) ? hideOnSelect : true; + + return function (e) { + if (L.DomUtil.hasClass(el, disabledCls)) { + return; + } + + var map = me._map, + containerPoint = me._showLocation.containerPoint, + layerPoint = map.containerPointToLayerPoint(containerPoint), + latlng = map.layerPointToLatLng(layerPoint), + relatedTarget = me._showLocation.relatedTarget, + data = { + containerPoint: containerPoint, + layerPoint: layerPoint, + latlng: latlng, + relatedTarget: relatedTarget + }; + + if (hideOnSelect) { + me._hide(); + } + + if (func) { + func.call(context || map, data); + } + + me._map.fire('contextmenu.select', { + contextmenu: me, + el: el, + data: data + }); + }; + }, + + _insertElementAt: function (tagName, className, container, index) { + var refEl, + el = document.createElement(tagName); + + el.className = className; + + if (index !== undefined) { + refEl = container.children[index]; + } + + if (refEl) { + container.insertBefore(el, refEl); + } else { + container.appendChild(el); + } + + return el; + }, + + _show: function (e) { + this._showAtPoint(e.containerPoint, e); + }, + + _showAtPoint: function (pt, data) { + if (this._items.length) { + var map = this._map, + event = L.extend(data || {}, {contextmenu: this}); + + this._showLocation = { + containerPoint: pt + }; + if (data && data.relatedTarget){ + this._showLocation.relatedTarget = data.relatedTarget; + } + + this._setPosition(pt); + + if (!this._visible) { + this._container.style.display = 'block'; + this._visible = true; + } + + this._map.fire('contextmenu.show', event); + } + if(data){ + L.DomEvent.stopPropagation(data); + } + }, + + _hide: function () { + if (this._visible) { + this._visible = false; + this._container.style.display = 'none'; + this._map.fire('contextmenu.hide', {contextmenu: this}); + } + }, + + _getIcon: function (options) { + return L.Browser.retina && options.retinaIcon || options.icon; + }, + + _getIconCls: function (options) { + return L.Browser.retina && options.retinaIconCls || options.iconCls; + }, + + _setPosition: function (pt) { + var mapSize = this._map.getSize(), + container = this._container, + containerSize = this._getElementSize(container), + anchor; + + if (this._map.options.contextmenuAnchor) { + anchor = L.point(this._map.options.contextmenuAnchor); + pt = pt.add(anchor); + } + + container._leaflet_pos = pt; + + if (pt.x + containerSize.x > mapSize.x) { + container.style.left = 'auto'; + container.style.right = Math.min(Math.max(mapSize.x - pt.x, 0), mapSize.x - containerSize.x - 1) + 'px'; + } else { + container.style.left = Math.max(pt.x, 0) + 'px'; + container.style.right = 'auto'; + } + + if (pt.y + containerSize.y > mapSize.y) { + container.style.top = 'auto'; + container.style.bottom = Math.min(Math.max(mapSize.y - pt.y, 0), mapSize.y - containerSize.y - 1) + 'px'; + } else { + container.style.top = Math.max(pt.y, 0) + 'px'; + container.style.bottom = 'auto'; + } + }, + + _getElementSize: function (el) { + var size = this._size, + initialDisplay = el.style.display; + + if (!size || this._sizeChanged) { + size = {}; + + el.style.left = '-999999px'; + el.style.right = 'auto'; + el.style.display = 'block'; + + size.x = el.offsetWidth; + size.y = el.offsetHeight; + + el.style.left = 'auto'; + el.style.display = initialDisplay; + + this._sizeChanged = false; + } + + return size; + }, + + _onKeyDown: function (e) { + var key = e.keyCode; + + // If ESC pressed and context menu is visible hide it + if (key === 27) { + this._hide(); + } + }, + + _onItemMouseOver: function (e) { + L.DomUtil.addClass(e.target || e.srcElement, 'over'); + }, + + _onItemMouseOut: function (e) { + L.DomUtil.removeClass(e.target || e.srcElement, 'over'); + } +}); + +L.Map.addInitHook('addHandler', 'contextmenu', L.Map.ContextMenu); +L.Mixin.ContextMenu = { + bindContextMenu: function (options) { + L.setOptions(this, options); + this._initContextMenu(); + + return this; + }, + + unbindContextMenu: function (){ + this.off('contextmenu', this._showContextMenu, this); + + return this; + }, + + addContextMenuItem: function (item) { + this.options.contextmenuItems.push(item); + }, + + removeContextMenuItemWithIndex: function (index) { + var items = []; + for (var i = 0; i < this.options.contextmenuItems.length; i++) { + if (this.options.contextmenuItems[i].index == index){ + items.push(i); + } + } + var elem = items.pop(); + while (elem !== undefined) { + this.options.contextmenuItems.splice(elem,1); + elem = items.pop(); + } + }, + + replaceContextMenuItem: function (item) { + this.removeContextMenuItemWithIndex(item.index); + this.addContextMenuItem(item); + }, + + _initContextMenu: function () { + this._items = []; + + this.on('contextmenu', this._showContextMenu, this); + }, + + _showContextMenu: function (e) { + var itemOptions, + data, pt, i, l; + + if (this._map.contextmenu) { + data = L.extend({relatedTarget: this}, e); + + pt = this._map.mouseEventToContainerPoint(e.originalEvent); + + if (!this.options.contextmenuInheritItems) { + this._map.contextmenu.hideAllItems(); + } + if(this.options.contextmenuWidth) { + this._map.contextmenu._container.style.width = this.options.contextmenuWidth + 'px'; + } + + for (i = 0, l = this.options.contextmenuItems.length; i < l; i++) { + itemOptions = this.options.contextmenuItems[i]; + this._items.push(this._map.contextmenu.insertItem(itemOptions, itemOptions.index)); + } + + //this._map.once('contextmenu.hide', this._hideContextMenu, this); + this._map.once('contextmenu.hide', this._hideContextMenu(this._map), this); + + this._map.contextmenu.showAt(pt, data); + } + }, + + _hideContextMenu: function (m) { + return function() { + var i, l; + + for (i = 0, l = this._items.length; i < l; i++) { + m.contextmenu.removeItem(this._items[i]); + } + this._items.length = 0; + + if (!this.options.contextmenuInheritItems) { + m.contextmenu.showAllItems(); + } + } + } +}; + +var classes = [L.Marker, L.Path], + defaultOptions = { + contextmenu: false, + contextmenuItems: [], + contextmenuInheritItems: true + }, + cls, i, l; + +for (i = 0, l = classes.length; i < l; i++) { + cls = classes[i]; + + // L.Class should probably provide an empty options hash, as it does not test + // for it here and add if needed + if (!cls.prototype.options) { + cls.prototype.options = defaultOptions; + } else { + cls.mergeOptions(defaultOptions); + } + + cls.addInitHook(function () { + if (this.options.contextmenu) { + this._initContextMenu(); + } + }); + + cls.include(L.Mixin.ContextMenu); +} +return L.Map.ContextMenu; +}); diff --git a/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.min.css b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.min.css new file mode 100644 index 00000000..ef6c6a0e --- /dev/null +++ b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.min.css @@ -0,0 +1 @@ +.leaflet-contextmenu{display:none;box-shadow:0 1px 7px rgba(0,0,0,0.4);-webkit-border-radius:4px;border-radius:4px;padding:4px 0;background-color:#fff;cursor:default;-webkit-user-select:none;-moz-user-select:none;user-select:none}.leaflet-contextmenu a.leaflet-contextmenu-item{display:block;color:#222;font-size:12px;line-height:20px;text-decoration:none;padding:0 12px;border-top:1px solid transparent;border-bottom:1px solid transparent;cursor:default;outline:0}.leaflet-contextmenu a.leaflet-contextmenu-item-disabled{opacity:.5}.leaflet-contextmenu a.leaflet-contextmenu-item.over{background-color:#f4f4f4;border-top:1px solid #f0f0f0;border-bottom:1px solid #f0f0f0}.leaflet-contextmenu a.leaflet-contextmenu-item-disabled.over{background-color:inherit;border-top:1px solid transparent;border-bottom:1px solid transparent}.leaflet-contextmenu-icon{margin:2px 8px 0 0;width:16px;height:16px;float:left;border:0}.leaflet-contextmenu-separator{border-bottom:1px solid #ccc;margin:5px 0} diff --git a/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.min.js b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.min.js new file mode 100644 index 00000000..80a88708 --- /dev/null +++ b/inst/htmlwidgets/lfx-contextmenu/leaflet.contextmenu.min.js @@ -0,0 +1,7 @@ +/* + Leaflet.contextmenu, a context menu for Leaflet. + (c) 2015, Adam Ratcliffe, GeoSmart Maps Limited + + @preserve +*/ +(function(t){var e;if(typeof define==="function"&&define.amd){define(["leaflet"],t)}else if(typeof module==="object"&&typeof module.exports==="object"){e=require("leaflet");module.exports=t(e)}else{if(typeof window.L==="undefined"){throw new Error("Leaflet must be loaded first")}t(window.L)}})(function(t){t.Map.mergeOptions({contextmenuItems:[]});t.Map.ContextMenu=t.Handler.extend({_touchstart:t.Browser.msPointer?"MSPointerDown":t.Browser.pointer?"pointerdown":"touchstart",statics:{BASE_CLS:"leaflet-contextmenu"},initialize:function(e){t.Handler.prototype.initialize.call(this,e);this._items=[];this._visible=false;var n=this._container=t.DomUtil.create("div",t.Map.ContextMenu.BASE_CLS,e._container);n.style.zIndex=1e4;n.style.position="absolute";if(e.options.contextmenuWidth){n.style.width=e.options.contextmenuWidth+"px"}this._createItems();t.DomEvent.on(n,"click",t.DomEvent.stop).on(n,"mousedown",t.DomEvent.stop).on(n,"dblclick",t.DomEvent.stop).on(n,"contextmenu",t.DomEvent.stop)},addHooks:function(){var e=this._map.getContainer();t.DomEvent.on(e,"mouseleave",this._hide,this).on(document,"keydown",this._onKeyDown,this);if(t.Browser.touch){t.DomEvent.on(document,this._touchstart,this._hide,this)}this._map.on({contextmenu:this._show,mousedown:this._hide,zoomstart:this._hide},this)},removeHooks:function(){var e=this._map.getContainer();t.DomEvent.off(e,"mouseleave",this._hide,this).off(document,"keydown",this._onKeyDown,this);if(t.Browser.touch){t.DomEvent.off(document,this._touchstart,this._hide,this)}this._map.off({contextmenu:this._show,mousedown:this._hide,zoomstart:this._hide},this)},showAt:function(e,n){if(e instanceof t.LatLng){e=this._map.latLngToContainerPoint(e)}this._showAtPoint(e,n)},hide:function(){this._hide()},addItem:function(t){return this.insertItem(t)},insertItem:function(t,e){e=e!==undefined?e:this._items.length;var n=this._createItem(this._container,t,e);this._items.push(n);this._sizeChanged=true;this._map.fire("contextmenu.additem",{contextmenu:this,el:n.el,index:e});return n.el},removeItem:function(e){var n=this._container;if(!isNaN(e)){e=n.children[e]}if(e){this._removeItem(t.Util.stamp(e));this._sizeChanged=true;this._map.fire("contextmenu.removeitem",{contextmenu:this,el:e});return e}return null},removeAllItems:function(){var e=this._container.children,n;while(e.length){n=e[0];this._removeItem(t.Util.stamp(n))}return e},hideAllItems:function(){var t,e,n;for(e=0,n=this._items.length;e'}else if(m){u=''}h.innerHTML=u+n.text;h.href="#";t.DomEvent.on(h,"mouseover",this._onItemMouseOver,this).on(h,"mouseout",this._onItemMouseOut,this).on(h,"mousedown",t.DomEvent.stopPropagation).on(h,"click",r);if(t.Browser.touch){t.DomEvent.on(h,this._touchstart,t.DomEvent.stopPropagation)}if(!t.Browser.pointer){t.DomEvent.on(h,"click",this._onItemMouseOut,this)}return{id:t.Util.stamp(h),el:h,callback:r}},_removeItem:function(e){var n,i,o,s,h;for(o=0,s=this._items.length;on.x){i.style.left="auto";i.style.right=Math.min(Math.max(n.x-e.x,0),n.x-o.x-1)+"px"}else{i.style.left=Math.max(e.x,0)+"px";i.style.right="auto"}if(e.y+o.y>n.y){i.style.top="auto";i.style.bottom=Math.min(Math.max(n.y-e.y,0),n.y-o.y-1)+"px"}else{i.style.top=Math.max(e.y,0)+"px";i.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(e){t.DomUtil.addClass(e.target||e.srcElement,"over")},_onItemMouseOut:function(e){t.DomUtil.removeClass(e.target||e.srcElement,"over")}});t.Map.addInitHook("addHandler","contextmenu",t.Map.ContextMenu);t.Mixin.ContextMenu={bindContextMenu:function(e){t.setOptions(this,e);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},addContextMenuItem:function(t){this.options.contextmenuItems.push(t)},removeContextMenuItemWithIndex:function(t){var e=[];for(var n=0;n\% + addTiles(group = "base") \%>\% + addContextmenu() \%>\% + addMarkers(data = breweries91, label = ~brewery, + layerId = ~founded, group = "marker", + options = markerOptions( + contextmenu = TRUE, + contextmenuWidth = 200, + contextmenuItems = + markermenuItems( + menuItem(text = "Show Marker Coords", + callback = "function(e) {alert(e.latlng);}", + index = 1) + ) + )) + +} +\references{ +\url{https://github.com/aratcliffe/Leaflet.contextmenu} +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/addItemContextmenu.Rd b/man/addItemContextmenu.Rd new file mode 100644 index 00000000..3a667ef3 --- /dev/null +++ b/man/addItemContextmenu.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{addItemContextmenu} +\alias{addItemContextmenu} +\title{addItemContextmenu} +\usage{ +addItemContextmenu(map, option) +} +\arguments{ +\item{map}{a map widget object created from \code{\link[leaflet]{leaflet}}} + +\item{option}{new menu item to add} +} +\value{ +A leaflet map object +} +\description{ +Add a new contextmenu menu item +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/hideContextmenu.Rd b/man/hideContextmenu.Rd new file mode 100644 index 00000000..e774c2c0 --- /dev/null +++ b/man/hideContextmenu.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{hideContextmenu} +\alias{hideContextmenu} +\title{hideContextmenu} +\usage{ +hideContextmenu(map) +} +\arguments{ +\item{map}{a map widget object created from \code{\link[leaflet]{leaflet}}} +} +\value{ +A leaflet map object +} +\description{ +Hide the contextmenu +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/insertItemContextmenu.Rd b/man/insertItemContextmenu.Rd new file mode 100644 index 00000000..8d0f132c --- /dev/null +++ b/man/insertItemContextmenu.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{insertItemContextmenu} +\alias{insertItemContextmenu} +\title{insertItemContextmenu} +\usage{ +insertItemContextmenu(map, option, index) +} +\arguments{ +\item{map}{a map widget object created from \code{\link[leaflet]{leaflet}}} + +\item{option}{new menu item to add} + +\item{index}{Index of the contextmenu. (NOTE: Since the index is passed to JavaScript, +it is zero-based)} +} +\value{ +A leaflet map object +} +\description{ +Insert a new contextmenu menu item at a specific index +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/mapmenuItems.Rd b/man/mapmenuItems.Rd new file mode 100644 index 00000000..cd4e9c47 --- /dev/null +++ b/man/mapmenuItems.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{mapmenuItems} +\alias{mapmenuItems} +\title{mapmenuItems} +\usage{ +mapmenuItems(...) +} +\arguments{ +\item{...}{contextmenu item/s} +} +\value{ +A list of \code{menuItem} for the map +} +\description{ +mapmenuItems +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/markermenuItems.Rd b/man/markermenuItems.Rd new file mode 100644 index 00000000..03f039fc --- /dev/null +++ b/man/markermenuItems.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{markermenuItems} +\alias{markermenuItems} +\title{markermenuItems} +\usage{ +markermenuItems(...) +} +\arguments{ +\item{...}{contextmenu item/s} +} +\value{ +A list of \code{menuItem} for markers +} +\description{ +markermenuItems +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/menuItem.Rd b/man/menuItem.Rd new file mode 100644 index 00000000..e0324394 --- /dev/null +++ b/man/menuItem.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{menuItem} +\alias{menuItem} +\title{menuItem} +\usage{ +menuItem(text, callback = NULL, ...) +} +\arguments{ +\item{text}{The label to use for the menu item} + +\item{callback}{A callback function to be invoked when the menu item is +clicked. The callback is passed an object with properties identifying the +location the menu was opened at: \code{latlng}, \code{layerPoint} and \code{containerPoint}. +The callback-function must be valid JavaScript and will be wrapped in +\code{\link[leaflet]{JS}}.} + +\item{...}{For further options please visit \url{https://github.com/aratcliffe/Leaflet.contextmenu}} +} +\value{ +A contextmenu item list +} +\description{ +menuItem +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/removeItemContextmenu.Rd b/man/removeItemContextmenu.Rd new file mode 100644 index 00000000..780d283e --- /dev/null +++ b/man/removeItemContextmenu.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{removeItemContextmenu} +\alias{removeItemContextmenu} +\title{removeItemContextmenu} +\usage{ +removeItemContextmenu(map, index) +} +\arguments{ +\item{map}{a map widget object created from \code{\link[leaflet]{leaflet}}} + +\item{index}{Index of the contextmenu. (NOTE: Since the index is passed to JavaScript, +it is zero-based)} +} +\value{ +A leaflet map object +} +\description{ +Remove a contextmenu item by index. +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/removeallItemsContextmenu.Rd b/man/removeallItemsContextmenu.Rd new file mode 100644 index 00000000..e7fd454c --- /dev/null +++ b/man/removeallItemsContextmenu.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{removeallItemsContextmenu} +\alias{removeallItemsContextmenu} +\title{removeallItemsContextmenu} +\usage{ +removeallItemsContextmenu(map) +} +\arguments{ +\item{map}{a map widget object created from \code{\link[leaflet]{leaflet}}} +} +\value{ +A leaflet map object +} +\description{ +Remove all contextmenu items from the map. +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{setDisabledContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/setDisabledContextmenu.Rd b/man/setDisabledContextmenu.Rd new file mode 100644 index 00000000..3ef778a5 --- /dev/null +++ b/man/setDisabledContextmenu.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{setDisabledContextmenu} +\alias{setDisabledContextmenu} +\title{setDisabledContextmenu} +\usage{ +setDisabledContextmenu(map, index, disabled = TRUE) +} +\arguments{ +\item{map}{a map widget object created from \code{\link[leaflet]{leaflet}}} + +\item{index}{Index of the contextmenu. (NOTE: Since the index is passed to JavaScript, +it is zero-based)} + +\item{disabled}{Set to \code{TRUE} to disable the element and \code{FALSE} +to enable it. Default is \code{TRUE}} +} +\value{ +A leaflet map object +} +\description{ +Enable/Disable a contextmenu item by index. +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{showContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/man/showContextmenu.Rd b/man/showContextmenu.Rd new file mode 100644 index 00000000..6f1cee61 --- /dev/null +++ b/man/showContextmenu.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contextmenu.R +\name{showContextmenu} +\alias{showContextmenu} +\title{showContextmenu} +\usage{ +showContextmenu(map, lat = NULL, lng = NULL, data = leaflet::getMapData(map)) +} +\arguments{ +\item{map}{a map widget object created from \code{\link[leaflet]{leaflet}()}} + +\item{lat}{a vector of latitudes or a formula (similar to the \code{lng} +argument; the names \code{lat} and \code{latitude} are used when guessing +the latitude column from \code{data})} + +\item{lng}{a numeric vector of longitudes, or a one-sided formula of the form +\code{~x} where \code{x} is a variable in \code{data}; by default (if not +explicitly provided), it will be automatically inferred from \code{data} by +looking for a column named \code{lng}, \code{long}, or \code{longitude} +(case-insensitively)} + +\item{data}{the data object from which the argument values are derived; by +default, it is the \code{data} object provided to \code{leaflet()} +initially, but can be overridden} +} +\value{ +A leaflet map object +} +\description{ +Open the contextmenu at certain lat/lng-coordinates +} +\seealso{ +Other Contextmenu Functions: +\code{\link{addContextmenu}()}, +\code{\link{addItemContextmenu}()}, +\code{\link{hideContextmenu}()}, +\code{\link{insertItemContextmenu}()}, +\code{\link{mapmenuItems}()}, +\code{\link{markermenuItems}()}, +\code{\link{menuItem}()}, +\code{\link{removeItemContextmenu}()}, +\code{\link{removeallItemsContextmenu}()}, +\code{\link{setDisabledContextmenu}()} +} +\concept{Contextmenu Functions} diff --git a/tests/testthat/test-contextmenu.R b/tests/testthat/test-contextmenu.R new file mode 100644 index 00000000..503f697a --- /dev/null +++ b/tests/testthat/test-contextmenu.R @@ -0,0 +1,128 @@ + +test_that("contextmenu", { + m <- leaflet(options = leafletOptions( + contextmenu = TRUE, + contextmenuWidth = 200, + contextmenuItems = + mapmenuItems( + menuItem("Zoom Out", "function(e) {this.zoomOut()}", disabled=FALSE), + "-", + menuItem("Zoom In", "function(e) {this.zoomIn()}")))) %>% + addTiles(group = "base") %>% + addContextmenu() %>% + addMarkers(data = breweries91, label = ~brewery, + layerId = ~founded, group = "marker", + options = markerOptions( + contextmenu = TRUE, + contextmenuWidth = 200, + contextmenuItems = + markermenuItems( + menuItem(text = "Show Marker Coords", + callback = "function(e) {alert(e.latlng);}", + index = 1) + ) + )) + + expect_is(m, "leaflet") + expect_true(m$x$options$contextmenu) + expect_is(m$x$options$contextmenuWidth, "numeric") + expect_is(m$x$options$contextmenuItems, "list") + expect_is(m$x$options$contextmenuItems[[1]]$callback, "JS_EVAL") + + deps <- findDependencies(m) + expect_equal(deps[[length(deps)]]$name, "lfx-contextmenu") + + m <- m %>% showContextmenu(data = leaflet::breweries91[sample(1:32, 1),]) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "showContextmenu") + expect_true(all(colnames(m$x$calls[[length(m$x$calls)]]$args[[1]]) %in% c("lng","lat"))) + + m <- m %>% showContextmenu(lat = 49.79433, lng = 11.50941) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "showContextmenu") + expect_true(all(colnames(m$x$calls[[length(m$x$calls)]]$args[[1]]) %in% c("lng","lat"))) + + m <- m %>% hideContextmenu() + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "hideContextmenu") + + if (packageVersion("leaflet") < "2.0.4") { + m <- expect_warning( + m %>% addItemContextmenu( + menuItem(text = "Added Menu Item", + callback = ("function(e) {alert('I am a new menuItem!'); + console.log('e');console.log(e);}"))) + ) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "addItemContextmenu") + + m <- expect_warning( + m %>% insertItemContextmenu(index = 1, + menuItem(text = "Inserted Menu Item", + callback = ("function(e) {alert('I am an inserted menuItem!');}"))) + ) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "insertItemContextmenu") + + } else { + m <- m %>% addItemContextmenu( + menuItem(text = "Added Menu Item", + callback = ("function(e) {alert('I am a new menuItem!'); + console.log('e');console.log(e);}"))) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "addItemContextmenu") + + m <- m %>% insertItemContextmenu(index = 2, + menuItem(text = "Added Menu Item", + callback = ("function(e) {alert('I am an inserted menuItem!');}"))) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "insertItemContextmenu") + } + + m <- m %>% removeItemContextmenu(index=1) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "removeItemContextmenu") + expect_true(m$x$calls[[length(m$x$calls)]]$args[[1]] == 1) + + m <- m %>% setDisabledContextmenu(index=1, disabled = TRUE) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "setDisabledContextmenu") + expect_true(m$x$calls[[length(m$x$calls)]]$args[[1]] == 1) + expect_true(m$x$calls[[length(m$x$calls)]]$args[[2]] == TRUE) + + m <- m %>% setDisabledContextmenu(index=2, disabled = FALSE) + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "setDisabledContextmenu") + expect_true(m$x$calls[[length(m$x$calls)]]$args[[1]] == 2) + expect_true(m$x$calls[[length(m$x$calls)]]$args[[2]] == FALSE) + + m <- m %>% removeallItemsContextmenu() + expect_equal(m$x$calls[[length(m$x$calls)]]$method, + "removeallItemsContextmenu") + + + mn <- menuItem("some text", "my callback", id="myid") + expect_is(mn, "list") + expect_is(mn$callback, "JS_EVAL") + + mn1 <- menuItem(id="myid", "some text", "my callback") + expect_is(mn, "list") + expect_is(mn$callback, "JS_EVAL") + expect_identical(mn1, mn) + + mn <- mapmenuItems( + menuItem("some text", "my callback", id="myid"), + menuItem("some other text", "my callback", id="myid2") + ) + expect_is(mn, "list") + expect_length(mn, 2) + + mn <- markermenuItems( + menuItem("some text", "my callback", id="myid"), + menuItem("some other text", "my callback", id="myid2") + ) + expect_is(mn, "list") + expect_length(mn, 1) + +}) +