Changing list-item css class using ng-class when mousing over Leaflet Map Markers - angularjs

I've got a doozy of an ng-class problem.
So I have an app with a map/markers on the right and a list-item menu on the left with info about the markers (like Yelp or Foursquare).
With some of my beginner hacking, I got the hover events to sort of work:
But it's odd, the list-item (pink background on hover) only works when I mouseout of the marker. I'm trying to set it up so that when you mouse over the marker, the appropriate list-item's background changes and when you mouseout, it goes back to white. Most of the other ng-class examples/questions I read through seem to work quite differently (they're going for a different functionality).
Ok, to the code:
HTML
<div class="col-md-6" id="leftCol">
<div class="list-group" ng-controller="ShowsCtrl">
<div class="nav nav-stacked list-group-item" id="sidebar" ng-repeat="(key, show) in shows.features" ng-mouseover="menuMouse(show)" ng-mouseout="menuMouseout(show)" ng-class="{hover: $index == hoveritem}">
The key part there is the ng-class="{hover: $index == hoveritem}"
Now I'll show you where hoveritem comes from
Controller
$scope.hoveritem = {};
function pointMouseover(leafletEvent) {
var layer = leafletEvent.target;
//console.log(layer.feature.properties.id);
layer.setIcon(mouseoverMarker);
$scope.hoveritem = layer.feature.properties.id;
console.log($scope.hoveritem);
}
function pointMouseout(leafletEvent) {
var layer = leafletEvent.target;
layer.setIcon(defaultMarker);
}
$scope.menuMouse = function(show){
var layer = layers[show.properties.id];
//console.log(layer);
layer.setIcon(mouseoverMarker);
}
$scope.menuMouseout = function(show){
var layer = layers[show.properties.id];
layer.setIcon(defaultMarker);
}
// Get the countries geojson data from a JSON
$http.get('/json/shows.geojson').success(function (data, status) {
angular.extend($scope, {
geojson: {
data: data,
onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.artist);
layer.setIcon(defaultMarker);
layer.on({
mouseover: pointMouseover,
mouseout: pointMouseout
});
layers[feature.properties.id] = layer;
//console.log(layers);
}
}
});
});
}]);
So mousing over a marker
(layer.on({
mouseover: pointMouseover,
mouseout: pointMouseout
});)
fires the appropriate functions which then changes the icon colors.
I connected the layer.feature.properties.id; to $scope.hoveritem so that my HTML can then use that as the index c. When you mouseover a marker, it then feeds the marker id through to $scope.hoveritem which then it turn goes into the $index part of the HTML, thus changing it's CSS class.
But something is awry. It only changes to the correct list item on mouseout instead of mouseover. Furthermore, I can't figure out to get it to return to the default white state. None of the list items should look active if the mouse is not on a marker.
Any ideas or hints on this would be very appreciated.

The reason for the delay in the mouseover effects was because of the angular $apply digest cycle. Angular basically wasn't aware of the changes to hoveritem. Wrapping it with $scope.$apply did the trick:
$scope.$apply(function () {
$scope.hoveritem = layer.feature.properties.id;
})

Related

Modal is closed when cursor is released outside the modal after Chrome update (angularjs and bootstrap-ui)

Sometimes when I want to quickly select the entire text of an input (within a modal), I begin selecting from the end of the text and move the mouse to the left until the entire text is selected and then I release.
Sometimes this release will occur outside the modal because the mouse movement is fast.
Picture describing the movement:
The problem is that the modal is closed when I release outside.
Question: how can I prevent the modal from closing when releasing outside?
I'm okay with the modal being closed with a click outside. But not okay with the release event.
I'm using:
angularjs 1.5.8
angular-bootstrap 2.5.0 (aka bootstrap-ui)
bootstrap 3.3.7 (only css!!! not js, because js is provided by the above)
Update:
I've created a plunkr and a GIF:
https://plnkr.co/edit/mxDLAdnrQ4p0KKyw?p=info
<div class="modal-body">
<div class="form-group">
<label for="">Foo</label>
<input type="text" class="form-control input-sm" ng-model="foo">
<p>Do this: select the text from right to left and release the mouse outside the modal.</p>
</div>
</div>
GIF:
Update 2
I have new information! This started happening after the last Goole Chrome update! I tried with another computer that had the previous version of Chrome and the modal doesn't close.
//prevent modal close when click starts in modal and ends on backdrop
$(document).on('mousedown', '.modal', function(e){
window.clickStartedInModal = $(e.target).is('.modal-dialog *');
});
$(document).on('mouseup', '.modal', function(e){
if(!$(e.target).is('.modal-dialog *') && window.clickStartedInModal) {
window.preventModalClose = true;
}
});
$("#modal").on("hide.bs.modal", function (e) {
if(window.preventModalClose){
window.preventModalClose = false;
return false;
}
});
The original repository has been archived and no contributions are accepted.
I forked a version and added my fixes for those who are interested:
https://github.com/peteriman/bootstrap
The comparison below:
https://github.com/angular-ui/bootstrap/compare/master...peteriman:modal-patch
= // moved from template to fix issue #2280
- element.on('click', scope.close);
+ var ignoreClick = false;
+ element.on('mousedown', function(evt1) {
+ element.one('mouseup', function(evt2) {
+ if (evt1.target !== evt2.target)
+ ignoreClick = true;
+ });
+ });
+ element.on('click', function(){
+ if (ignoreClick) ignoreClick = false;
+ else scope.close.apply(this, arguments);
+ });
As mousedown and mouseup events trigger before click event, the code checks if mousedown and mouseup are on the same element. If on different elements, it sets ignoreClick=true for the click event to not trigger.
Maintains backward compatibility for click event for existing codes that calls element.click() programmatically.
Original problem:
https://plnkr.co/edit/mxDLAdnrQ4p0KKyw?p=info&preview
Solution by me: (plkr, modal.js, line 103-114)
https://plnkr.co/edit/V42G9NcTUnH9n9M4?p=info&preview
I updated only the code referring to "Modal.js" in bootstrap.js and bootstrap.min.js
Corrected version:
* Bootstrap: modal.js v3.4.1
* https://getbootstrap.com/docs/3.4/javascript/#modals
bootstrap.js print
Yes, this started happening again after the last Goole Chrome update Version 74.0.3729.169, is this a bug with Chrome we can't fix and that we'll just have to wait for a Chrome update for it to be resolved?
or a bootstrap maintainer will update the code for fixing this?
Issue url: https://github.com/twbs/bootstrap/issues/28844
This problem is not recent is already mentioned on github
https://github.com/angular-ui/bootstrap/issues/5810
the following solution works very well with small improvements if necessary.
$rootScope.$watch(() => document.querySelectorAll('.modal').length, val => {
//everytime the number of modals changes
for (let modal of document.querySelectorAll('.modal')) {
if ($uibModalStack.getTop().value.backdrop !== 'static') { // Testing if the
modal is supposed to be static before attaching the event
modal.addEventListener('mousedown', e => {
if (e.which === 1) {
$uibModalStack.getTop().key.dismiss()
}
})
modal.querySelector('.modal-content').addEventListener('mousedown', e => {
e.stopPropagation()
})
}
}
if (val > 0) {
$uibModalStack.getTop().value.backdrop = 'static'
}
})
Another solution on the same principle that keeps the draggrable footer and header of the modal
$rootScope.$watch(function () {
return $document.find('.modal').length;
}, function (val) {
if(openedWindows.top() ) {
var modal = $document.find('.modal');
angular.forEach(modal, function(value) {
if ($modalStack.getTop().value.backdrop !== 'static') {
value.addEventListener('mousedown', function (e) {
if (value === e.target && e.which === 1 && openedWindows.top()) {
$modalStack.getTop().key.dismiss();
}
});
}
});
if (val>0) {
$modalStack.getTop().value.backdrop = 'static';
}
}
});
I'm using Bootstrap v3.0.0 and ran into the same problem. In the end, I had to change a click event to a mousedown event.
In my bootstrap.js file, under the modal.js section, I changed this.$element.on('click.dismiss.modal', $.proxy(function (e) to this.$element.on('mousedown.dismiss.modal', $.proxy(function (e). and everything appears to be working. You may also have to change this in the bootstrap.min.js file.
Note, this will immediately close the modal on mouse down of backdrop so if for some reason you want a user to be able to click down on the backdrop, then drag the mouse and release on the modal, this will not work.
Have you tried using backdrop: 'static'. I think that should do the trick. It is present in the documentation here
Add css padding around modal window and resize it larger. Click outside still works but releasing mouse while dragging over the edge won't close it.
I had a similar situation with range slider. leaving click during slide outside the modal closes it. so I removed data-toggle="modal" and data-target="#mymodal" and added a click event with extra parameters
jQuery('button#modal_toggler').click(function(){
jQuery('#myModal').modal({
backdrop: 'static',
keyboard: false
})
})
backdrop to disable modal close on clicking outside
keyboard this is for my scenario, to disable keyboard entry for closing modal
I have figured out different way to solve the problem, idk if it will cause a problem later but anyway it works, so basically, I put modal-dialog to another <div> object (I call it modal-helper) and then put it to modal. The modal-helper element width and height are inherited (100%) as default but there is small space on top so you can use some margin and padding to close it.
<div class="modal fade" id="login-modal" tabindex="-1" aria-labelledby="loginModalLabel" style="display: none;" aria-hidden="true">
<div id="modal-helper" style="pointer-events: auto;">
<div class="modal-dialog">
...
</div>
</div>
</div>
Then I have used some JS to hide modal when modal-helper (as backdrop) is clicked (by the 'clicked' I mean when pointerup event triggered after pointerdown event on modal-helper).
The code below sets the value of isPointerDownToModalHelper true when pointerdown event triggered on modal-helper, then when the pointerup event triggered on any object it hides the modal and sets the value of isPointerDownToModalHelper back to false:
var login_modal_helper = document.getElementById('modal-helper')
var isPointerDownToModalHelper = false;
addEventListener('pointerdown', (event) => {
var objectID = event['path']['0']['id'];
if (objectID === login_modal_helper.id) { // if pointer was over modal-helper
isPointerDownToModalHelper = true;
}
});
addEventListener('pointerup', (event) => {
if (isPointerDownToModalHelper === true) {
isPointerDownToModalHelper = false;
$('#login-modal').modal('hide'); // hide the modal
}
});
It seems to work fine for now, I hope it can help someone :).

aframe glft click event

I am trying to catch click event for glft model in a scene. Scene code below.
<a-scene raycaster-autorefresh>
<a-assets>
<a-asset-item id="store_homeModel" src="app/home/store_models/store_model.gltf"></a-asset-item>
<a-asset-item id="shampoo" src="app/home/product_models/Ayush Shampoo.gltf"></a-asset-item>
<a-asset-item id="soap" src="app/home/product_models/Ayush Soap.gltf"></a-asset-item>
<a-asset-item id="facewash" src="app/home/product_models/Citra Pearl face wash.gltf"></a-asset-item>
</a-assets>
<a-gltf-model src="#store_homeModel"></a-gltf-model>
<a-gltf-model src="#shampoo" open-desc"></a-gltf-model>
</a-scene>
and component code I have. But click event is not getting catch and i am not able to see console log...
AFRAME.registerComponent('raycaster-autorefresh', {
init: function () {
// This will be called after the entity has properly attached and loaded.
console.log('I am readyyyyy!');
}
});
AFRAME.registerComponent('open-desc', {
init: function() {
var data = this.data;
var el = this.el;
el.addEventListener('click', function() {
console.log("hiiiiiiiiiiii");
});
},
});
Please remember that a-frame renders the whole view on a <canvas>, so you can't just use your mouse to click on any element inside the <a-scene>.
If you want a simple gaze based cursor, you need to attach a cursor to the camera:
<a-camera>
<a-cursor></a-cursor>
</a-camera>
If You want to use the mouse, use the cursor component in the <a-scene>:
<a-scene cursor="rayOrigin:mouse">
If you want to use vive / oculus controllers, you should try laser-controls.
More details about properties and events regarding the cursor can be found in the docs.

angular/firebase remove ng-repeat item and re-add

I have mapbox marker objects that are being stored in a Firebase array. They are being loaded as geojson markers on my map and I am also listing those objects in a container with a simple ng-repeat. My goal is to have a function where, if the particular marker is out of view, to remove that marker from the DOM. If the marker comes back into view, to include that back into the ng-repeated list.
Let's say my list is being displayed like this:
<div id="list-item-container">
<div class="list-item" title="{{marker.name}}" ng-repeat="marker in markers">{{marker.name}}</div>
</div>
In my controller, I'm trying to hide and show these list items based on them being in the map bounds like so:
var markers = L.mapbox.featureLayer()
.addTo(map);
markers.setGeoJSON($scope.driverMarkers);
var listingsFromMarker = function() {
var bounds = map.getBounds();
markers.eachLayer(function(marker) {
var inBounds = [], id = marker.toGeoJSON().$id;
var idElement = $('.list-item[title="'+marker.toGeoJSON().$id+'"]');
if (bounds.contains(marker.getLatLng())) {
HOW DO I GET THIS ITEM BACK IN MY LIST???
} else {
idElement.remove();
}
});
};
map.on('move', function() {
listingsFromMarker();
});
Can anyone steer me in the right direction on how to place this ng-repeated item back into the DOM?
Thank you.
This is not the Angular way to do things. Deleting the DOM element that was created by ng-repeat binding ruins the concept... why would you use Angular at all in this case.. In all cases DOM should be manipulated with the help of Angular directives which are controlled via model.
Thus you should store two arrays. One is the real data - all markers. Another contains only markers that are desired to be shown at this moment in the list.
It will look something like below
In view
<div class="list-item"
ng-repeat="marker in markersInView"
title="{{marker.name}}">{{marker.name}}</div>
In controller
var listingsFromMarker = function() {
var bounds = map.getBounds();
var inBounds = [];
markers.eachLayer(function(marker) {
if (bounds.contains(marker.getLatLng())) {
inBounds.push(marker);
}
});
$scope.markersInView = inBounds;
$scope.$apply();//as this happens on mapbox event it isn't in $digest cycle
//so need to tell Angular to update bindings
};
And of cause you need to initialize $scope.markersInView somewhere in the beginning. This code is not presented in OP so I don't invent it. I guess you will figure out how to filter markers on first show

How can I make it so only an empty state or a drop target placeholder of ui-sortable show?

I have two connected ui-sortable lists. When one of the lists is empty, I need to show a message; when that empty list is hovered while dragging, I need to show a styled drop target and hide the empty list message. I was able to program the vast majority of this code and here is a simplifed Codepen of it working.
The bug is that when you drag from the populated list over the empty list and then out again, the empty list shows both the empty list placeholder and the styled drop target. Here is a screen capture:
The root of the problem appears to be in way I calculate if the list is empty for the sortableList directive:
scope.isEmpty = function() {
if (!scope.attachments) {
return true;
} else if (scope.dragDirection === 'drag-out' && !scope.hovered) {
return scope.attachments.length <= 1;
} else if (scope.hovered) {
return false;
} else {
return scope.attachments.length === 0;
}
};
Note that I am keeping track of the state on the scope and using $apply to ensure the DOM updates like so:
function onDragStart() {
scope.$apply(function() {
scope.dragDirection = 'drag-out';
});
}
function onDragStop() {
scope.$apply(function() {
scope.dragDirection = '';
});
}
function onDragOver() {
scope.$apply(function() {
scope.hovered = true;
});
}
function onDragOut() {
scope.$apply(function() {
scope.hovered = false;
});
}
Here is the html for the directives template:
<div class="drop-target" ui-sortable="sortOptions" ng-model="attachments">
<div ng-repeat="attachment in attachments" class="attachment-box">
<span class="fa fa-bars pull-left drag-handle"></span>
<div class="link-attachment">
<a href ng-href="{{ attachment.fileUrl }}" target="_blank" class="attachment-name">{{ attachment.name }}</a>
<div class="extra-info link-info">{{ attachment.fileType }}</div>
</div>
</div>
<attachment-empty-state ng-show="isEmpty()"></attachment-empty-state>
</div>
The dependency list is quite long for the codepen to work, I simplified the code from actual production code and eliminating the dependencies would have made the custom code quite substantial. Here is a list of the dependencies if you want to try to get it running yourself: jquery, jquery-ui, angular, bootstrap, lodash, and sortable from angular-ui. There is some font-awesome in there as well.
I think I solved the problem. Here is a codepen with the solution.
Basically, the problem was that the dragout event was being (correctly) fired when your cursor dragged the item out of a sortable-list, but the placeholder would stay in the sortable-list until you dragged it into another sortable-list. So in that in between time, both the attachment-empty-state element and the placeholder would be shown in the sortable-list.
Here are the lines that I edited in the code:
Less file:
attachment-empty-state {
...
// hide empty state when the placeholder is in this list
.placeholderShown & {
display:none;
}
}
JS:
//Inside sortable-list
// Helper function
function setPlaceholderShownClass(element) {
$(".drop-target").removeClass("placeholderShown");
$(element).addClass("placeholderShown");
}
...
function onPlaceholderUpdate(container, placeholder) {
setPlaceholderShownClass(container.element.context);
...
}
If you don't like using jQuery to add and remove classes globally, you could use $rootScope.$broadcast("placeholderShown") and $rootScope.$on("placeholderShown",function() { // scope logic }. I figured a little jQuery is less complex, even though it isn't pure Angular.

AngularJS animate dynamic margin

I have an element that appears when the user clicks a button elsewhere on the screen. The element appears to come out of the top of the screen. The element by default needs to be tucked out of view above the screen, so I will have a margin-top style that is based on the height of the element (and will be a negative value). This cannot be hardcoded in css because the element height may vary. When I click the button, I want the element margin-top to change to 0 and I want a transition animation.
The sample shown on angularJS documentation is for adding a removing a class. This would work fine if I knew the values to be set and could code them in CSS, however I cannot. What is the correct way to solve this?
The code below works for displaying and hiding my element using a margin but there is no animation. How do I trigger an animation here when the margin changes?
https://docs.angularjs.org/guide/animations
Quote Total: {{salesPriceTotal + taxesTotal - tradeInsTotal | currency}}
<div class="totals" ng-style="setTopMargin()">
// totals stuff here.
</div>
$scope.setTopMargin = function() {
return {
marginTop: $scope.marginTop
}
};
$scope.$watch('showTotals', function() {
var margin = $scope.showTotals ? 10 : -160 + $scope.modelTotals.length * -200;
$scope.marginTop = margin.toString() + 'px';
});
I added the following code per a suggested solution, but this code is never hit.
myApp.animation('.totals', function () {
return {
move: function (element, done) {
element.css('opacity', 0);
jQuery(element).animate({
opacity: 1
}, done);
// optional onDone or onCancel callback
// function to handle any post-animation
// cleanup operations
return function (isCancelled) {
if (isCancelled) {
jQuery(element).stop();
}
}
},
}
});
As the documentation explains: "The same approach to animation can be used using JavaScript code (jQuery is used within to perform animations)".
So you basically needs to use animate() from jQuery to do what you want.

Resources