OpenLayers zoom scroll events are ignored if no layers are visible - javascript

I've noticed that if all OpenLayers.Layers have visibility set to false, I can no longer zoom in and out using my mousewheel. I can still use the OpenLayers.Control.Zoom buttons, but no longer the mouse wheel.
Is there anyway to disable this 'feature'?
Edit:
Here is a jsfiddle link. Set the layer to invisible, then scroll, then set it back to visible. It doesn't change. Now set it invisible, use the zoom control button, then set it back visible.
http://jsfiddle.net/a8kK4/56/
Here's some code for how the map is instantiated because SO won't let me post jsfiddle without it:
var map = new OpenLayers.Map({
div: "map",
projection: new OpenLayers.Projection("EPSG:900913"),
displayProjection: new OpenLayers.Projection("EPSG:4326"),
layers: [ new OpenLayers.Layer.OSM() ]
});

The issue is to do with the onWheelEvent of the MouseWheel handler, see the code here on github. The issue arises with the lines
if (!overScrollableDiv && overMapDiv) {
if (allowScroll) {
as with the map hidden, you will never get it so that all of these conditions are met and therefore the call to this.wheelZoom(e) which actually does the zoom in/out never gets called. The behavior you are seeing is, therefore, by design.
One, somewhat crude way to fix this, is to override the whole function and just set the line where allowScroll is initialized to true, ie, you put the the whole function into your code, after the main OpenLayers.js has downloaded, and just change that one line
OpenLayers.Handler.MouseWheel.prototype.onWheelEvent= function(e){
if (!this.map || !this.checkModifiers(e)) {
return;
}
var overScrollableDiv = false;
//CHANGE THIS LINE TO TRUE
var allowScroll = true;
var overMapDiv = false;
//rest of function .......
};
The problem of doing this is that a scroll will now happen if you use the mouse wheel anywhere. There is probably a more elegant way by changing the conditions under which the values of overMapDiv and allowScroll and overScrollableDiv get set. I could not get this to work on jsFiddle (I think due to version conflicts), but locally it worked as expected. I hope this helps.

Related

How to extend Leaflet Icon Class to add data-open attribute to marker HTML?

I'm trying to trigger some functionality based on the click of a marker on a GeoJSON layer in Leaflet. The eventual functionality I'm trying to implement is a flyout, or scroll out type modal populated from the individual feature's JSON attributes. Essentially, I'm trying to implement the functionality in this Tutsplus Tutorial with dynamic feature content based on the marker click.
I THINK I've figured out most of the pieces I need, but I'm struggling with how to add a data attribute, specifically data-open, to the individual marker. Building on an earlier question of mine I've realized it's not enough to just update a DOM element's CSS, but rather my app should be implementing changes based on data attributes to fully get the functionality I want.
From this question I know that this should be done by extending the L.Icon class that Leaflet provides, but the answer is a bit too terse for my current JS skills. I apologize for this effectively being a "ELI5" of a previously asked question, but I'm not sure where the options and slug come into function. I think they're implied by the question, rather than the answer I'm citing and being set on the marker itself.
Here's a simplified version of the the click handler on my markers, which grabs and zooms to location, gets feature info, and populates that info to a div. The zoom functionality works, as does extracting and placing the feature info, but I'm struggling with how to connect the functionality to trigger the modal and place the div with the feature info over the map.
function zoomToFeature(e) {
var latLngs = [e.target.getLatLng()];
var markerBounds = L.latLngBounds(latLngs);
var street = e.target.feature.properties.str_addr;
document.getElementById('street').textContent = street;
mymap.fitBounds(markerBounds);
//where the modal trigger should be
document.getElementById('infoBox').classList.add('is-visible');
}
Here are the event listeners taken from the linked tutorial, which are currently not firing, but I have them working in a standalone implementation:
const openEls = document.querySelectorAll("[data-open]");
const closeEls = document.querySelectorAll("[data-close]");
const isVisible = "is-visible";
//this is the event I want to trigger on marker click
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
console.log(this);
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
So, where I'm trying to get is that when my markers are clicked, the trigger the modal to appear over the map. So, I think I'm missing connecting the marker click event with the event that triggers the modal. I think what's missing is adding the data attribute to the markers, or some way chain the events without the data attributes. As there's no direct way to add an attribute to the markers, I try to add slug option on my circle markers:
var circleMarkerOptions = {
radius: 2,
weight: 1,
opacity: 1,
fillOpacity: 0.8,
slug: 'open',
}
and If I read the previously asked question's answer correctly, than extending the Icon Class this way should add a data-open attribute.
L.Icon.DataMarkup = L.Icon.extend({
_setIconStyles: function(img, name) {
L.Icon.prototype._setIconStyles.call(this, img, name);
if (options.slug) {
img.dataset.slug = options.slug;
}
}
});
A stripped down version of my code is here (thanks #ghybs). My full implementation pulls the markers from a PostGIS table. It's a bit hard to see in the Plunker, but this code adds my class to my modal, but doesn't trigger the functionality. It does trigger the visibility if the class is manually updated to modal.is-visible, but the current implementation which renders modal is-visbile doesn't, which I think is because the CSS is interpreted on page load(?) and not in response to the update via the dev tools, while the concatenated css class matches extactly(?). When I do trigger the modal via the dev tools, the close modal listeners don't seem to work, so I'm also missing that piece of the puzzle.
So, it's a work-around to setting the data attribute, but I realized I was shoe-horning a solution where it wasn't needed. Assuming someone ends up with the same mental block. Appropriate listeners on the modal close button and another function passed to the existing marker click listener produce the desired functionality.
const closeM = document.querySelector(".close-modal");
closeM.addEventListener("click", closeMe);
var modal = document.getElementById('infoBox');
and
function modalAction(){
modal.style.display = 'block';
}
function closeMe(){
modal.style.display = 'none';
}

ng-show only working when set to true from the beginning

I am creating a map-based application using the angularjs library ng-map and have encountered a rather strange bug.
I have the following code:
script.js
vm.showMarker = true //Boolean to toggle marker
vm.toggleMarker = function(){
if(vm.map.getZoom()>=12){
vm.showMarker = true;
} else {
vm.showMarker = false;
}
$scope.$apply();
}
html
<ng-map on-zoom-changed="vm.toggleMarker()" zoom="9">
<custom-marker>
<div ng-show="vm.showMarker">
...
</div>
</custom-marker>
</ng-map>
The idea is that the marker should only be visible if the map is zoomed enough.
If vm.showMarker is set to true from the beginning like above, the marker is first visible when the page is loaded, it then dissapears when zooming until zoom is >= 12 as expected.
However if i set vm.showMarker to false so that it is not visible from the beginning, it does not become visible either when zooming in.
Any idea on what might be causing this behaviour?
Try using ng-if instead:
<custom-marker ng-if="vm.showMarker">

jscrollpane scrolltobottom but pause with mouseover on scrollbar

i have finished this code off but im stuck on teh last thing i have to do, when a item is added it auto scrolls to the bottom which is great but what i cannot work out how to do and im sure its simple is when i use the scroll bar i want to stop it jumping to the bottom until i stop using the scroll bar.
you will see in the example ive provided if you click on scrollbar and drag up it keeps jumping down when it adds a new item, i just need to pause that until i stop using the scroll bar if that makes sense.
it uses api.scrollToBottom(); to scroll to the bottom once the new item is added which is correct, but unsure on the condition for mouseover etc... to stop that when those are the conditions.
Hope someone can help me on this, many thanks in advance! :)
JSFiddle here: http://jsfiddle.net/GT7sV/2/
JS
var settings = {
showArrows: true,
verticalArrowPositions: 'os',
horizontalArrowPositions: 'os',
animateScroll: true
};
var pane = jQuery('.scroll-pane');
pane.jScrollPane(settings);
var api = pane.data('jsp');
var i = 1;
api.scrollToBottom();
setInterval(
function()
{
api.getContentPane().append("<li>testing this new item</li>").children(':last').hide().fadeIn(1000);
api.reinitialise();
api.scrollToBottom();
},
1000
);
});
Thanks!
I dont know if this is ok for you but you could try it in different direction. You could load new rows when user scrolls to the bottom:
conversationHeadersScrollPane = $('.scroll-pane').bind(
'jsp-scroll-y',
function(event, scrollPositionY, isAtTop, isAtBottom) {
if (isAtBottom) {
//add new rows for example with ajax call
}
});
edit: there is a function getPercentScrolledY()
so you could before calling scrollToBottom check if user is for example bellow 90%, if not let him read while he scrolls on his own to the bottom.

Dynamically change the location of the popover depending upon where it displays

I'd like to dynamically change the popover placement (left/right, top/bottom) depending on where the element is located on the screen.
//get_popover_placement(dom_el) returns 'left', 'right', 'top', or 'bottom'
function set_popover(dom_el) {
var the_placement = get_popover_placement(dom_el);
$(dom_el).popover({
offset: 10,
placement: the_placement
}).popover('show');
}
//set the placement on every hover
$('a[data-rel=popover]').hover(function(){
set_popover(this);
}, function(){});
It works the first time, but if the position of the element changes (when the window is resized, for example), the placement is not updated with subsequent calls to set_popover.
I added a little bit of code to get_popover_placement that adds a different color border to the element, depending on the placement. The border color gets updated every time, indicating the code is being called and the calculations are being done correctly, but the placement does not get updated.
It appears as though the placement option can only be set one time. How can I get around this limitation? Is there a variable somewhere that can be cleared to reset popovers?
Try this change, using the .on() function in jQuery to attach an event listener:
Changed this reply by updating Kevin Ansfield's - added code to the placement function.
$('a[data-rel=popover]').popover({
placement: get_popover_placement
});
function get_popover_placement(pop, dom_el) {
var width = window.innerWidth;
if (width<500) return 'bottom';
var left_pos = $(dom_el).offset().left;
if (width - left_pos > 400) return 'right';
return 'left';
}
I just checked the bootstrap source again and realised that functions passed to the placement property get passed arguments. I managed to achieve a similar thing to what you were attempting, try the following and see if it works for you:
$('a[data-rel=popover]').popover({
offset: 10,
placement: get_popover_placement
});
function get_popover_placement(pop, dom_el) {
// your placement function code here
}

Ext.layout.CardLayout requires fullscreen?

I'm programming a Sencha Touch app with a moderately complex composition of Ext.TabBar with Ext.Panel 's inside it.
But my Ext.Panel using Ext.layout.CardLayout runs in to a mysterious problem when not having a fullscreen: true property set on it: it throws an TypeError: Object card has no method 'setActiveItem' when I try to call the panel's .setActiveItem() method which it didn't in my proof of concept version that had fullscreen: true turned on.
I can replicate the problem on Chrome's console at a page with the Sencha Touch library loaded like this:
> var p1 = new Ext.Panel({layout:'card', items:[{html:'a'},{html:'b'}]})
undefined
> p1.setActiveItem(0)
TypeError: Object card has no method 'setActiveItem'
And it doesn't happen with the .fullscreenproperty:
> var p2 = new Ext.Panel({fullscreen: true,
layout:'card',
items:[{html:'a'},{html:'b'}]})
undefined
> p2.setActiveItem(0)
subclass
What gives?
Version info: I'm using Sencha Touch 1.0.1a
Update 1 (Jan 3, ~10.30UTC+1h), stepping around with the debugger and discovering things:
Just setting layout: 'card' won't trigger the creation of an actual Ext.layout.CardLayout object being created for real. Since .setActiveItem() tries to delegate to the compent's .layout property, it will fail almost instantly. However, setting .layout to a new Ext.layout.CardLayout causes more problems down the line..
Update 2: (Jan 3, ~12:25UTC+1h) It all comes down to various component objects not being rendered/inserted in the dependency sufficiently to be ready to render. I managed to get my code working by adding listeners, first a listener for the added event in the enclosing panel that does a this.setLayout(new Ext.layout.CardLayout());, then an afterrender listener on the component being added that finally calls .setActiveItem() to switch to the desired card.
A card layout works fine when it's not the outer, fullscreen one, but of course something has to be the root, fullscreen panel.
In this case, I'm using a 'fit' layout around the inner card layout, and setting the active item works fine:
var inner = new Ext.Panel({
layout:'card',
items:[
{html:'a'},
{html:'b'}
]
});
var outer = new Ext.Panel({
fullscreen:true,
layout:'fit',
items:[
inner
]
});
I suspect the matter is more about whether the panel has been rendered or not, and fullscreen:true just happens to force immediate rendering (and of any children, which is why it works in my code above).
This is from the Ext.Component source:
if (this.fullscreen || this.floating) {
this.monitorOrientation = true;
this.autoRender = true;
}
if (this.fullscreen) {
var viewportSize = Ext.Viewport.getSize();
this.width = viewportSize.width;
this.height = viewportSize.height;
this.cls = (this.cls || '') + ' x-fullscreen';
this.renderTo = document.body;
}
I suppose you could do some of this set up manually... but I have to ask, how do you avoid have a fullscreen ancestor component outside it in the first place?
I had the same problem. The card layout only works if the container panel had the layout of 'fit'. But setting the container class to fit was causing the scroller to not working properly. So what I had to do is disabling the scroller for container and the card panel and set the scroller for the children of card panel.
Update 2 in my question kind of answers the question, although the solution feels distinctly cargo-cultish. I won't be too surprised if it breaks in future Sencha Touch versions.
Sencha may have changed this since this was posted since I don't get an error. However, setActiveItem() will force my views to be rendered immediately which is undesirable when setting up the UI. If you want to set the initial card without rendering the views immediately, use the property directly instead of the setter, like this:
yourComp.activeItem = yourView;
instead of
yourComp.setActiveItem(yourView);

Categories

Resources