Join two observables in RX.js - system.reactive

I'm trying to create new observable based on two others. I have:
var mouseClickObservable = Rx.Observable.fromEvent(this.canvas, "click");
var mouseMoveObservable = Rx.Observable.fromEvent(this.canvas, "mousemove");
function findObject(x, y) {/* logic for finding object under cursor here. */}
var objectUnderCursor = this.mouseMoveObservable.select(function (ev) {
return findObject(ev.clientX, clientY);
});
I want to create objectClicked observable, that should produce values when user clicks on an object. I could just call findObject again, like this:
var objectClicked = this.mouseClickObservable.select(function (ev) {
return findObject(ev.clientX, clientY);
});
but it's very time-consuming function.
Another way, which I currently use, is to store last hovered object in a variable, but I assume, there should be pure functional way of doing this. I tryed to use Observable.join like this:
var objectClicked = this.objectUnderCursor.join(
mouseClickObservable,
function (obj) { return this.objectUnderCursor },
function (ev) { return Rx.Observable.empty() },
function (obj, ev) { return obj })
but it produces multiple values for one click

I don't see any code where you actually subscribe to any of these observables you have defined, so it is hard to provide a good answer. Do you actually need to call findObject on every mouse move? Are you needing to provide some sort of hover effect as the mouse moves? Or do you just need to know the object that was clicked, in which case you only need to call findObject once when clicked?
Assuming you only need to know what object was clicked, you don't even worry about mouse moves and just do something like:
var objectClicked = mouseClickObservable
.select(function (ev) { return findObject(ev.clientX, ev.clientY); });
objectClicked.subscribe(function(o) { ... });
If you indeed need to know what object the mouse is hovering over, but want to avoid calling your expensive hit test also on a click, then indeed you need to store the intermediate value (which you are needing to store anyway to do your hovering effects). You can use a BehaviorSubject for this purpose:
this.objectUnderCursor = new Rx.BehaviorSubject();
mouseMoveObservable
.select(function (ev) { return findObject(ev.clientX, ev.clientY); })
.subscribe(this.objectUnderCursor);
this.objectUnderCursor.subscribe(function (o) { do your hover effects here });
mouseClickObservable
.selectMany(function () { return this.objectUnderCursor; })
.subscribe(function (o) { do your click effect });

Related

Double on click event with mapbox gl

I am redrawing layers on style.load event and removing the layers
map.on('style.load', function() {
loadByBounds(tempBounds)
});
function loadByBounds(b) {
if (map.getLayer("cluster-count")) {
map.removeLayer("cluster-count");
}
...
map.on('click', 'unclustered-point', function(e) {
var popup = new mapboxgl.Popup()
.setLngLat(e.features[0].geometry.coordinates)
.setHTML(text)
.addTo(map);
})}
But how to remove map.on('click') events? As when I click the point the Popup() displays 2 times. And when I change layer one more time the onclick event fires 3 times and so on. So I think I have to remove the click event but how? Thanks
You might wanna use map.once(). This will add a listener that will be called only once to a specified event type. However after 1 click event got fired this event listener won't listen to any further click events.
https://www.mapbox.com/mapbox-gl-js/api/#evented#once
With map.off() it's basically the opposite of map.on() and you can use it to unregister any applied event listeners. However you would need to add event listeners without an anonymous function in order to use map.off().
https://www.mapbox.com/mapbox-gl-js/api/#map#off
// you would need to use a named function
function clickHandler(e) {
// handle click
}
map.on('click', clickHandler);
// then you can use
map.off('click', clickHandler);
// With an anonymous function you won't be able to use map.off
map.on('click', (e) => {
// handle click
});
To prevent your app from registering multiple listeners you maybe need to set a flag that gets set after your first event listener got applied.
let notListening = true;
function loadByBounds(b) {
// ....
if (notListening) {
notListening = false;
map.on('click', (e) => {
// do something
});
}
}

Having trouble attaching event listener to a kml layer's polygon

Using Google Earth I have a loaded kml layer that displays polygons of every county in the US. On click a balloon pop's up with some relevant info about the state (name, which state, area, etc) When a user clicks the polygon I want the information to also pop up on a DIV element somewhere else.
This is my code so far.
var ge;
google.load("earth", "1");
function init() {
google.earth.createInstance('map3d', initCB, failureCB);
}
function initCB(instance) {
ge = instance;
ge.getWindow().setVisibility(true);
ge.getNavigationControl().setVisibility(ge.VISIBILITY_AUTO);
ge.getNavigationControl().setStreetViewEnabled(true);
ge.getLayerRoot().enableLayerById(ge.LAYER_ROADS, true);
//here is where im loading the kml file
google.earth.fetchKml(ge, href, function (kmlObject) {
if (kmlObject) {
// show it on Earth
ge.getFeatures().appendChild(kmlObject);
} else {
setTimeout(function () {
alert('Bad or null KML.');
}, 0);
}
});
function recordEvent(event) {
alert("click");
}
// Listen to the mousemove event on the globe.
google.earth.addEventListener(ge.getGlobe(), 'click', recordEvent);
}
function failureCB(errorCode) {}
google.setOnLoadCallback(init);
My problem is that when I change ge.getGlobe() to kmlObject or ge.getFeatures() it doesn't work.
My first question is what should I change ge.getGlobe() to to be able to get a click listener when a user clicks on a kml layer's polygon?
After that I was planning on using getDescription() or getBalloonHtml() to get the polygons balloons information. Am I even on the right track?
...what should I change ge.getGlobe() to...
You don't need to change the event object from GEGlobe. Indeed it is the best option as you can use it to capture all the events and then check the target object in the handler. This means you only have to set up a single event listener in the API.
The other option would be to somehow parse the KML and attach specific event handlers to specific objects. This means you have to create an event listener for each object.
Am I even on the right track?
So, yes you are on the right track. I would keep the generic GEGlobe event listener but extend your recordEvent method to check for the types of KML object you are interested in. You don't show your KML so it is hard to know how you have structured it (are your <Polygon>s nested in <Placemarks> or ` elements for example).
In the simple case if your Polygons are in Placemarks then you could just do the following. Essentially listening for clicks on all objects, then filtering for all Placmark's (either created via the API or loaded in via KML).
function recordEvent(event) {
var target = event.getTarget();
var type = target.getType();
if(type == "KmlPolygon") {
} else if(type == "KmlPlacemark") {
// get the data you want from the target.
var description = target.getDescription();
var balloon = target.getBalloonHtml();
} else if(type == "KmlLineString") {
//etc...
}
};
google.earth.addEventListener(ge.getGlobe(), 'click', recordEvent);
If you wanted to go for the other option you would iterate over the KML Dom once it has loaded and then add events to specific objects. You can do this using something like kmldomwalk.js. Although I wouldn't really recommend this approach here as you will create a large number of event listeners in the api (one for each Placemark in this case). The up side is that the events are attached to each specific object from the kml file, so if you have other Plaemarks, etc, that shouldn't have the same 'click' behaviour then it can be useful.
function placeMarkClick(event) {
var target = event.getTarget();
// get the data you want from the target.
var description = target.getDescription();
var balloon = target.getBalloonHtml();
}
google.earth.fetchKml(ge, href, function (kml) {
if (kml) {
parseKml(kml);
} else {
setTimeout(function () {
alert('Bad or null KML.');
}, 0);
}
});
function parseKml(kml) {
ge.getFeatures().appendChild(kml);
walkKmlDom(kml, function () {
var type = this.getType();
if (type == 'KmlPlacemark') {
// add event listener to `this`
google.earth.addEventListener(this, 'click', placeMarkClick);
}
});
};
Long time since i have worked with this.. but i can try to help you or to give you some tracks...
About your question on "google.earth.addEventListener(ge.getGlobe(), 'click', recordEvent);"
ge.getGlobe can not be replaced with ge.getFeatures() : if you look in the documentation ( https://developers.google.com/earth/documentation/reference/interface_g_e_feature_container-members) for GEFeatureContainer ( which is the output type of getFeatures() , the click Event is not defined!
ge.getGlobe replaced with kmlObject: waht is kmlObject here??
About using getDescription, can you have a look on the getTarget, getCurrentTarget ...
(https://developers.google.com/earth/documentation/reference/interface_kml_event)
As I told you, i haven't work with this since a long time.. so I'm not sure this can help you but at least, it's a first track on which you can look!
Please keep me informed! :-)

Click not firing first time after rebind with live() method

I understand that this is a probably a noob-ish question, but I've had no luck with the other threads I've found on the same topic.
I've devised a workaround to hack a views exposed filter to hide and show products with a stock count of "0". The exposed filter for the stock count (input#edit-stock) is hidden with CSS and inside a custom block is a link to manipulate the form and trigger the query (with ajax). This is working great, but with one exception - after resetting the form with the views-provided "reset" button, toggle() will not rebind properly to the link, and click won't fire the first time. Works fine on the 2nd click. I'm sure that the solution is very simple, but I'm at a loss..
How to rebind toggle() effectively?
Sorry, I'm unable to provide a live example. Many thanks for any input.
CUSTOM BLOCK:
<a id="toggle" href="#">exclude</a>
JQUERY:
$(document).ready(function () {
var include = function () {
$('input#edit-stock').attr('value', 0).submit();
$('a#toggle').html('include');
};
var exclude = function () {
$('input#edit-stock').attr('value', '').submit();
$('a#toggle').html('exclude');
};
$('a#toggle').toggle(include, exclude);
$('input#edit-reset').live('click', function (event) {
$('a#toggle').unbind('toggle').toggle(include, exclude).html('exclude');
});
});
if i get the problem right you need to reset the toggle. Why instead of unbind toggle and rebinding it you just don't simulate a click if the link is == to include?
$(document).ready(function () {
var include = function () {
$('input#edit-stock').attr('value', 0).submit();
$('a#toggle').html('include');
};
var exclude = function () {
$('input#edit-stock').attr('value', '').submit();
$('a#toggle').html('exclude');
};
$('a#toggle').toggle(include, exclude);
$('input#edit-reset').live('click', function (event) {
//if the link is include, click it so that it resets to exclude, else do nothing
if ($('a#toggle').html() == 'include'){
$('a#toggle').click();
}
});
});
fiddle here: http://jsfiddle.net/PSLBb/
(Hope this is what you were looking for)

What is the proper way in OpenLayers (OSM) to trigger a popup for a feature?

I have the feature ID, I can grab the marker layer on GeoRSS loadend, but I'm still not sure how to cause the popup to appear programmatically.
I'll create the popup on demand if that's necessary, but it seems as though I should be able to get the id of the marker as drawn on the map and call some event on that. I've tried using jQuery and calling the $(marker-id).click() event on the map elements, but that doesn't seem to be working. What am I missing?
Since I was asked for code, and since I presumed it to be boilerplate, here's where I am so far:
map = new OpenLayers.Map('myMap');
map.addLayer(new OpenLayers.Layer.OSM());
map.addLayer(new OpenLayers.Layer.GeoRSS(name,url));
//I've done some stuff as well in re: projections and centering and
//setting extents, but those really don't pertain to this question.
Elsewhere I've done a bit of jQuery templating and built me a nice list of all the points that are being shown on the map. I know how to do a callback from the layer loadend and get the layer object, I know how to retrieve my layer out of the map manually, I know how to iter over the layers collection and find my layer. So I can grab any of those details about the popup, but I still don't know how to go about using the built-in methods of the DOM or of this API to make it as easy as element.click() which is what I would prefer to do.
You don't have to click the feature to open a popup.
First you need a reference to the feature from the feature id. I would do that in the loadend event of the GeoRSS layer, using the markers property on the layer.
Assuming you have a reference to your feature, I would write a method which handles the automatic popup:
var popups = {}; // to be able to handle them later
function addPopup(feature) {
var text = getHtmlContent(feature); // handle the content in a separate function.
var popupId = evt.xy.x + "," + evt.xy.y;
var popup = popups[popupId];
if (!popup || !popup.map) {
popup = new OpenLayers.Popup.Anchored(
popupId,
feature.lonlat,
null,
" ",
null,
true,
function(evt) {
delete popups[this.id];
this.hide();
OpenLayers.Event.stop(evt);
}
);
popup.autoSize = true;
popup.useInlineStyles = false;
popups[popupId] = popup;
feature.layer.map.addPopup(popup, true);
}
popup.setContentHTML(popup.contentHTML + text);
popup.show();
}
fwiw I finally came back to this and did something entirely different, but his answer was a good one.
//I have a list of boxes that contain the information on the map (think google maps)
$('.paginatedItem').live('mouseenter', onFeatureSelected).live('mouseleave',onFeatureUnselected);
function onFeatureSelected(event) {
// I stuff the lookup attribute (I'm lazy) into a global
// a global, because there can be only one
hoveredItem = $(this).attr('lookup');
/* Do something here to indicate the onhover */
// find the layer pagination id
var feature = findFeatureById(hoveredItem);
if (feature) {
// use the pagination id to find the event, and then trigger the click for that event to show the popup
// also, pass a null event, since we don't necessarily have one.
feature.marker.events.listeners.click[0].func.call(feature, event)
}
}
function onFeatureUnselected(event) {
/* Do something here to indicate the onhover */
// find the layer pagination id
var feature = findFeatureById(hoveredItem);
if (feature) {
// use the pagination id to find the event, and then trigger the click for that event to show the popup
// also, pass a null event, since we don't necessarily have one.
feature.marker.events.listeners.click[0].func.call(feature, event)
}
/* Do something here to stop the indication of the onhover */
hoveredItem = null;
}
function findFeatureById(featureId) {
for (var key in map.layers) {
var layer = map.layers[key];
if (layer.hasOwnProperty('features')) {
for (var key1 in layer.features) {
var feature = layer.features[key1];
if (feature.hasOwnProperty('id') && feature.id == featureId) {
return feature;
}
}
}
}
return null;
}
also note that I keep map as a global so I don't have to reacquire it everytime I want to use it

How to handle calling a function without oEvent

I have a CheckBox with a handler attached to the select event. In this function is the code to dynamically populate/ display few fields. If I come on the screen and the data brings in a value which makes the checkbox selected already, then those fields are not displayed (because they become visible only when I select the checkbox).
I want to ensure that if the CheckBox is auto selected, still I should be able to process the logic in the function, which has oEvent as an input parameter. But the issue is that if I call this function from another method, that function does not work as it has many statements like oEvent().getSource() which I do not pass.
Controller.js
onCheckBoxSelect: function(oEvent) {
var cells = sap.ui.getCore().byId("cell");
controlCell.destroyContent();
vc.abc();
var material= sap.ui.getCore().byId("abc");
var isSelected = oEvent.getParameters("selected").selected;
if (isSelected) {
// ...
}
},
someFunction : function(){
if(true){
// want to call onCheckBoxSelect here
}
// ...
},
If you assign an ID to your checkbox, you can get the checkbox in any function you want as long as it is known in the view. By doing that you won't need the oEvent which is only available when an event on the checkbox is executed.
Example:
var cb = this.byId('checkboxId');
if(cb.getProperty('selected')) {
// execute code
} else {
// do something else
}
Decouple the handler body into a separate function so that other functions can call the decoupled function with the right arguments. For example:
Controller
onCheckBoxSelect: function(oEvent) {
const bSelected = oEvent.getParameter("selected");
this.doIt(bSelected); // Instead of "doing it" all here
},
someFunction: function(){
if (/*Something truthy*/) {
const checkBox = this.byId("myCheckBox");
const bSelected = checkBox.getSelected();
doIt(bSelected); // passing the same argument as in onCheckBoxSelect
}
// ...
},
doIt: function(bSelected) { // decoupled from onCheckBoxSelect
// ...
if (bSelected) {
// ...
}
},
View
<CheckBox id="myCheckBox"
select=".onCheckBoxSelect"
/>
Or since 1.56:
<CheckBox id="myCheckBox"
select=".doIt(${$parameters>/selected})"
/>
Docu: Handling Events in XML Views
By that, you can have a pure, decoupled function that can be called from anywhere.
I would suggest a different approach. Use the same property that you have used in your checkbox binding, to determine the visibility of the other fields, i.e. bind the visible property of each relevant field to that property in your model.
If there is additional post-processing required in populating the fields, you can either use expression binding or custom formatter for field-specific processing, or model binding events if you need to do a bit more "staging" work (in which case you would probably store the resultant data in a client model and bind to that for populating your fields).